Merge pull request #1724 from adrianVmariano/master

add named anchors to linear_sweep
This commit is contained in:
adrianVmariano
2025-06-21 08:16:46 -04:00
committed by GitHub
3 changed files with 148 additions and 79 deletions

View File

@@ -4145,7 +4145,9 @@ function _prism_fillet_prism(name, basepoly, bot, top, d, k, N, overlap, uniform
// The prism will connect anchor points described by the two descriptions you supply. The supported object // The prism will connect anchor points described by the two descriptions you supply. The supported object
// types are prismoids, VNFs, cylinders, spheres, and linear sweeps. For prismoids and VNFs you can use any anchor on a face // types are prismoids, VNFs, cylinders, spheres, and linear sweeps. For prismoids and VNFs you can use any anchor on a face
// or edge anchors that include edge geometry. For spheres you can use any anchor. In the case of cylinders and linear sweeps you can // or edge anchors that include edge geometry. For spheres you can use any anchor. In the case of cylinders and linear sweeps you can
// attach to the flat top or bottom in any case, but for side attachments, the shape must not have scaling (so it cannot // attach to the flat top or bottom, or to named face anchors in any case. When you do this, the attachment is treated as an infinite plane.
// You can attach to the side of the extrusion and follow the shape of the extrusion using the standard anchors only, but the
// shape must not have scaling (so it cannot
// be conical) and it must not have any shift. Only right angle cylinders and extrusions are supported. // be conical) and it must not have any shift. Only right angle cylinders and extrusions are supported.
// Anchors on the top and bottom edges are also not supported. When connecting to an extrusion the selected anchor // Anchors on the top and bottom edges are also not supported. When connecting to an extrusion the selected anchor
// point must lie on the surface of the shape. This may requires setting `atype="intersect"` when creating the extrusion. // point must lie on the surface of the shape. This may requires setting `atype="intersect"` when creating the extrusion.
@@ -4479,7 +4481,7 @@ function _get_obj_type(ind,geom,anchor,prof) =
) )
[[x,-y],[0,0], [x,y]] [[x,-y],[0,0], [x,y]]
: starts_with(geom[0], "extrusion") ? : starts_with(geom[0], "extrusion") ?
anchor==UP || anchor==DOWN ? "plane" anchor==UP || anchor==DOWN || starts_with(anchor,"face") ? "plane"
: :
assert(geom[3]==0, str("Extrusion in desc", ind, " has nonzero twist, which is not supported.")) assert(geom[3]==0, str("Extrusion in desc", ind, " has nonzero twist, which is not supported."))
assert(geom[5]==[0,0], str("Extrusion in desc", ind, " has nonzero shift, which is not supported.")) assert(geom[5]==[0,0], str("Extrusion in desc", ind, " has nonzero shift, which is not supported."))

View File

