Merge pull request #1645 from adrianVmariano/master

region support for offset_sweep
This commit is contained in:
adrianVmariano 2025-04-22 20:34:59 -04:00 committed by GitHub
commit 9f1991730a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 136 additions and 40 deletions

View file

@ -1412,11 +1412,14 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
// "chamfer". Use the "chamfer" style offset only in cases where the number of steps is small or just one (such as when using
// the `os_chamfer` profile type).
// .
// The module form only can support a region as input. You can provide different profiles for the cutouts in a region using the `bottom_hole`, `top_hole`
// or `ends_hole` parameters.
// .
// This module offers four anchor types. The default is "hull" in which VNF anchors are placed on the VNF of the **unrounded** object. You
// can also use "intersect" to get the intersection anchors to the unrounded object. If you prefer anchors that respect the rounding
// then use "surf_hull" or "intersect_hull".
// Arguments:
// path = 2d path (list of points) to extrude
// path = 2d path (list of points) to extrude or a region for the module form
// 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 / bot = rounding spec for the bottom end
// top = rounding spec for the top end.
@ -1434,6 +1437,9 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
// angle = default angle for chamfers. Default: 45
// joint = default joint value for smooth roundover.
// k = default curvature parameter value for "smooth" roundover
// ends_hole = (module only) rounding spec that applies to top and bottom of holes in a region
// bot_hole / bottom_hole = (module only) rounding spec for bottom end of holes in a region
// top_hole = (module only) rounding spec for top end of holes in a region
// convexity = convexity setting for use with polyhedron. (module only) Default: 10
// anchor = Translate so anchor point is at the origin. Default: "base"
// spin = Rotate this many degrees around Z axis after anchor. Default: 0
@ -1573,6 +1579,84 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
// star = star(5, r=220, ir=130);
// rounded_star = round_corners(star, cut=flatten(repeat([5,0],5)), $fn=24);
// offset_sweep(rounded_star, height=100, top=os_mask(ogee), bottom=os_mask(ogee,out=true));
// Example(3D,NoAxes): Applying to a region, with different profiles for the outside in inside curves.
// $fn = 32;
// rgn = difference(
// [
// rect(50, rounding=5),
// move([15,15], circle(d=10)),
// move([-15,-15], circle(d=10)),
// move([0,25], rect([10,7],anchor=BACK,
// rounding=[-2,-2,2,2])),
// zrot(55, square([4, 100], center=true)),
// ellipse([12,4])
// ]
// );
// offset_sweep(rgn, height=12, steps=6, ends_hole=os_chamfer(width=2),
// ends=os_circle(r=1.7));
module _offset_sweep_region(region, height,
bottom, top,
h, l, length, ends, bot, top_hole, bot_hole, bottom_hole, ends_hole,
offset="round", r=0, steps=16,
quality=1, check_valid=true,
extra=0,
cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45,
convexity=10,anchor="base",cp="centroid",
spin=0, orient=UP, atype="hull")
{
connected_reg = region_parts(region);
vnf_h_list = [for(reg=connected_reg)
offset_sweep(path=reg[0], height=height, h=h, l=l, length=length, bot=bot, top=top, bottom=bottom, ends=ends,
offset=offset, r=r, steps=steps,
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)];
vnf_list = column(vnf_h_list,0);
height = vnf_h_list[0][1];
holes = [for(reg=connected_reg, i=[1:1:len(reg)-1]) reg[i]];
anchors = [
named_anchor("zcenter", [0,0,0], UP),
named_anchor("base", [0,0,-height/2], UP),
named_anchor("top", [0,0,height/2], UP)
];
bottom_hole=first_defined([bottom_hole, bot_hole, ends_hole, bottom, ends]);
top_hole = first_defined([top_hole,ends_hole,top,ends]);
if (in_list(atype,["hull","intersect"]))
attachable(anchor,spin,orient,region=region,h=height,cp=cp,anchors=anchors,extent=atype=="hull"){
down(height/2)
difference(){
for(vnf=vnf_list)
polyhedron(vnf[0],vnf[1],convexity=convexity);
for(path=holes)
offset_sweep(path=path, height=height, h=h, l=l, length=length, bot=bot, top=top_hole, bottom=bottom_hole,
offset=offset, r=r, steps=steps,
quality=quality, check_valid=check_valid, extra=extra+0.1, cut=cut, chamfer_width=chamfer_width,
chamfer_height=chamfer_height, joint=joint, k=k, angle=angle, _flipdir=true,convexity=convexity);
}
children();
}
else {
allvnf=vnf_join(vnf_list);
attachable(anchor,spin.orient,vnf=allvnf, cp=cp,anchors=anchors, extent = atype=="surf_hull"){
difference(){
for(vnf=vnf_list)
vnf_polyhedron(vnf,convexity=convexity);
for(path=holes)
offset_sweep(path=path, height=height, h=h, l=l, length=length, bot=bot, top=top_hole, bottom=bottom_hole,
offset=offset, r=r, steps=steps,
quality=quality, check_valid=check_valid, extra=extra+0.1, cut=cut, chamfer_width=chamfer_width,
chamfer_height=chamfer_height, joint=joint, k=k, angle=angle, _flipdir=true,convexity=convexity);
}
children();
}
}
}
// This function does the actual work of repeatedly calling offset() and concatenating the resulting face and vertex lists to produce
@ -1630,7 +1714,7 @@ function offset_sweep(
extra=0, caps=true,
cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45, anchor="base", orient=UP, spin=0,atype="hull", cp="centroid",
_return_height=false
_return_height=false, _flipdir=false
) =
let(
argspec = [
@ -1668,8 +1752,9 @@ function offset_sweep(
)
assert(offsetsok,"Offsets must be one of \"round\", \"delta\", or \"chamfer\"")
let(
offsets_bot = _rounding_offsets(bottom, -1),
offsets_top = _rounding_offsets(top, 1),
do_flip = _flipdir ? function(x) xflip(x) : function(x) x ,
offsets_bot = do_flip(_rounding_offsets(bottom, -1)),
offsets_top = do_flip(_rounding_offsets(top, 1)),
dummy = (struct_val(top,"offset")=="chamfer" && len(offsets_top)>5)
|| (struct_val(bottom,"offset")=="chamfer" && len(offsets_bot)>5)
? echo("WARNING: You have selected offset=\"chamfer\", which leads to exponential growth in the vertex count and requested more than 5 layers. This can be slow or run out of recursion depth.")
@ -1722,39 +1807,48 @@ function offset_sweep(
: 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,
bottom, top,
h, l, length, ends, bot,
offset="round", r=0, steps=16,
quality=1, check_valid=true,
extra=0,
extra=0, top_hole, bot_hole, bottom_hole, ends_hole,
cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45,
convexity=10,anchor="base",cp="centroid",
spin=0, orient=UP, atype="hull")
spin=0, orient=UP, atype="hull", _flipdir)
{
assert(in_list(atype, ["intersect","hull","surf_hull","surf_intersect"]), "Anchor type must be \"hull\" or \"intersect\"");
vnf_h = offset_sweep(path=path, height=height, h=h, l=l, length=length, bot=bot, top=top, bottom=bottom, ends=ends,
offset=offset, r=r, steps=steps,
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);
vnf = vnf_h[0];
height = vnf_h[1];
anchors = [
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();
}
else
attachable(anchor,spin.orient,vnf=vnf, cp=cp,anchors=anchors, extent = atype=="surf_hull"){
vnf_polyhedron(vnf,convexity=convexity);
children();
}
if (is_region(path) && len(path)>1)
_offset_sweep_region(region=path, height=height, bottom=bottom, top=top, h=h, l=l, length=length, ends=ends, bot=bot,
offset=offset, r=r, steps=steps, quality=quality, check_valid=check_valid, extra=extra,
cut=cut, chamfer_width=chamfer_width, chamfer_height=chamfer_height, joint=joint, k=k, angle=angle,
bot_hole=bot_hole,top_hole=top_hole,bottom_hole=bottom_hole,ends_hole=ends_hole,
convexity=convexity, anchor=anchor, cp=cp, spin=spin, orient=orient, atype=atype) children();
else {
vnf_h = offset_sweep(path=path, height=height, h=h, l=l, length=length, bot=bot, top=top, bottom=bottom, ends=ends,
offset=offset, r=r, steps=steps,
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, _flipdir=_flipdir);
vnf = vnf_h[0];
height = vnf_h[1];
anchors = [
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();
}
else
attachable(anchor,spin.orient,vnf=vnf, cp=cp,anchors=anchors, extent = atype=="surf_hull"){
vnf_polyhedron(vnf,convexity=convexity);
children();
}
}
}

View file

@ -1334,10 +1334,9 @@ function textured_tile(
) =
assert(is_undef(tex_reps) || is_int(tex_reps) || (all_integer(tex_reps) && len(tex_reps)==2), "tex_reps must be an integer or list of two integers")
assert(is_undef(tex_size) || is_vector(tex_size,2) || is_finite(tex_size))
assert(num_defined([tex_size, tex_reps])<2, "Cannot give both tex_size and tex_reps")
assert(num_defined([tex_size, tex_reps])==1, "Must give exactly one of tex_size and tex_reps")
assert(is_undef(size) || is_num(size) || is_vector(size,2) || is_vector(size,3), "size must be a 2-vector or 3-vector")
assert(is_undef(size) || num_defined([ysize,h, height, thickness, w1,w2,ang])==0, "Cannot combine size with any other dimensional specifications")
let(
inset = is_num(tex_inset)? tex_inset : tex_inset? 1 : 0,
default_thick = inset>0 ? 0.1+abs(tex_depth)*inset : 0.1,
@ -1362,7 +1361,7 @@ function textured_tile(
texture = _get_texture(texture, tex_rot),
tex_reps = is_def(tex_reps) ? force_list(tex_reps,2)
: let(tex_size=is_undef(tex_size)? [5,5] : force_list(tex_size,2))
: let(tex_size=force_list(tex_size,2))
[round(size.x/tex_size.x), round(size.y/tex_size.y)],
extra = is_undef(extra)? tex_reps == [1,1] ? [0,0] : [1,1]
: force_list(tex_extra,2),

View file

@ -903,9 +903,11 @@ function linear_sweep(
// .
// If you want to place just one or a few copies of a texture onto an object rather than texturing the entire object you can do that by using
// and angle smaller than 360. However, if you want to control the aspect ratio of the resulting texture you will have to carefully calculate the proper
// angle to use. To simplify this process you can use `pixel_aspect` or `tex_aspect`. You can set `tex_aspect` for any type of tile and it specifies
// angle to use to ensure that the arc length in the horizontal direction is the proper length compared to the arc length in the vertical direction.
// To simplify this process you can use `pixel_aspect` or `tex_aspect`. You can set `tex_aspect` for any type of tile and it specifies
// the desired aspect ratio (width/height) for the tiles. You must specify `tex_reps` in order to use this feature. For heightfields you can instead provide
// a pixel aspect ratio, which is suited to the case where your texture is a non-square image that you want to place on a curved object.
// a pixel aspect ratio, which is suited to the case where your texture is a non-square image that you want to place on a curved object. For a simple cylinder
// it is obvious what the horizontal arc length is; for other objects this is computed based on the average radius of the longest path in `shape`.
// Arguments:
// shape = The polygon or [region](regions.scad) to sweep around the Z axis.
// angle = If given, specifies the number of degrees to sweep the region around the Z axis, counterclockwise from the X+ axis. Default: 360 (full rotation)
@ -1180,7 +1182,7 @@ function rotate_sweep(
style="min_edge", cp="centroid",
atype="hull", anchor="origin",
spin=0, orient=UP, start=0,
_tex_inhibit_y_slicing=false
_tex_inhibit_y_slicing
) =
assert(num_defined([tex_reps,tex_counts])<2, "In rotate_sweep() the 'tex_counts' parameters has been replaced by 'tex_reps'. You cannot give both.")
assert(num_defined([tex_scale,tex_depth])<2, "In linear_sweep() the 'tex_scale' parameter has been replaced by 'tex_depth'. You cannot give both.")
@ -4496,7 +4498,7 @@ function _tile_edge_path_list(vnf, axis, maxopen=1) =
/// Arguments:
/// shape = The path or region to sweep/extrude.
/// texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to the revolution surface. See {{texture()}} for what named textures are supported.
/// tex_size = An optional 2D target size for the textures. Actual texture sizes are scaled somewhat to evenly fit the available surface. Default: `[5,5]`
/// tex_size = An optional 2D target size for the textures. Actual texture sizes are scaled somewhat to evenly fit the available surface.
/// tex_scale = Scaling multiplier for the texture depth.
/// ---
/// inset = If numeric, lowers the texture into the surface by that amount, before the tex_scale multiplier is applied. If `true`, insets by exactly `1`. Default: `false`
@ -4520,7 +4522,7 @@ function _textured_revolution(
shape, texture, tex_size, tex_scale=1,
inset=false, rot=false, shift=[0,0],
taper, closed=true, angle=360,
inhibit_y_slicing=false,tex_aspect, pixel_aspect,
inhibit_y_slicing,tex_aspect, pixel_aspect,
counts, samples, start=0,tex_extra,
style="min_edge", atype="intersect",
anchor=CENTER, spin=0, orient=UP
@ -4539,6 +4541,7 @@ function _textured_revolution(
assert(num_defined([tex_aspect, pixel_aspect])<=1, "Cannot give both tex_aspect and pixel_aspect")
//assert(num_defined([tex_aspect, pixel_aspect])==0 || is_undef(angle), "Cannot give tex_aspect or pixel_aspect if you give angle")
let(
inhibit_y_slicing = default(inhibit_y_slicing, is_path(shape) && len(shape)==2 ? true : false),
regions = !is_path(shape,2)? region_parts(shape)
: closed? region_parts([shape])
: let(
@ -4859,7 +4862,7 @@ function _textured_point_array(points, texture, tex_reps, tex_size, tex_samples,
col_wrap=false, tex_depth=1, row_wrap=false, caps, cap1, cap2, reverse=false, style="min_edge", tex_extra, tex_skip, sidecaps,sidecap1,sidecap2,normals) =
assert(tex_reps==undef || is_int(tex_reps) || (all_integer(tex_reps) && len(tex_reps)==2), "tex_reps must be an integer or list of two integers")
assert(tex_size==undef || is_num(tex_size) || is_vector(tex_size,2), "tex_size must be a scalar or 2-vector")
assert(num_defined([tex_size, tex_reps])<2, "Cannot give both tex_size and tex_reps")
assert(num_defined([tex_size, tex_reps])==1, "Must give exactly one of tex_size and tex_reps")
assert(in_list(style,["default","alt","quincunx", "convex","concave", "min_edge","min_area","flip1","flip2"]))
assert(is_matrix(points[0], n=3),"Point array has the wrong shape or points are not 3d")
assert(is_consistent(points), "Non-rectangular or invalid point array")
@ -4875,7 +4878,7 @@ function _textured_point_array(points, texture, tex_reps, tex_size, tex_samples,
ptsize=[len(points[0]), len(points)],
tex_reps = is_def(tex_reps) ? force_list(tex_reps,2)
: let(
tex_size = is_undef(tex_size) ? [5,5] : force_list(tex_size,2),
tex_size = force_list(tex_size,2),
xsize = norm(points[0][0]-points[0][1])*(ptsize.x+(col_wrap?1:0)),
ysize = norm(points[0][0]-points[1][0])*(ptsize.y+(row_wrap?1:0))
)

View file

@ -86,8 +86,8 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// triangulate = If true, triangulates endcaps to resolve possible CGAL issues. This can be an expensive operation if the endcaps are complex. Default: false
// convexity = (module) Max number of times a line could intersect a wall of the shape.
// texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to vertical surfaces. See {{texture()}} for what named textures are supported.
// tex_size = An optional 2D target size (scalar or 2-vector) for the textures at `points[0][0]`. This size is approximate; the actual texture sizes are scaled as needed for whole tiles to fit the available surface. Default: `[5,5]`
// tex_reps = If given instead of tex_size, an integer scalar or 2-vector giving the number of texture tile repetitions in the horizontal and vertical directions.
// tex_size = An optional 2D target size for the textures at `points[0][0]`. Actual texture sizes are scaled somewhat to evenly fit the available surface.
// tex_reps = If given instead of tex_size, a 2-vector giving the number of texture tile repetitions in the horizontal and vertical directions.
// tex_inset = If numeric, lowers the texture into the surface by the specified proportion, e.g. 0.5 would lower it half way into the surface. If `true`, insets by exactly its full depth. Default: `false`
// tex_rot = Rotate texture by specified angle, which must be a multiple of 90 degrees. Default: 0
// tex_depth = Specify texture depth; if negative, invert the texture. Default: 1.
@ -282,7 +282,7 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// z)
// ];
// vnf_polyhedron(vnf_vertex_array(polystack, col_wrap=true, caps=true,
// texture="dots", tex_samples=1));
// texture="dots", tex_samples=1, tex_size=5));
// Example(3D,Med,NoAxes,VPR=[0,0,0],VPD=126.00,VPT=[-0.35,-0.54,4.09]): This point array defines a square region but with a non-uniform grid.
// pts = [for(x=[-1:.1:1])
// [for(y=[-1:.1:1])