Describe the bug
Things which rely on _partition_cutpath leave partial subpaths at the edges.
Here's the logic:
function _partition_cutpath(l, h, cutsize, cutpath, gap, cutpath_centered) =
let(
//--snip--
reps_raw = ceil(l/(cutsize.x+gap)),
reps = reps_raw%2==0 && cutpath_centered ? reps_raw+1 : reps_raw,
cplen = (cutsize.x+gap) * reps,
path = deduplicate(concat(
[[-l/2, cutpath[0].y*cutsize.y]],
[for (i=[0:1:reps-1], pt=cutpath) v_mul(pt,cutsize)+[i*(cutsize.x+gap)+gap/2-cplen/2,0]],
[[ l/2, cutpath[len(cutpath)-1].y*cutsize.y]]
)),
//--snip--
Code To Reproduce Bug
include <BOSL2/std.scad>
include <BOSL2/partitions.scad>
partition(cutsize=[10,5], cutpath="hammerhead")
cuboid([47,10,5]);
The interesting part is that 47 isn't divisible by 10.
Here's a more interesting example, pay particular attention to the gap = 3 line:
include <BOSL2/std.scad>
include <BOSL2/partitions.scad>
l = 47;
cutsize=[10,5];
ycopies(n=5, spacing=30)
{
gap = $idx;
xdistribute(25)
{
ydistribute(15)
{
partition(cutsize=cutsize, gap=gap, cutpath="dovetail") cuboid([l,10,5]);
}
fwd(10)
text(str(
"gap = ", gap,
"; cutsize.x+gap = ", cutsize.x+gap,
"; f = ", floor(l / (cutsize.x+gap)),
"; c = ", ceil(l / (cutsize.x+gap)),
"; rem = ", l % (cutsize.x+gap)));
}
}
It's a problem whenever the cutpath does not have x always go up.
Expected behavior
High-level: No extra floating bits at the ends.
More specifically, if there are reps subpaths, there will be reps-1 gaps. Pick the biggest reps such that reps*cutsize.x + (reps-1)*gap <= l and, if cutpath_centered, ensure reps is odd.
I think that means something like this:
function _partition_cutpath(l, h, cutsize, cutpath, gap, cutpath_centered) =
let(
check = assert(is_finite(l))
assert(is_finite(h))
assert(is_finite(gap))
assert(is_finite(cutsize) || is_vector(cutsize,2))
+ assert(l >= cutsize.x)
assert(is_string(cutpath) || is_path(cutpath,2)),
cutsize = is_vector(cutsize)? cutsize : [cutsize*2, cutsize],
cutpath = is_path(cutpath)? cutpath :
_partition_subpath(cutpath),
+ // pick the biggest reps_raw such that cplen will be <= l
+ reps_raw = 1+floor((l - cutsize.x)/(cutsize.x + gap)),
- reps_raw = ceil(l/(cutsize.x+gap)),
+ // if cutpath_centered, make sure reps is odd so a subpath will be centered on the cutpath
+ // be sure to always pick a smaller value so we don't truncate the path
+ reps = reps_raw%2==0 && cutpath_centered ? reps_raw-1 : reps_raw,
- reps = reps_raw%2==0 && cutpath_centered ? reps_raw+1 : reps_raw,
+ // there will be reps copies of the subpath and reps-1 gaps
+ cplen = reps*cutsize.x + (reps-1)*gap,
- cplen = (cutsize.x+gap) * reps,
path = deduplicate(concat(
- [[-cplen/2, cutpath[0].y*cutsize.y]],
+ [[-l/2, cutpath[0].y*cutsize.y]],
- [for (i=[0:1:reps-1], pt=cutpath) v_mul(pt,cutsize)+[i*(cutsize.x+gap)+gap/2-cplen/2,0]],
+ [for (i=[0:1:reps-1], pt=cutpath) v_mul(pt,cutsize)+[i*(cutsize.x+gap),0]],
- [[ l/2, cutpath[len(cutpath)-1].y*cutsize.y]]
+ [[ cplen/2, cutpath[len(cutpath)-1].y*cutsize.y]]
)),
Even though I'm presenting the above as a diff, I haven't tested it. I don't know what the policy is for breaking changes, but I only started looking at this because I was wrestling with tweaking parameters to try to get the preview to look the way I wanted.
If you like this approach, I'm happy to make a PR and do some validation. I just wanted to start by checking to see if you need a more backward-compatible solution.
Screenshots
See above.
Additional context
Add any other context about the problem here.
- OpenSCAD Version: 2025.06.22 (git de4f48109)
- Other libraries used: none
There's some other odd stuff, not really related to this bug, in the function.
It takes an h parameter (and callers pass in the extrusion height) which isn't used.
I don't think this code is really necessary as long as the cutpath points have 0 <= x <= 1:
stidxs = [for (i = idx(path)) if (path[i].x < -l/2) i],
enidxs = [for (i = idx(path)) if (path[i].x > +l/2) i],
stidx = stidxs? last(stidxs) : 0,
enidx = enidxs? enidxs[0] : -1,
trunc = select(path, stidx, enidx)
But, if someone "scribbles outside the lines", silently truncating might be a surprise.
Describe the bug
Things which rely on
_partition_cutpathleave partial subpaths at the edges.Here's the logic:
Code To Reproduce Bug
The interesting part is that 47 isn't divisible by 10.
Here's a more interesting example, pay particular attention to the
gap = 3line:It's a problem whenever the
cutpathdoes not havexalways go up.Expected behavior
High-level: No extra floating bits at the ends.
More specifically, if there are
repssubpaths, there will bereps-1gaps. Pick the biggestrepssuch thatreps*cutsize.x + (reps-1)*gap <= land, ifcutpath_centered, ensurerepsis odd.I think that means something like this:
function _partition_cutpath(l, h, cutsize, cutpath, gap, cutpath_centered) = let( check = assert(is_finite(l)) assert(is_finite(h)) assert(is_finite(gap)) assert(is_finite(cutsize) || is_vector(cutsize,2)) + assert(l >= cutsize.x) assert(is_string(cutpath) || is_path(cutpath,2)), cutsize = is_vector(cutsize)? cutsize : [cutsize*2, cutsize], cutpath = is_path(cutpath)? cutpath : _partition_subpath(cutpath), + // pick the biggest reps_raw such that cplen will be <= l + reps_raw = 1+floor((l - cutsize.x)/(cutsize.x + gap)), - reps_raw = ceil(l/(cutsize.x+gap)), + // if cutpath_centered, make sure reps is odd so a subpath will be centered on the cutpath + // be sure to always pick a smaller value so we don't truncate the path + reps = reps_raw%2==0 && cutpath_centered ? reps_raw-1 : reps_raw, - reps = reps_raw%2==0 && cutpath_centered ? reps_raw+1 : reps_raw, + // there will be reps copies of the subpath and reps-1 gaps + cplen = reps*cutsize.x + (reps-1)*gap, - cplen = (cutsize.x+gap) * reps, path = deduplicate(concat( - [[-cplen/2, cutpath[0].y*cutsize.y]], + [[-l/2, cutpath[0].y*cutsize.y]], - [for (i=[0:1:reps-1], pt=cutpath) v_mul(pt,cutsize)+[i*(cutsize.x+gap)+gap/2-cplen/2,0]], + [for (i=[0:1:reps-1], pt=cutpath) v_mul(pt,cutsize)+[i*(cutsize.x+gap),0]], - [[ l/2, cutpath[len(cutpath)-1].y*cutsize.y]] + [[ cplen/2, cutpath[len(cutpath)-1].y*cutsize.y]] )),Even though I'm presenting the above as a diff, I haven't tested it. I don't know what the policy is for breaking changes, but I only started looking at this because I was wrestling with tweaking parameters to try to get the preview to look the way I wanted.
If you like this approach, I'm happy to make a PR and do some validation. I just wanted to start by checking to see if you need a more backward-compatible solution.
Screenshots
See above.
Additional context
Add any other context about the problem here.
There's some other odd stuff, not really related to this bug, in the function.
It takes an
hparameter (and callers pass in the extrusion height) which isn't used.I don't think this code is really necessary as long as the
cutpathpoints have0 <= x <= 1:But, if someone "scribbles outside the lines", silently truncating might be a surprise.