Merge pull request #1395 from adrianVmariano/master

offset_sweep fixes
This commit is contained in:
Revar Desmera 2024-03-02 22:09:38 -08:00 committed by GitHub
commit 0c484417de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 103 additions and 80 deletions

View file

@ -1272,9 +1272,9 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
// Topics: Rounding, Offsets // Topics: Rounding, Offsets
// See Also: convex_offset_extrude(), rounded_prism(), bent_cutout_mask(), join_prism(), linear_sweep() // See Also: convex_offset_extrude(), rounded_prism(), bent_cutout_mask(), join_prism(), linear_sweep()
// Usage: most common module arguments. See Arguments list below for more. // Usage: most common module arguments. See Arguments list below for more.
// offset_sweep(path, [height|length|h|l|], [bottom], [top], [offset=], [convexity=],...) [ATTACHMENTS]; // offset_sweep(path, [height|length=|h=|l=], [bottom], [top], [offset=], [convexity=],...) [ATTACHMENTS];
// Usage: most common function arguments. See Arguments list below for more. // Usage: most common function arguments. See Arguments list below for more.
// vnf = offset_sweep(path, [height|h|l|length], [bottom], [top], [offset=], ...); // vnf = offset_sweep(path, [height|length=|h=|l=], [bottom], [top], [offset=], ...);
// Description: // Description:
// Takes a 2d path as input and extrudes it upwards and/or downward. Each layer in the extrusion is produced using `offset()` to expand or shrink the previous layer. When invoked as a function returns a VNF; when invoked as a module produces geometry. // Takes a 2d path as input and extrudes it upwards and/or downward. Each layer in the extrusion is produced using `offset()` to expand or shrink the previous layer. When invoked as a function returns a VNF; when invoked as a module produces geometry.
// Using the `top` and/or `bottom` arguments you can specify a sequence of offsets values, or you can use several built-in offset profiles that // Using the `top` and/or `bottom` arguments you can specify a sequence of offsets values, or you can use several built-in offset profiles that
@ -1309,7 +1309,7 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
// - mask: os_mask(mask, [out]). Create a profile from one of the [2d masking shapes](shapes2d.scad#5-2d-masking-shapes). The `out` parameter specifies that the mask should flare outward (like crown molding or baseboard). This is set false by default. // - mask: os_mask(mask, [out]). Create a profile from one of the [2d masking shapes](shapes2d.scad#5-2d-masking-shapes). The `out` parameter specifies that the mask should flare outward (like crown molding or baseboard). This is set false by default.
// . // .
// The general settings that you can use with all of the helper functions are mostly used to control how offset_sweep() calls the offset() function. // The general settings that you can use with all of the helper functions are mostly used to control how offset_sweep() calls the offset() function.
// - extra: Add an extra vertical step of the specified height, to be used for intersections or differences. This extra step will extend the resulting object beyond the height you specify. Default: 0 // - extra: Add an extra vertical step of the specified height, to be used for intersections or differences. This extra step will extend the resulting object beyond the height you specify. It is ignored by anchoring. Default: 0
// - check_valid: passed to offset(). Default: true // - check_valid: passed to offset(). Default: true
// - quality: passed to offset(). Default: 1 // - quality: passed to offset(). Default: 1
// - steps: Number of vertical steps to use for the profile. (Not used by os_profile). Default: 16 // - steps: Number of vertical steps to use for the profile. (Not used by os_profile). Default: 16
@ -1346,15 +1346,17 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
// //
// Arguments: // Arguments:
// path = 2d path (list of points) to extrude // path = 2d path (list of points) to extrude
// height / l / h = total height (including rounded portions, but not extra sections) of the output. Default: combined height of top and bottom end treatments. // height / length / l / h = total height (including rounded portions, but not extra sections) of the output. Default: combined height of top and bottom end treatments.
// bottom = rounding spec for the bottom end // bottom / bot = rounding spec for the bottom end
// top = rounding spec for the top end. // top = rounding spec for the top end.
// --- // ---
// ends = give a rounding spec that applies to both the top and bottom
// offset = default offset, `"round"` or `"delta"`. Default: `"round"` // offset = default offset, `"round"` or `"delta"`. Default: `"round"`
// steps = default step count. Default: 16 // steps = default step count. Default: 16
// quality = default quality. Default: 1 // quality = default quality. Default: 1
// check_valid = default check_valid. Default: true. // check_valid = default check_valid. Default: true.
// extra = default extra height. Default: 0 // extra = default extra height. Default: 0
// caps = if false do not create end faces. Can be a boolean vector to control ends independent. (function only) Default: true.
// cut = default cut value. // cut = default cut value.
// chamfer_width = default width value for chamfers. // chamfer_width = default width value for chamfers.
// chamfer_height = default height value for chamfers. // chamfer_height = default height value for chamfers.
@ -1362,9 +1364,9 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
// joint = default joint value for smooth roundover. // joint = default joint value for smooth roundover.
// k = default curvature parameter value for "smooth" roundover // k = default curvature parameter value for "smooth" roundover
// convexity = convexity setting for use with polyhedron. (module only) Default: 10 // convexity = convexity setting for use with polyhedron. (module only) Default: 10
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin" // anchor = Translate so anchor point is at the origin. Default: "base"
// spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0 // spin = Rotate this many degrees around Z axis after anchor. Default: 0
// orient = Vector to rotate top towards after spin (module only) // orient = Vector to rotate top towards after spin
// atype = Select "hull", "intersect", "surf_hull" or "surf_intersect" anchor types. Default: "hull" // atype = Select "hull", "intersect", "surf_hull" or "surf_intersect" anchor types. Default: "hull"
// cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" // cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid"
// Anchor Types: // Anchor Types:
@ -1372,6 +1374,10 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
// intersect = Anchors to the surface of the linear sweep of the path, ignoring any end roundings. // intersect = Anchors to the surface of the linear sweep of the path, ignoring any end roundings.
// surf_hull = Anchors to the convex hull of the offset_sweep shape, including end treatments. // surf_hull = Anchors to the convex hull of the offset_sweep shape, including end treatments.
// surf_intersect = Anchors to the surface of the offset_sweep shape, including any end treatments. // surf_intersect = Anchors to the surface of the offset_sweep shape, including any end treatments.
// Extra Anchors:
// "base" = Anchor to the base of the shape in its native position, ignoring any "extra"
// "top" = Anchor to the top of the shape in its native position, ignoring any "extra"
// "zcenter" = Center shape in the Z direction in the native XY position, ignoring any "extra"
// Example: Rounding a star shaped prism with postive radius values // Example: Rounding a star shaped prism with postive radius values
// star = star(5, r=22, ir=13); // star = star(5, r=22, ir=13);
// rounded_star = round_corners(star, cut=flatten(repeat([.5,0],5)), $fn=24); // rounded_star = round_corners(star, cut=flatten(repeat([.5,0],5)), $fn=24);
@ -1500,40 +1506,42 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
// This function does the actual work of repeatedly calling offset() and concatenating the resulting face and vertex lists to produce // This function does the actual work of repeatedly calling offset() and concatenating the resulting face and vertex lists to produce
// the inputs for the polyhedron module. // the inputs for the polyhedron module.
function _make_offset_polyhedron(path,offsets, offset_type, flip_faces, quality, check_valid, offsetind=0, function _make_offset_polyhedron(path,offsets, offset_type, flip_faces, quality, check_valid, cap=true,
vertexcount=0, vertices=[], faces=[] )= offsetind=0, vertexcount=0, vertices=[], faces=[] )=
offsetind==len(offsets)? ( offsetind==len(offsets)?
let( let(
bottom = count(len(path),vertexcount), bottom = count(len(path),vertexcount),
oriented_bottom = !flip_faces? bottom : reverse(bottom) oriented_bottom = !flip_faces? bottom : reverse(bottom)
) [vertices, concat(faces,[oriented_bottom])] )
) : ( [
let( vertices,
this_offset = offsetind==0? offsets[0][0] : offsets[offsetind][0] - offsets[offsetind-1][0], [each faces,
delta = offset_type=="delta" || offset_type=="chamfer" ? this_offset : undef, if (cap) oriented_bottom]
r = offset_type=="round"? this_offset : undef, ]
do_chamfer = offset_type == "chamfer" :
) let(
let( this_offset = offsetind==0? offsets[0][0] : offsets[offsetind][0] - offsets[offsetind-1][0],
vertices_faces = offset( delta = offset_type=="delta" || offset_type=="chamfer" ? this_offset : undef,
path, r=r, delta=delta, chamfer = do_chamfer, closed=true, r = offset_type=="round"? this_offset : undef,
check_valid=check_valid, quality=quality, do_chamfer = offset_type == "chamfer",
return_faces=true, vertices_faces = offset(
firstface_index=vertexcount, path, r=r, delta=delta, chamfer = do_chamfer, closed=true,
flip_faces=flip_faces check_valid=check_valid, quality=quality,
) return_faces=true,
) firstface_index=vertexcount,
_make_offset_polyhedron( flip_faces=flip_faces
vertices_faces[0], offsets, offset_type, )
flip_faces, quality, check_valid, )
offsetind+1, vertexcount+len(path), _make_offset_polyhedron(
vertices=concat( vertices_faces[0], offsets, offset_type,
vertices, flip_faces, quality, check_valid, cap,
path3d(vertices_faces[0],offsets[offsetind][1]) offsetind+1, vertexcount+len(path),
), vertices=concat(
faces=concat(faces, vertices_faces[1]) vertices,
) path3d(vertices_faces[0],offsets[offsetind][1])
); ),
faces=concat(faces, vertices_faces[1])
);
function _struct_valid(spec, func, name) = function _struct_valid(spec, func, name) =
@ -1543,13 +1551,15 @@ function _struct_valid(spec, func, name) =
function offset_sweep( function offset_sweep(
path, height, path, height,
bottom=[], top=[], bottom, top,
h, l, length, h, l, length,
ends,bot,
offset="round", r=0, steps=16, offset="round", r=0, steps=16,
quality=1, check_valid=true, quality=1, check_valid=true,
extra=0, extra=0, caps=true,
cut=undef, chamfer_width=undef, chamfer_height=undef, cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45 joint=undef, k=0.75, angle=45, anchor="base", orient=UP, spin=0,atype="hull", cp="centroid",
_return_height=false
) = ) =
let( let(
argspec = [ argspec = [
@ -1572,15 +1582,16 @@ function offset_sweep(
path = force_path(path) path = force_path(path)
) )
assert(is_path(path,2), "Input path must be a 2D path") assert(is_path(path,2), "Input path must be a 2D path")
assert(is_bool(caps) || is_bool_list(caps,2), "caps must be boolean or a list of two booleans")
let( let(
caps = is_bool(caps) ? [caps,caps] : caps,
clockwise = is_polygon_clockwise(path), clockwise = is_polygon_clockwise(path),
dummy1 = _struct_valid(top,"offset_sweep","top"), top_temp = one_defined([ends,top],"ends,top",dflt=[]),
dummy2 = _struct_valid(bottom,"offset_sweep","bottom"), bottom_temp = one_defined([ends,bottom,bot],"ends,bottom,bot",dflt=[]),
top = struct_set(argspec, top, grow=false), dummy1 = _struct_valid(top_temp,"offset_sweep","top"),
bottom = struct_set(argspec, bottom, grow=false), dummy2 = _struct_valid(bottom_temp,"offset_sweep","bottom"),
top = struct_set(argspec, top_temp, grow=false),
// This code does not work. It hits the error in _make_offset_polyhedron from offset being wrong bottom = struct_set(argspec, bottom_temp, grow=false),
// before this code executes. Had to move the test into _make_offset_polyhedron, which is ugly since it's in the loop
offsetsok = in_list(struct_val(top, "offset"),["round","delta","chamfer"]) offsetsok = in_list(struct_val(top, "offset"),["round","delta","chamfer"])
&& in_list(struct_val(bottom, "offset"),["round","delta","chamfer"]) && in_list(struct_val(bottom, "offset"),["round","delta","chamfer"])
) )
@ -1598,20 +1609,17 @@ function offset_sweep(
top_height = len(offsets_top)==0 ? 0 : abs(last(offsets_top)[1]) - struct_val(top,"extra"), top_height = len(offsets_top)==0 ? 0 : abs(last(offsets_top)[1]) - struct_val(top,"extra"),
height = one_defined([l,h,height,length], "l,h,height,length", dflt=u_add(bottom_height,top_height)), height = one_defined([l,h,height,length], "l,h,height,length", dflt=u_add(bottom_height,top_height)),
middle = height-bottom_height-top_height dummy1 = assert(is_finite(height) && height>0, "Height must be positive"),
) middle = height-bottom_height-top_height,
assert(height>0, "Height must be positive") dummy2= assert(middle>=0, str("Specified end treatments (bottom height = ",bottom_height,
assert(middle>=0, str("Specified end treatments (bottom height = ",bottom_height, " top_height = ",top_height,") are too large for extrusion height (",height,")")),
" top_height = ",top_height,") are too large for extrusion height (",height,")"
)
)
let(
initial_vertices_bot = path3d(path), initial_vertices_bot = path3d(path),
vertices_faces_bot = _make_offset_polyhedron( vertices_faces_bot = _make_offset_polyhedron(
path, offsets_bot, struct_val(bottom,"offset"), clockwise, path, offsets_bot, struct_val(bottom,"offset"), clockwise,
struct_val(bottom,"quality"), struct_val(bottom,"quality"),
struct_val(bottom,"check_valid"), struct_val(bottom,"check_valid"),
caps[0],
vertices=initial_vertices_bot vertices=initial_vertices_bot
), ),
@ -1622,6 +1630,7 @@ function offset_sweep(
struct_val(top,"offset"), !clockwise, struct_val(top,"offset"), !clockwise,
struct_val(top,"quality"), struct_val(top,"quality"),
struct_val(top,"check_valid"), struct_val(top,"check_valid"),
caps[1],
vertexcount=top_start_ind, vertexcount=top_start_ind,
vertices=initial_vertices_top vertices=initial_vertices_top
), ),
@ -1629,42 +1638,55 @@ function offset_sweep(
for(i=[0:len(path)-1]) let( for(i=[0:len(path)-1]) let(
oneface=[i, (i+1)%len(path), top_start_ind+(i+1)%len(path), top_start_ind+i] oneface=[i, (i+1)%len(path), top_start_ind+(i+1)%len(path), top_start_ind+i]
) !clockwise ? reverse(oneface) : oneface ) !clockwise ? reverse(oneface) : oneface
] ],
) vnf = [up(bottom_height-height/2, concat(vertices_faces_bot[0],vertices_faces_top[0])), // Vertices
[up(bottom_height, concat(vertices_faces_bot[0],vertices_faces_top[0])), // Vertices concat(vertices_faces_bot[1], vertices_faces_top[1], middle_faces)], // Faces
concat(vertices_faces_bot[1], vertices_faces_top[1], middle_faces)]; // Faces anchors = [
named_anchor("zcenter", [0,0,0], UP),
named_anchor("base", [0,0,-height/2], UP),
named_anchor("top", [0,0,height/2], UP)
],
final_vnf = in_list(atype,["hull","intersect"])
? reorient(anchor,spin,orient, path=path, h=height, extent=atype=="hull", cp=cp, p=vnf, anchors=anchors)
: reorient(anchor,spin,orient, vnf=vnf, p=vnf, extent=atype=="surf_hull", cp=cp, anchors=anchors)
) _return_height ? [final_vnf,height] : final_vnf;
module offset_sweep(path, height, module offset_sweep(path, height,
bottom=[], top=[], bottom, top,
h, l, h, l, length, ends, bot,
offset="round", r=0, steps=16, offset="round", r=0, steps=16,
quality=1, check_valid=true, quality=1, check_valid=true,
extra=0, extra=0,
cut=undef, chamfer_width=undef, chamfer_height=undef, cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45, joint=undef, k=0.75, angle=45,
convexity=10,anchor="origin",cp="centroid", convexity=10,anchor="base",cp="centroid",
spin=0, orient=UP, atype="hull") spin=0, orient=UP, atype="hull")
{ {
assert(in_list(atype, ["intersect","hull","surf_hull","surf_intersect"]), "Anchor type must be \"hull\" or \"intersect\""); assert(in_list(atype, ["intersect","hull","surf_hull","surf_intersect"]), "Anchor type must be \"hull\" or \"intersect\"");
vnf = offset_sweep(path=path, height=height, h=h, l=l, top=top, bottom=bottom, offset=offset, r=r, steps=steps, vnf_h = offset_sweep(path=path, height=height, h=h, l=l, length=length, bot=bot, top=top, bottom=bottom, ends=ends,
quality=quality, check_valid=check_valid, extra=extra, cut=cut, chamfer_width=chamfer_width, offset=offset, r=r, steps=steps,
chamfer_height=chamfer_height, joint=joint, k=k, angle=angle); quality=quality, check_valid=check_valid, extra=extra, cut=cut, chamfer_width=chamfer_width,
chamfer_height=chamfer_height, joint=joint, k=k, angle=angle, _return_height=true);
if (in_list(atype,["hull","intersect"])){ vnf = vnf_h[0];
h=first_defined([h,l,height]); height = vnf_h[1];
attachable(anchor,spin,orient,region=force_region(path),h=h,extent=atype=="hull",cp=cp){ anchors = [
down(h/2)polyhedron(vnf[0],vnf[1],convexity=convexity); named_anchor("zcenter", [0,0,0], UP),
named_anchor("base", [0,0,-height/2], UP),
named_anchor("top", [0,0,height/2], UP)
];
if (in_list(atype,["hull","intersect"]))
attachable(anchor,spin,orient,region=force_region(path),h=height,cp=cp,anchors=anchors,extent=atype=="hull"){
down(height/2)polyhedron(vnf[0],vnf[1],convexity=convexity);
children(); children();
} }
}
else else
vnf_polyhedron(vnf,convexity=convexity,anchor=anchor, spin=spin, orient=orient, atype=atype=="surf_hull"?"hull":"intersect", cp=cp) attachable(anchor,spin.orient,vnf=vnf, cp=cp,anchors=anchors, extent = atype=="surf_hull"){
vnf_polyhedron(vnf,convexity=convexity);
children(); children();
}
} }
function os_circle(r,cut,extra,check_valid, quality,steps, offset) = function os_circle(r,cut,extra,check_valid, quality,steps, offset) =
assert(num_defined([r,cut])==1, "Must define exactly one of `r` and `cut`") assert(num_defined([r,cut])==1, "Must define exactly one of `r` and `cut`")
_remove_undefined_vals([ _remove_undefined_vals([

View file

@ -1258,6 +1258,7 @@ module will ignore its children.
```openscad-3D ```openscad-3D
include <BOSL2/std.scad> include <BOSL2/std.scad>
$fn=32;
module cutcube(anchor=CENTER,spin=0,orient=UP) module cutcube(anchor=CENTER,spin=0,orient=UP)
{ {
tag_scope(){ tag_scope(){