From ab04738768b9cdbafe91e0f5ec980f39cb825fb0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 15 Jun 2026 13:23:52 +0000 Subject: [PATCH 1/4] Version bump to v2.0.744 --- version.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.scad b/version.scad index 21e9ef19..defed886 100644 --- a/version.scad +++ b/version.scad @@ -14,7 +14,7 @@ _BOSL2_VERSION = is_undef(_BOSL2_STD) && (is_undef(BOSL2_NO_STD_WARNING) || !BOS -BOSL_VERSION = [2,0,743]; +BOSL_VERSION = [2,0,744]; From 744bee40d2f3328de98d74db63c2b880c563300a Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Mon, 22 Jun 2026 20:19:11 -0400 Subject: [PATCH 2/4] Fix anchoring for linear_sweep() so twist=360*k+180 will work Fix scale support for linear_sweep with texturing --- masks.scad | 7 ++- skin.scad | 178 +++++++++++++++++++++++++++++++++-------------------- 2 files changed, 117 insertions(+), 68 deletions(-) diff --git a/masks.scad b/masks.scad index 39c8f7d0..51297270 100644 --- a/masks.scad +++ b/masks.scad @@ -1322,7 +1322,7 @@ module edge_profile(edges=EDGES_ALL, except=[], excess=0.01, convexity=10) { // excess = Excess length to extrude the profile to make edge masks. Default: 0.01 // convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10 // flip = If true, reverses the orientation of any external profile parts at each edge. Default false -// corner_type = Specifies how exterior corners should be formed. Must be one of `"none"`, `"chamfer"`, `"round"`, or `"sharp"`. Default: `"none"` +// corner_type = Specifies how to form exterior corners, and if `size` is set, interior corners. Must be one of `"none"`, `"chamfer"`, `"round"`, or `"sharp"`. Default: `"none"` // size = If given the width and height of the 2D profile, enable rounding and chamfering of internal corners when given a negative profile. // Side Effects: // Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. @@ -1561,7 +1561,10 @@ module edge_profile_asym( check1 = assert($parent_geom != undef, "\nNo object to attach to!") assert(is_cuboid, "Parent must be a cuboid") assert(in_list(corner_type, ["none", "round", "chamfer", "sharp"])) - assert(is_bool(flip)); + assert(is_bool(flip)) + assert((is_number(size) && size>0) || (is_vector(size,2) && all_positive(size)), + "size must be a positive number of 2-vector"); + size = force_list(size,2); edges = _edges(edges, except=except); vecs = [ for (i = [0:3], axis=[0:2]) diff --git a/skin.scad b/skin.scad index 8cb6b16a..bfacae10 100644 --- a/skin.scad +++ b/skin.scad @@ -587,7 +587,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close // Named Anchors: // "origin" = Centers the extruded shape vertically only, but keeps the original path positions in the X and Y. Oriented UP. // "original_base" = Keeps the original path positions in the X and Y, but at the bottom of the extrusion. Oriented DOWN. -// "original_top" = Keeps the original path positions in the X and Y, but at the top of the extrusion. Oriented UP. +// "original_top" = Keeps the original path positions in the X and Y, but at the top height of the extrusion. Oriented UP. // "edge0", "edge1", etc. = Center of each side edge, spin pointing up along the edge. Can access with EDGE(i) // "face0", "face1", etc. = Center of each side face, spin pointing up. Can access with FACE(i) // "top_edge0", "top_edge1", etc = Center of each top edge, spin pointing clockwise (from top). Can access with EDGE(TOP,i) @@ -785,61 +785,102 @@ module linear_sweep( } -function _make_all_prism_anchors(bot, top, startind=0) = - let( - facenormal= [ - for(i=idx(bot)) - let( - edge0 = [top[i],bot[i]], // vertical edge at i - edge1 = [select(top,i+1),select(bot,i+1)], // vertical edge at i+1 - facenormal = unit(unit(cross(edge1[1]-edge0[0], edge0[1]-edge0[0]))+ - unit(cross(edge0[0]-edge1[1], edge1[0]-edge1[1]))) - ) - facenormal - ], - anchors = [for(i=idx(bot)) - let( - edge1 = [top[i],bot[i]], // vertical edge at i - edge2 = [select(top,i+1),select(bot,i+1)], // vertical edge at i+1 - - facecenter = mean(concat(edge1,edge2)), - facespin = _compute_spin(facenormal[i], UP), - - side_edge_center = mean(edge1), - side_edge_dir = top[i]-bot[i], - side_edge_normal = unit(vector_bisect(facenormal[i],select(facenormal,i-1))), - side_edge_spin = _compute_spin(side_edge_normal, side_edge_dir), - side_edge_angle = 180-vector_angle(facenormal[i], select(facenormal,i-1)), - side_edge_len = norm(side_edge_dir), - - top_edge_center = (edge2[0]+edge1[0])/2, - top_edge_dir = edge2[0]-edge1[0], - bot_edge_center = (edge1[1]+edge2[1])/2, - bot_edge_dir = edge1[1]-edge2[1], - topnormal = unit(facenormal[i]+UP), - botnormal = unit(facenormal[i]+DOWN), - topedgespin = _compute_spin(topnormal, top_edge_dir), - botedgespin = _compute_spin(botnormal, bot_edge_dir), - topedgeangle = 180-vector_angle(UP,facenormal[i]) - ) - each [ - named_anchor(str("face",i+startind), facecenter, facenormal[i], facespin), - named_anchor(str("edge",i+startind), side_edge_center, side_edge_normal, side_edge_spin, - info=[["edge_angle",side_edge_angle], ["edge_length",side_edge_len]]), - named_anchor(str("top_edge",i+startind), top_edge_center, topnormal, topedgespin, - info=[["edge_angle",topedgeangle],["edge_length",norm(top_edge_dir)]]), - named_anchor(str("bot_edge",i+startind), bot_edge_center, botnormal, botedgespin, - info=[["edge_angle",180-topedgeangle],["edge_length",norm(bot_edge_dir)]]), - named_anchor(str("top_corner",i+startind), top[i], unit(side_edge_normal+UP), - _compute_spin(unit(side_edge_normal+UP),side_edge_dir)), - named_anchor(str("bot_corner",i+startind), bot[i], unit(side_edge_normal+DOWN), - _compute_spin(unit(side_edge_normal+DOWN),side_edge_dir)) - ] - ] - ) - anchors; +function _lsw_sweep_tangent(p, u, h, shift=[0,0], scale=[1,1], twist=0) = + let( + sc = lerp([1,1], scale, u), + dsc = scale - [1,1], + q = zrot(-twist*u, p), + k = -twist*PI/180 + ) + [ + dsc.x*q.x - sc.x*k*q.y + shift.x, + dsc.y*q.y + sc.y*k*q.x + shift.y, + h + ]; + +function _lsw_face_normal(path, layer, i, u, h, shift=[0,0], scale=[1,1], twist=0, p=undef) = + let( + p0 = select(path, i), + p1 = select(path, i+1), + ps = is_undef(p)? (p0+p1)/2 : p, + eh = select(layer, i+1) - select(layer, i), + vs = _lsw_sweep_tangent(ps, u, h, shift=shift, scale=scale, twist=twist), + raw = cross(vs, eh), + fb = cross(UP, eh) + ) + unit(raw, error=unit(fb, error=RIGHT)); +function _lsw_seam_normal(path, layer, i, u, h, shift=[0,0], scale=[1,1], twist=0) = + let( + p = select(path, i), + n0 = _lsw_face_normal(path, layer, i-1, u, h, shift=shift, scale=scale, twist=twist, p=p), + n1 = _lsw_face_normal(path, layer, i, u, h, shift=shift, scale=scale, twist=twist, p=p) + ) + unit(vector_bisect(n1, n0), error=unit(n1+n0, error=n1)); + + +function _make_all_linear_sweep_anchors(path, midpath, toppath, h, shift=[0,0], scale=[1,1], twist=0, startind=0) = + let( + bot = path3d(path, -h/2), + mid = path3d(midpath, 0), + top = path3d(toppath, h/2) + ) + [ + for (i=idx(path)) + let( + p0 = path[i], + + mid_face_normal = _lsw_face_normal(path, mid, i, 1/2, h, shift=shift, scale=scale, twist=twist), + top_face_normal = _lsw_face_normal(path, top, i, 1, h, shift=shift, scale=scale, twist=twist), + bot_face_normal = _lsw_face_normal(path, bot, i, 0, h, shift=shift, scale=scale, twist=twist), + + facecenter = (mid[i] + select(mid, i+1)) / 2, + faceref = unit(point3d(shift,h)), + facespin = _compute_spin(mid_face_normal, faceref), + + side_edge_center = mid[i], + side_edge_dir = _lsw_sweep_tangent(p0, 1/2, h, shift=shift, scale=scale, twist=twist), + side_edge_normal = _lsw_seam_normal(path, mid, i, 1/2, h, shift=shift, scale=scale, twist=twist), + side_edge_spin = _compute_spin(side_edge_normal, side_edge_dir), + side_edge_angle = 180 - vector_angle( + _lsw_face_normal(path, mid, i, 1/2, h, shift=shift, scale=scale, twist=twist, p=p0), + _lsw_face_normal(path, mid, i-1, 1/2, h, shift=shift, scale=scale, twist=twist, p=p0) + ), + side_edge_len = norm(top[i]-bot[i]), + + top_edge_center = (top[i] + select(top, i+1)) / 2, + top_edge_dir = select(top, i+1) - top[i], + topnormal = unit(top_face_normal + UP, error=UP), + topedgespin = _compute_spin(topnormal, top_edge_dir), + topedgeangle = 180 - vector_angle(UP, top_face_normal), + + bot_edge_center = (bot[i] + select(bot, i+1)) / 2, + bot_edge_dir = bot[i] - select(bot, i+1), + botnormal = unit(bot_face_normal + DOWN, error=DOWN), + botedgespin = _compute_spin(botnormal, bot_edge_dir), + botedgeangle = 180 - vector_angle(DOWN, bot_face_normal), + + top_corner_pos = top[i], + top_corner_norm = unit(_lsw_seam_normal(path, top, i, 1, h, shift=shift, scale=scale, twist=twist) + UP, error=UP), + + bot_corner_pos = select(bot, i), + bot_corner_norm = unit(_lsw_seam_normal(path, bot, i, 0, h, shift=shift, scale=scale, twist=twist) + DOWN, error=DOWN) + ) + each [ + named_anchor(str("face", i+startind), facecenter, mid_face_normal, facespin), + named_anchor(str("edge", i+startind), side_edge_center, side_edge_normal, side_edge_spin, + info=[["edge_angle", side_edge_angle], ["edge_length", side_edge_len]]), + named_anchor(str("top_edge", i+startind), top_edge_center, topnormal, topedgespin, + info=[["edge_angle", topedgeangle], ["edge_length", norm(top_edge_dir)]]), + named_anchor(str("bot_edge", i+startind), bot_edge_center, botnormal, botedgespin, + info=[["edge_angle", botedgeangle], ["edge_length", norm(bot_edge_dir)]]), + named_anchor(str("top_corner", i+startind), top_corner_pos, top_corner_norm, + _compute_spin(top_corner_norm, side_edge_dir)), + named_anchor(str("bot_corner", i+startind), bot_corner_pos, bot_corner_norm, + _compute_spin(bot_corner_norm, side_edge_dir)) + ] + ]; function linear_sweep( @@ -866,8 +907,11 @@ function linear_sweep( assert(is_vector(shift, 2), str(shift)) assert(is_bool(caps) || is_bool_list(caps,2), "\ncaps must be boolean or a list of two booleans.") let( + scale = is_num(scale) ? [scale, scale] : point2d(scale), h = one_defined([h, height,l,length],"h,height,l,length",dflt=1), regions = region_parts(region), + topmat = move(shift) * scale(scale) * rot(-twist), // needed for anchoring even in texture case + midmat = move(shift/2) * scale(lerp([1,1],scale,1/2)) * rot(-twist/2), // needed for anchoring even in texture case vnf = !is_undef(texture)? _textured_linear_sweep( region, h=h, caps=caps, @@ -882,8 +926,6 @@ function linear_sweep( center == false? "original_base" : default(anchor, "original_base"), slices = default(slices, max(1,ceil(abs(twist)/5))), - scale = is_num(scale)? [scale,scale] : point2d(scale), - topmat = move(shift) * scale(scale) * rot(-twist), trgns = [ for (rgn = regions) [ for (path = rgn) let( @@ -922,14 +964,19 @@ function linear_sweep( vnf, regparts = flatten(regions), sizes = [0,each cumsum([for(entry=regparts) len(entry)])], - ganchors = [ - for(i=idx(regparts)) - let( - bot = path3d(regparts[i],-h/2), - top = path3d(move(shift,scale(scale, zrot(-twist, regparts[i]))),h/2) - ) - each _make_all_prism_anchors(bot,top, startind=sizes[i]) - ], +ganchors = [ + for (i=idx(regparts)) + each _make_all_linear_sweep_anchors( + regparts[i], + apply(midmat,regparts[i]), + apply(topmat,regparts[i]), + h=h, + shift=shift, + scale=scale, + twist=twist, + startind=sizes[i] + ) +], anchors = [ named_anchor("original_base", [0,0,-h/2], DOWN), named_anchor("original_top", [0,0,h/2], UP), @@ -4545,8 +4592,7 @@ function _textured_linear_sweep( inset = is_num(inset)? inset : inset? 1 : 0, twist = default(twist, 0), shift = default(shift, [0,0]), - scale = scale==undef? [1,1,1] : - is_num(scale)? [scale,scale,1] : scale, + scale = scale==undef? [1,1,1] : point3d(scale,1), samples = !is_vnf(texture)? len(texture[0]) : is_num(samples)? samples : 8, vnf_tile = From 3de0acb4106f5e102116453954676d229b1dfe9d Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Mon, 22 Jun 2026 20:58:33 -0400 Subject: [PATCH 3/4] bugfix --- masks.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/masks.scad b/masks.scad index 51297270..fbd7cdc2 100644 --- a/masks.scad +++ b/masks.scad @@ -1562,7 +1562,7 @@ module edge_profile_asym( assert(is_cuboid, "Parent must be a cuboid") assert(in_list(corner_type, ["none", "round", "chamfer", "sharp"])) assert(is_bool(flip)) - assert((is_number(size) && size>0) || (is_vector(size,2) && all_positive(size)), + assert((is_num(size) && size>0) || (is_vector(size,2) && all_positive(size)), "size must be a positive number of 2-vector"); size = force_list(size,2); edges = _edges(edges, except=except); From be2367e73eab3867c3c66a5664dfd891db16c8cf Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Mon, 22 Jun 2026 21:43:45 -0400 Subject: [PATCH 4/4] bugfix --- masks.scad | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/masks.scad b/masks.scad index fbd7cdc2..971b88ac 100644 --- a/masks.scad +++ b/masks.scad @@ -1420,7 +1420,7 @@ module edge_profile_asym( edges=EDGES_ALL, except=[], excess=0.01, convexity=10, flip=false, corner_type="none", - size=[0,0] + size=undef ) { function _corner_orientation(pos,pvec) = let( @@ -1562,9 +1562,10 @@ module edge_profile_asym( assert(is_cuboid, "Parent must be a cuboid") assert(in_list(corner_type, ["none", "round", "chamfer", "sharp"])) assert(is_bool(flip)) - assert((is_num(size) && size>0) || (is_vector(size,2) && all_positive(size)), + assert(is_undef(size) || (is_num(size) && size>0) || (is_vector(size,2) && all_positive(size)), "size must be a positive number of 2-vector"); - size = force_list(size,2); + size = is_undef(size) ? [0,0] : force_list(size,2); + edges = _edges(edges, except=except); vecs = [ for (i = [0:3], axis=[0:2]) @@ -1599,11 +1600,11 @@ module edge_profile_asym( $profile_type = "corner"; position(pos) { multmatrix(mirT) { - if (vp1.x == vp2.x && size.y > 0) { + if (vp1.x == vp2.x && size.x>0) { zflip() { if (corner_type=="chamfer") { fn = $fn; - move([size.y,size.y]) { + move(size) { rotate_extrude(angle=90, $fn=4) left_half(planar=true, $fn=fn) zrot(-90) fwd(size.y) children(); @@ -1616,7 +1617,7 @@ module edge_profile_asym( square([size.x+0.01, size.y+0.01]); } } else if (corner_type=="round") { - move([size.y,size.y]) { + move(size) { rotate_extrude(angle=90) left_half(planar=true) zrot(-90) fwd(size.y) children();