@@ -851,8 +851,7 @@ function prismoid(
// being located at the bottom of the shape, so confirm anchor positions before use. // being located at the bottom of the shape, so confirm anchor positions before use.
// Additional named face and edge anchors are located on the side faces and vertical edges of the prism. // Additional named face and edge anchors are located on the side faces and vertical edges of the prism.
// You can use `EDGE(i)`, `EDGE(TOP,i)` and `EDGE(BOT,i)` as a shorthand for accessing the named edge anchors, and `FACE(i)` for the face anchors. // You can use `EDGE(i)`, `EDGE(TOP,i)` and `EDGE(BOT,i)` as a shorthand for accessing the named edge anchors, and `FACE(i)` for the face anchors.
// When you use `shift`, which moves the top face of the prism, the spin for the side face and edges anchors will align // The "edge0" anchor identifies an edge located along the X+ axis, and then edges
// the child with the edge or face direction. The "edge0" anchor identifies an edge located along the X+ axis, and then edges
// are labeled counting up in the clockwise direction. Similarly "face0" is the face immediately clockwise from "edge0", and face // are labeled counting up in the clockwise direction. Similarly "face0" is the face immediately clockwise from "edge0", and face
// labeling proceeds clockwise. The top and bottom edge anchors label edges directly above and below the face with the same label. // labeling proceeds clockwise. The top and bottom edge anchors label edges directly above and below the face with the same label.
// If you set `realign=true` then "face0" is oriented in the X+ direction. // If you set `realign=true` then "face0" is oriented in the X+ direction.

218
skin.scad
View File

@@ -541,6 +541,15 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
// correct only for twisted objects, and corner anchors may point in unexpected directions in some cases. These anchors also ignore any applied texture. // correct only for twisted objects, and corner anchors may point in unexpected directions in some cases. These anchors also ignore any applied texture.
// If you need anchors directly computed from the surface you can pass the vnf from linear_sweep // If you need anchors directly computed from the surface you can pass the vnf from linear_sweep
// to {{vnf_polyhedron()}}, which computes anchors directly from the full VNF. // to {{vnf_polyhedron()}}, which computes anchors directly from the full VNF.
// Additional named face and edge anchors are located on the side faces and vertical edges of the prism.
// When you sweep a polygon you can use `EDGE(i)`, `EDGE(TOP,i)` and `EDGE(BOT,i)` as a shorthand for
// accessing the named edge anchors, and `FACE(i)` for the face anchors.
// The "edge0" anchor identifies an edge located along the X+ axis, and then edges
// are labeled counting up in the clockwise direction. Similarly "face0" is the face immediately clockwise from "edge0", and face
// labeling proceeds clockwise. The top and bottom edge anchors label edges directly above and below the face with the same label.
// When you sweep a region, the region is decomposed using {{region_parts()}} and the anchors are generated for the region components
// in the order produced by the decomposition, working entirely through each component and then on to the next component.
// The anchors for twisted shapes may be inaccurate.
// Arguments: // Arguments:
// region = The 2D [Region](regions.scad) or polygon that is to be extruded. // region = The 2D [Region](regions.scad) or polygon that is to be extruded.
// h / height / l / length = The height to extrude the region. Default: 1 // h / height / l / length = The height to extrude the region. Default: 1
@@ -574,6 +583,12 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
// "origin" = Centers the extruded shape vertically only, but keeps the original path positions in the X and Y. Oriented UP. // "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_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 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)
// "bot_edge0", "bot_edge1", etc = Center of each bottom edge, spin pointing clockwise (from bottom). Can access with EDGE(BOT,i)
// "top_corner0", "top_corner1", etc = Top corner, pointing in direction of associated edge anchor, spin up along associated edge
// "bot_corner0", "bot_corner1", etc = Bottom corner, pointing in direction of associated edge anchor, spin up along associated edge
// Example: Extruding a Compound Region. // Example: Extruding a Compound Region.
// rgn1 = [for (d=[10:10:60]) circle(d=d,$fn=8)]; // rgn1 = [for (d=[10:10:60]) circle(d=d,$fn=8)];
// rgn2 = [square(30,center=false)]; // rgn2 = [square(30,center=false)];
@@ -743,7 +758,7 @@ module linear_sweep(
anchor = center==true? "origin" : anchor = center==true? "origin" :
center == false? "original_base" : center == false? "original_base" :
default(anchor, "original_base"); default(anchor, "original_base");
vnf = linear_sweep( vnf_geom = linear_sweep(
region, height=h, style=style, caps=caps, region, height=h, style=style, caps=caps,
twist=twist, scale=scale, shift=shift, twist=twist, scale=scale, shift=shift,
texture=texture, texture=texture,
@@ -755,31 +770,73 @@ module linear_sweep(
tex_depth=tex_depth, tex_depth=tex_depth,
tex_samples=tex_samples, tex_samples=tex_samples,
slices=slices, slices=slices,
maxseg=maxseg, maxseg=maxseg, atype=atype,
anchor="origin" anchor="origin", _return_geom=true
); );
anchors = [ attachable(anchor,spin,orient, geom=vnf_geom[1]) {
named_anchor("original_base", [0,0,-h/2], DOWN), vnf_polyhedron(vnf_geom[0], convexity=convexity);
named_anchor("original_top", [0,0,h/2], UP),
];
cp = default(cp, "centroid");
geom = atype=="hull"? attach_geom(cp=cp, region=region, h=h, extent=true, shift=shift, scale=scale, twist=twist, anchors=anchors) :
atype=="intersect"? attach_geom(cp=cp, region=region, h=h, extent=false, shift=shift, scale=scale, twist=twist, anchors=anchors) :
atype=="bbox"?
let(
bounds = pointlist_bounds(flatten(region)),
size = bounds[1] - bounds[0],
midpt = (bounds[0] + bounds[1])/2
)
attach_geom(cp=[0,0,0], size=point3d(size,h), offset=point3d(midpt), shift=shift, scale=scale, twist=twist, anchors=anchors) :
assert(in_list(atype, ["hull","intersect","bbox"]), "\nAnchor type must be \"hull\", \"intersect\", or \"bbox\".");
attachable(anchor,spin,orient, geom=geom) {
vnf_polyhedron(vnf, convexity=convexity);
children(); children();
} }
} }
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 linear_sweep( function linear_sweep(
region, height, center, region, height, center,
twist=0, scale=1, shift=[0,0], twist=0, scale=1, shift=[0,0],
@@ -788,7 +845,7 @@ function linear_sweep(
texture, tex_size=[5,5], tex_reps, tex_counts, texture, tex_size=[5,5], tex_reps, tex_counts,
tex_inset=false, tex_rot=0, tex_inset=false, tex_rot=0,
tex_scale, tex_depth, tex_samples, h, l, length, tex_scale, tex_depth, tex_samples, h, l, length,
anchor, spin=0, orient=UP anchor, spin=0, orient=UP, _return_geom=false
) = ) =
assert(num_defined([tex_reps,tex_counts])<2, "\nIn linear_sweep() the 'tex_counts' parameter has been replaced by 'tex_reps'. You cannot give both.") assert(num_defined([tex_reps,tex_counts])<2, "\nIn linear_sweep() the 'tex_counts' parameter has been replaced by 'tex_reps'. You cannot give both.")
assert(num_defined([tex_scale,tex_depth])<2, "\nIn linear_sweep() the 'tex_scale' parameter has been replaced by 'tex_depth'. You cannot give both.") assert(num_defined([tex_scale,tex_depth])<2, "\nIn linear_sweep() the 'tex_scale' parameter has been replaced by 'tex_depth'. You cannot give both.")
@@ -804,63 +861,74 @@ function linear_sweep(
assert(is_vector(shift, 2), str(shift)) 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.") assert(is_bool(caps) || is_bool_list(caps,2), "\ncaps must be boolean or a list of two booleans.")
let( let(
h = one_defined([h, height,l,length],"h,height,l,length",dflt=1) h = one_defined([h, height,l,length],"h,height,l,length",dflt=1),
)
!is_undef(texture)? _textured_linear_sweep(
region, h=h, caps=caps,
texture=texture, tex_size=tex_size,
counts=tex_reps, inset=tex_inset,
rot=tex_rot, tex_scale=tex_depth,
twist=twist, scale=scale, shift=shift,
style=style, samples=tex_samples,
anchor=anchor, spin=spin, orient=orient
) :
let(
caps = is_bool(caps) ? [caps,caps] : caps,
anchor = center==true? "origin" :
center == false? "original_base" :
default(anchor, "original_base"),
regions = region_parts(region), regions = region_parts(region),
slices = default(slices, max(1,ceil(abs(twist)/5))), vnf = !is_undef(texture)?
scale = is_num(scale)? [scale,scale] : point2d(scale), _textured_linear_sweep(
topmat = move(shift) * scale(scale) * rot(-twist), region, h=h, caps=caps,
trgns = [ texture=texture, tex_size=tex_size,
for (rgn = regions) [ counts=tex_reps, inset=tex_inset,
for (path = rgn) let( rot=tex_rot, tex_scale=tex_depth,
p = list_unwrap(path), twist=twist, scale=scale, shift=shift,
path = is_undef(maxseg)? p : [ style=style, samples=tex_samples)
for (seg = pair(p,true)) each : let(
let( steps = ceil(norm(seg.y - seg.x) / maxseg) ) caps = is_bool(caps) ? [caps,caps] : caps,
lerpn(seg.x, seg.y, steps, false) anchor = center==true? "origin" :
] center == false? "original_base" :
) apply(topmat, path) default(anchor, "original_base"),
] slices = default(slices, max(1,ceil(abs(twist)/5))),
], scale = is_num(scale)? [scale,scale] : point2d(scale),
vnf = vnf_join([ topmat = move(shift) * scale(scale) * rot(-twist),
for (rgn = regions) trgns = [
for (pathnum = idx(rgn)) let( for (rgn = regions) [
p = list_unwrap(rgn[pathnum]), for (path = rgn) let(
path = is_undef(maxseg)? p : [ p = list_unwrap(path),
for (seg=pair(p,true)) each path = is_undef(maxseg)? p : [
let(steps=ceil(norm(seg.y-seg.x)/maxseg)) for (seg = pair(p,true)) each
lerpn(seg.x, seg.y, steps, false) let( steps = ceil(norm(seg.y - seg.x) / maxseg) )
], lerpn(seg.x, seg.y, steps, false)
verts = [ ]
for (i=[0:1:slices]) let( ) apply(topmat, path)
u = i / slices, ]
scl = lerp([1,1], scale, u), ],
ang = lerp(0, -twist, u), vnf = vnf_join([
off = lerp([0,0,-h/2], point3d(shift,h/2), u), for (rgn = regions)
m = move(off) * scale(scl) * rot(ang) for (pathnum = idx(rgn)) let(
) apply(m, path3d(path)) p = list_unwrap(rgn[pathnum]),
] path = is_undef(maxseg)? p : [
) vnf_vertex_array(verts, caps=false, col_wrap=true, style=style), for (seg=pair(p,true)) each
if (caps[0]) for (rgn = regions) vnf_from_region(rgn, down(h/2), reverse=true), let(steps=ceil(norm(seg.y-seg.x)/maxseg))
if (caps[1]) for (rgn = trgns) vnf_from_region(rgn, up(h/2), reverse=false) lerpn(seg.x, seg.y, steps, false)
]), ],
verts = [
for (i=[0:1:slices]) let(
u = i / slices,
scl = lerp([1,1], scale, u),
ang = lerp(0, -twist, u),
off = lerp([0,0,-h/2], point3d(shift,h/2), u),
m = move(off) * scale(scl) * rot(ang)
) apply(m, path3d(path))
]
) vnf_vertex_array(verts, caps=false, col_wrap=true, style=style),
if (caps[0]) for (rgn = regions) vnf_from_region(rgn, down(h/2), reverse=true),
if (caps[1]) for (rgn = trgns) vnf_from_region(rgn, up(h/2), reverse=false)
])
)
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])
],
anchors = [ anchors = [
named_anchor("original_base", [0,0,-h/2], DOWN), named_anchor("original_base", [0,0,-h/2], DOWN),
named_anchor("original_top", [0,0,h/2], UP), named_anchor("original_top", [0,0,h/2], UP),
each ganchors
], ],
cp = default(cp, "centroid"), cp = default(cp, "centroid"),
geom = atype=="hull"? attach_geom(cp=cp, region=region, h=h, extent=true, shift=shift, scale=scale, twist=twist, anchors=anchors) : geom = atype=="hull"? attach_geom(cp=cp, region=region, h=h, extent=true, shift=shift, scale=scale, twist=twist, anchors=anchors) :
@@ -873,7 +941,7 @@ function linear_sweep(
) )
attach_geom(cp=[0,0,0], size=point3d(size,h), offset=point3d(midpt), shift=shift, scale=scale, twist=twist, anchors=anchors) : attach_geom(cp=[0,0,0], size=point3d(size,h), offset=point3d(midpt), shift=shift, scale=scale, twist=twist, anchors=anchors) :
assert(in_list(atype, ["hull","intersect","bbox"]), "\nAnchor type must be \"hull\", \"intersect\", or \"bbox\".") assert(in_list(atype, ["hull","intersect","bbox"]), "\nAnchor type must be \"hull\", \"intersect\", or \"bbox\".")
) reorient(anchor,spin,orient, geom=geom, p=vnf); ) _return_geom ? [vnf,geom] : reorient(anchor,spin,orient, geom=geom, p=vnf);
// Function&Module: rotate_sweep() // Function&Module: rotate_sweep()