Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Richard Milewski
2025-05-13 18:15:00 -07:00
8 changed files with 132 additions and 63 deletions

View File

@@ -767,6 +767,7 @@ function _make_anchor_legal(anchor,geom) =
// This module differs from {{position()}} and {{align()}} in that it rotates the children to // This module differs from {{position()}} and {{align()}} in that it rotates the children to
// the anchor direction, which generally means it places the children on the surface of a parent. // the anchor direction, which generally means it places the children on the surface of a parent.
// There are two modes of operation, parent anchor (single argument) and parent-child anchor (double argument). // There are two modes of operation, parent anchor (single argument) and parent-child anchor (double argument).
// In most cases you should use the parent-child (double argument) version of `attach()`.
// . // .
// The parent-child anchor (double argument) version is usually easier to use, and it is more powerful because it supports // The parent-child anchor (double argument) version is usually easier to use, and it is more powerful because it supports
// alignment. You provide an anchor on the parent (`parent`) and an anchor on the child (`child`). // alignment. You provide an anchor on the parent (`parent`) and an anchor on the child (`child`).
@@ -810,17 +811,6 @@ function _make_anchor_legal(anchor,geom) =
// ignored** with the **double argument** version of `attach()`. As noted above, you can give `spin=` to the // ignored** with the **double argument** version of `attach()`. As noted above, you can give `spin=` to the
// child but using the `spin=` parameter to `attach()` is more likely to be useful. // child but using the `spin=` parameter to `attach()` is more likely to be useful.
// . // .
// For the single parameter version of `attach()` you give only the `parent` anchor. The `align` direction
// is not permitted. In this case the child is placed at the specified parent anchor point
// and rotated to the anchor direction. For example, `attach(TOP) cuboid(2);` will place a small
// cube **with its center** located at the TOP anchor of the parent, so just half the cube will project
// from the parent. If you want the cube sitting on the parent you need to anchor the cube to its bottom:
// `attach(TOP) cuboid(2,anchor=BOT);`.
// .
// The **single argument** version of `attach()` **respects `anchor=` and `orient=` given to the child.**
// These options will probably be necessary, in fact, to get the child correctly positioned. Note that
// giving `spin=` to `attach()` in this case is the same as applying `zrot()` to the child.
// .
// You can overlap attached children into the parent by giving the `$overlap` value // You can overlap attached children into the parent by giving the `$overlap` value
// which is 0 by default, or by the `overlap=` argument. This is to prevent OpenSCAD // which is 0 by default, or by the `overlap=` argument. This is to prevent OpenSCAD
// from making non-manifold objects. You can define `$overlap=` as an argument in a parent // from making non-manifold objects. You can define `$overlap=` as an argument in a parent
@@ -833,6 +823,17 @@ function _make_anchor_legal(anchor,geom) =
// the parent. For an inside child this is equivalent to giving a positive overlap and negative inset value. // the parent. For an inside child this is equivalent to giving a positive overlap and negative inset value.
// For a child with `inside=false` it is equivalent to a negative overlap and negative inset. // For a child with `inside=false` it is equivalent to a negative overlap and negative inset.
// . // .
// The single parameter version of `attach()` is rarely needed; to use it, you give only the `parent` anchor. The `align` direction
// is not permitted. In this case the child is placed at the specified parent anchor point
// and rotated to the anchor direction. For example, `attach(TOP) cuboid(2);` will place a small
// cube **with its center** located at the TOP anchor of the parent, so just half the cube will project
// from the parent. If you want the cube sitting on the parent you need to anchor the cube to its bottom:
// `attach(TOP) cuboid(2,anchor=BOT);`.
// .
// The **single argument** version of `attach()` **respects `anchor=` and `orient=` given to the child.**
// These options will probably be necessary, in fact, to get the child correctly positioned. Note that
// giving `spin=` to `attach()` in this case is the same as applying `zrot()` to the child.
// .
// For a step-by-step explanation of // For a step-by-step explanation of
// attachments, see the [Attachments Tutorial](Tutorial-Attachments). // attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments: // Arguments:

View File

@@ -1267,7 +1267,6 @@ function bezier_vnf(patches=[], splinesteps=16, style="default") =
: assert(false,"\nInvalid patch list.") : assert(false,"\nInvalid patch list.")
] ]
); );
// Function: bezier_vnf_degenerate_patch() // Function: bezier_vnf_degenerate_patch()
@@ -1283,7 +1282,8 @@ function bezier_vnf(patches=[], splinesteps=16, style="default") =
// equal. If the resulting patch has no faces then returns an empty VNF. Note that due to the degeneracy, // equal. If the resulting patch has no faces then returns an empty VNF. Note that due to the degeneracy,
// the shape of the surface can be triangular even though the underlying patch is a rectangle. // the shape of the surface can be triangular even though the underlying patch is a rectangle.
// If you specify return_edges then the return is a list whose first element is the VNF and whose second // If you specify return_edges then the return is a list whose first element is the VNF and whose second
// element lists the edges in the order [left, right, top, bottom], where each list is a list of the actual // element lists the edges in the order [left (index zero of rows), right (last index of rows), top (first row), bottom (last row)],
// where each list is a list of the actual
// point values, but possibly only a single point if that edge is degenerate. // point values, but possibly only a single point if that edge is degenerate.
// The method checks for various types of degeneracy and uses a triangular or partly triangular array of sample points. // The method checks for various types of degeneracy and uses a triangular or partly triangular array of sample points.
// See examples below for the types of degeneracy detected and how the patch is sampled for those cases. // See examples below for the types of degeneracy detected and how the patch is sampled for those cases.
@@ -1292,7 +1292,7 @@ function bezier_vnf(patches=[], splinesteps=16, style="default") =
// patch = Patch to process // patch = Patch to process
// splinesteps = Number of segments to produce on each side. Default: 16 // splinesteps = Number of segments to produce on each side. Default: 16
// reverse = reverse direction of faces. Default: false // reverse = reverse direction of faces. Default: false
// return_edges = if true return the points on the four edges: [left, right, top, bottom]. Default: false // return_edges = if true return the points on the four edges of the array: [left (index zero of rows), right (last index of rows) , top (first row), bottom (last row)]. Default: false
// Example(3D,NoAxes): This quartic patch is degenerate at one corner, where a row of control points are equal. Processing this degenerate patch normally produces excess triangles near the degenerate point. // Example(3D,NoAxes): This quartic patch is degenerate at one corner, where a row of control points are equal. Processing this degenerate patch normally produces excess triangles near the degenerate point.
// splinesteps=8; // splinesteps=8;
// patch=[ // patch=[

View File

@@ -17,10 +17,10 @@
// Section: Utility Functions // Section: Utility Functions
// Definitions: // Definitions:
// Point|Points = A numeric vector of length 2 or 3 that represents either a 2D or 3D vertex. // Point|Points = A list of numbers, also called a vector. Usually has length 2 or 3 to represent points in the place on points in space.
// Pointlist|Pointlists|Point List|Point Lists = An unordered list of points. // Pointlist|Pointlists|Point List|Point Lists = An unordered list of {{points}}.
// Path|Paths = A list of two or more 2D {{point}} coordinates that specify a route on the XY plane. // Path|Paths = An ordered list of two or more {{points}} specifying a path through space. Usually points are 2D.
// Polygon|Polygons = A {{path}} where the first and last {{point}} coordinates are considered to be connected. // Polygon|Polygons = A {{path}}, usually 2D, that describes a polygon by asuming that the first and last point are connected.
// Function: is_path() // Function: is_path()
// Synopsis: Returns True if 'list' is a {{path}}. // Synopsis: Returns True if 'list' is a {{path}}.

View File

@@ -62,9 +62,9 @@
// ]; // ];
// region(rgn); // region(rgn);
// //
// Definitions:
// Region|Regions = A list of zero or more non-intersecting {{polygons}}, representing possibly disjointed shape perimeters with enclosed holes.
// Definitions:
// Region|Regions = A list of one or more non-intersecting {{polygons}} representing a union of one or more disconnected polygons that may have internal holes.
// Function: is_region() // Function: is_region()
// Synopsis: Returns true if the input appears to be a {{region}}. // Synopsis: Returns true if the input appears to be a {{region}}.

View File

@@ -7,11 +7,9 @@
// two prisms together with a rounded fillet at the joint. // two prisms together with a rounded fillet at the joint.
// Includes: // Includes:
// include <BOSL2/std.scad> // include <BOSL2/std.scad>
// include <BOSL2/rounding.scad>
// FileGroup: Advanced Modeling // FileGroup: Advanced Modeling
// FileSummary: Round path corners, rounded prisms, rounded cutouts in tubes, filleted prism joints // FileSummary: Round path corners, rounded prisms, rounded cutouts in tubes, filleted prism joints
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
include <structs.scad>
// Section: Types of Roundovers // Section: Types of Roundovers
// The functions and modules in this file support two different types of roundovers and some different mechanisms for specifying // The functions and modules in this file support two different types of roundovers and some different mechanisms for specifying

123
skin.scad
View File

@@ -1731,7 +1731,15 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals
// You can also apply scaling to the profile along the path. You can give a list of scalar scale factors or a list of 2-vector scale. // You can also apply scaling to the profile along the path. You can give a list of scalar scale factors or a list of 2-vector scale.
// In the latter scale the x and y scales of the profile are scaled separately before the profile is placed onto the path. For non-closed // In the latter scale the x and y scales of the profile are scaled separately before the profile is placed onto the path. For non-closed
// paths you can also give a single scale value or a 2-vector, which is treated as the final scale. The intermediate sections // paths you can also give a single scale value or a 2-vector, which is treated as the final scale. The intermediate sections
// are then scaled by linear interpolation either relative to length (if scale_by_length is true) or by point count otherwise. // are then scaled by linear interpolation either relative to length (if scale_by_length is true) or by point count otherwise.
// .
// The `caps` parameter controls what happens at the ends of the polyhedron. If `closed=true` the shape links to itself and has no
// ends, but when `closed` is false, the two ends are, by default capped with flat faces. If you set `caps=false` then the ends
// receive no faces and the resulting non-manifold polyhedron has exposed edges. You can also set caps to a number, which adds a
// rounded cap with the specified radius, or you can set caps to an {{offset_sweep()}} end treatment, and the specified sweep will
// be attached as a cap. Note that you are **adding** a rounded cap, not rounding the specified shape as is common for many other
// library modules. The rounded cap is attached to the end face and may not blend neatly with the swept shape unless the sides of
// the swept shape are perpendicular to the end cap.
// . // .
// You can use set `transforms` to true to return a list of transformation matrices instead of the swept shape. In this case, you can // You can use set `transforms` to true to return a list of transformation matrices instead of the swept shape. In this case, you can
// often omit shape entirely. The exception is when `closed=true` and you are using the "incremental" method. In this case, `path_sweep` // often omit shape entirely. The exception is when `closed=true` and you are using the "incremental" method. In this case, `path_sweep`
@@ -1764,7 +1772,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals
// uniform = if set to false then compute tangents using the uniform=false argument, which may give better results when your path is non-uniformly sampled. This argument is passed to {{path_tangents()}}. Default: true // uniform = if set to false then compute tangents using the uniform=false argument, which may give better results when your path is non-uniformly sampled. This argument is passed to {{path_tangents()}}. Default: true
// tangent = a list of tangent vectors in case you need more accuracy (particularly at the end points of your curve) // tangent = a list of tangent vectors in case you need more accuracy (particularly at the end points of your curve)
// relaxed = set to true with the "manual" method to relax the orthogonality requirement of cross sections to the path tangent. Default: false // relaxed = set to true with the "manual" method to relax the orthogonality requirement of cross sections to the path tangent. Default: false
// caps = Can be a boolean or vector of two booleans. Set to false to disable caps at the two ends. Default: true // caps = if closed is false, set caps to false to leave the ends open. Other values are true to create a flat cap, a number a rounded cap, or an {{offset_sweep()}} end treatment to create the specified offset sweep. Can be a single value or pair of values to control the caps independently at each end. Default: true
// style = vnf_vertex_array style. Default: "min_edge" // style = vnf_vertex_array style. Default: "min_edge"
// profiles = if true then display all the cross section profiles instead of the solid shape. Can help debug a sweep. (module only) Default: false // profiles = if true then display all the cross section profiles instead of the solid shape. Can help debug a sweep. (module only) Default: false
// width = the width of lines used for profile display. (module only) Default: 1 // width = the width of lines used for profile display. (module only) Default: 1
@@ -2101,6 +2109,15 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals
// closed=true, twist=360*2/5,symmetry=5, // closed=true, twist=360*2/5,symmetry=5,
// texture="bricks_vnf",tex_reps=[10,40], // texture="bricks_vnf",tex_reps=[10,40],
// tex_depth=.1); // tex_depth=.1);
// Example(NoScales): Applying rounded end caps to a sweep
// $fs=1;$fa=1;
// path_sweep(circle(r=5), arc(r=15, angle=[0,230]),caps=2.5);
// Example(NoScales): Using a small `$fn` creates a chamfer on the endcap
// $fs=1;$fa=1;
// path_sweep(circle(r=5), arc(r=15, angle=[0,230]),caps=1, $fn=4);
// Example(NoScales): One flat endcap and one rounding with a negative radius
// $fs=1;$fa=1;
// path_sweep(circle(r=5), arc(r=15, angle=[180,330]),caps=[true, -3]);
module path_sweep(shape, path, method="incremental", normal, closed, twist=0, twist_by_length=true, scale=1, scale_by_length=true, module path_sweep(shape, path, method="incremental", normal, closed, twist=0, twist_by_length=true, scale=1, scale_by_length=true,
@@ -2113,9 +2130,6 @@ module path_sweep(shape, path, method="incremental", normal, closed, twist=0, tw
assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\""); assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\"");
trans_scale = path_sweep(shape, path, method, normal, closed, twist, twist_by_length, scale, scale_by_length, trans_scale = path_sweep(shape, path, method, normal, closed, twist, twist_by_length, scale, scale_by_length,
symmetry, last_normal, tangent, uniform, relaxed, caps, style, transforms=true,_return_scales=true); symmetry, last_normal, tangent, uniform, relaxed, caps, style, transforms=true,_return_scales=true);
caps = is_def(caps) ? caps :
closed ? false : true;
fullcaps = is_bool(caps) ? [caps,caps] : caps;
transforms = trans_scale[0]; transforms = trans_scale[0];
scales = trans_scale[1]; scales = trans_scale[1];
firstscale = is_num(scales[0]) ? 1/scales[0] : [1/scales[0].x, 1/scales[0].y]; firstscale = is_num(scales[0]) ? 1/scales[0] : [1/scales[0].x, 1/scales[0].y];
@@ -2125,7 +2139,7 @@ module path_sweep(shape, path, method="incremental", normal, closed, twist=0, tw
shape_normals = -path3d(path_normals(clockwise_polygon(shape), closed=true)) shape_normals = -path3d(path_normals(clockwise_polygon(shape), closed=true))
) )
[for(T=transforms) apply(_force_rot(T),shape_normals)]; [for(T=transforms) apply(_force_rot(T),shape_normals)];
vnf = sweep(is_path(shape)?clockwise_polygon(shape):shape, transforms, closed=false, _closed_for_normals=closed, caps=fullcaps,style=style, vnf = sweep(is_path(shape)?clockwise_polygon(shape):shape, transforms, closed=false, _closed_for_normals=closed, caps=caps,style=style,
texture=texture, tex_reps=tex_reps, tex_size=tex_size, tex_samples=tex_samples, normals=tex_normals, texture=texture, tex_reps=tex_reps, tex_size=tex_size, tex_samples=tex_samples, normals=tex_normals,
tex_inset=tex_inset, tex_rot=tex_rot, tex_depth=tex_depth, tex_extra=tex_extra, tex_skip=tex_skip); tex_inset=tex_inset, tex_rot=tex_rot, tex_depth=tex_depth, tex_extra=tex_extra, tex_skip=tex_skip);
shapecent = point3d(centroid(shape)); shapecent = point3d(centroid(shape));
@@ -2181,10 +2195,6 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0,
assert((is_region(shape) || is_path(shape,2)) || (transforms && !(closed && method=="incremental")),"shape must be a 2d path or region") assert((is_region(shape) || is_path(shape,2)) || (transforms && !(closed && method=="incremental")),"shape must be a 2d path or region")
let( let(
path = path3d(path), path = path3d(path),
caps = is_def(caps) ? caps :
closed ? false : true,
capsOK = is_bool(caps) || is_bool_list(caps,2),
fullcaps = is_bool(caps) ? [caps,caps] : caps,
normalOK = is_undef(normal) || (method!="natural" && is_vector(normal,3)) normalOK = is_undef(normal) || (method!="natural" && is_vector(normal,3))
|| (method=="manual" && same_shape(normal,path)), || (method=="manual" && same_shape(normal,path)),
scaleOK = scale==1 || ((is_num(scale) || is_vector(scale,2)) && !closed) || is_vector(scale,len(path)) || is_matrix(scale,len(path),2) scaleOK = scale==1 || ((is_num(scale) || is_vector(scale,2)) && !closed) || is_vector(scale,len(path)) || is_matrix(scale,len(path),2)
@@ -2193,8 +2203,6 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0,
assert(normalOK, method=="natural" ? "Cannot specify normal with the \"natural\" method" assert(normalOK, method=="natural" ? "Cannot specify normal with the \"natural\" method"
: method=="incremental" ? "Normal with \"incremental\" method must be a 3-vector" : method=="incremental" ? "Normal with \"incremental\" method must be a 3-vector"
: str("Incompatible normal given. Must be a 3-vector or a list of ",len(path)," 3-vectors")) : str("Incompatible normal given. Must be a 3-vector or a list of ",len(path)," 3-vectors"))
assert(capsOK, "caps must be boolean or a list of two booleans")
assert(!closed || !caps, "Cannot make closed shape with caps")
assert(is_undef(normal) || (is_vector(normal) && len(normal)==3) || (is_path(normal) && len(normal)==len(path) && len(normal[0])==3), "Invalid normal specified") assert(is_undef(normal) || (is_vector(normal) && len(normal)==3) || (is_path(normal) && len(normal)==len(path) && len(normal[0])==3), "Invalid normal specified")
assert(is_undef(tangent) || (is_path(tangent) && len(tangent)==len(path) && len(tangent[0])==3), "Invalid tangent specified") assert(is_undef(tangent) || (is_path(tangent) && len(tangent)==len(path) && len(tangent[0])==3), "Invalid tangent specified")
assert(scaleOK,str("Incompatible or invalid scale",closed?" for closed path":"",": must be ", closed?"":"a scalar, a 2-vector, ", assert(scaleOK,str("Incompatible or invalid scale",closed?" for closed path":"",": must be ", closed?"":"a scalar, a 2-vector, ",
@@ -2330,7 +2338,7 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0,
transforms && _return_scales transforms && _return_scales
? [transform_list,scale] ? [transform_list,scale]
: transforms ? transform_list : transforms ? transform_list
: sweep(is_path(shape)?clockwise_polygon(shape):shape, transform_list, closed=false, caps=fullcaps,style=style, : sweep(is_path(shape)?clockwise_polygon(shape):shape, transform_list, closed=false, caps=caps,style=style,
anchor=anchor,cp=cp,spin=spin,orient=orient,atype=atype, anchor=anchor,cp=cp,spin=spin,orient=orient,atype=atype,
texture=texture, tex_reps=tex_reps, tex_size=tex_size, tex_samples=tex_samples, texture=texture, tex_reps=tex_reps, tex_size=tex_size, tex_samples=tex_samples,
tex_inset=tex_inset, tex_rot=tex_rot, tex_depth=tex_depth, tex_extra=tex_extra, tex_skip=tex_skip, tex_inset=tex_inset, tex_rot=tex_rot, tex_depth=tex_depth, tex_extra=tex_extra, tex_skip=tex_skip,
@@ -2485,9 +2493,16 @@ function _ofs_face_edge(face,firstlen,second=false) =
// is a list of 4x4 transformation matrices. The sweep algorithm applies each transformation in sequence // is a list of 4x4 transformation matrices. The sweep algorithm applies each transformation in sequence
// to the shape input and links the resulting polygons together to form a polyhedron. // to the shape input and links the resulting polygons together to form a polyhedron.
// If `closed=true` then the first and last transformation are linked together. // If `closed=true` then the first and last transformation are linked together.
// The `caps` parameter controls whether the ends of the shape are closed.
// As a function, returns the VNF for the polyhedron. As a module, computes the polyhedron. // As a function, returns the VNF for the polyhedron. As a module, computes the polyhedron.
// . // .
// The `caps` parameter controls what happens at the ends of the polyhedron. If `closed=true` the shape links to itself and has no
// ends, but when `closed` is false, the two ends are, by default capped with flat faces. If you set `caps=false` then the ends
// receive no faces and the resulting non-manifold polyhedron has exposed edges. You can also set caps to a number, which adds a
// rounded cap with the specified radius, or you can set caps to an {{offset_sweep()}} end treatment, and the specified sweep will
// be attached as a cap. Note that you are **adding** a rounded cap, not rounding the specified shape as is common for many other
// library modules. The rounded cap is attached to the end face and may not blend neatly with the swept shape unless the sides of
// the swept shape are perpendicular to the end cap.
// .
// This is a powerful, general framework for producing polyhedra. It is important // This is a powerful, general framework for producing polyhedra. It is important
// to ensure that your resulting polyhedron does not include any self-intersections, or it will // to ensure that your resulting polyhedron does not include any self-intersections, or it will
// be invalid and generate CGAL errors. If you get such errors, most likely you have an // be invalid and generate CGAL errors. If you get such errors, most likely you have an
@@ -2500,11 +2515,13 @@ function _ofs_face_edge(face,firstlen,second=false) =
// This works by passing through to {{vnf_vertex_array()}}, which also has more details on // This works by passing through to {{vnf_vertex_array()}}, which also has more details on
// texturing. Note that textures work only when the shape is a path; you cannot apply a texture to a region. // texturing. Note that textures work only when the shape is a path; you cannot apply a texture to a region.
// The texture tiles are oriented on the path sweep so that the Y axis of the tile is aligned with the sweep direction. // The texture tiles are oriented on the path sweep so that the Y axis of the tile is aligned with the sweep direction.
// .
//
// Arguments: // Arguments:
// shape = 2d path or region, describing the shape to be swept. // shape = 2d path or region, describing the shape to be swept.
// transforms = list of 4x4 matrices to apply // transforms = list of 4x4 matrices to apply
// closed = set to true to form a closed (torus) model. Default: false // closed = set to true to form a closed (torus) model. Default: false
// caps = true to create endcap faces when closed is false. Can be a singe boolean to specify endcaps at both ends, or a length 2 boolean array. Default is true if closed is false. // caps = if closed is false, set caps to false to leave the ends open. Other values are true to create a flat cap, a number a rounded cap, or an {{offset_sweep()}} end treatment to create the specified offset sweep. Can be a single value or pair of values to control the caps independently at each end. Default: true
// style = vnf_vertex_array style. Default: "min_edge" // style = vnf_vertex_array style. Default: "min_edge"
// --- // ---
// convexity = convexity setting for use with polyhedron. (module only) Default: 10 // convexity = convexity setting for use with polyhedron. (module only) Default: 10
@@ -2570,15 +2587,24 @@ function sweep(shape, transforms, closed=false, caps, style="min_edge",
assert(is_consistent(transforms, ident(4)), "Input transforms must be a list of numeric 4x4 matrices in sweep") assert(is_consistent(transforms, ident(4)), "Input transforms must be a list of numeric 4x4 matrices in sweep")
assert(is_path(shape,2) || is_region(shape), "Input shape must be a 2d path or a region.") assert(is_path(shape,2) || is_region(shape), "Input shape must be a 2d path or a region.")
let( let(
caps = is_def(caps) ? caps : caps = is_list(caps) && select(caps,0,1)==["for","offset_sweep"] ? [caps,caps]
closed ? false : true, : is_bool(caps) || is_num(caps) ? [caps,caps]
capsOK = is_bool(caps) || is_bool_list(caps,2), : is_undef(caps) ? closed ? [false,false] : [true,true]
fullcaps = is_bool(caps) ? [caps,caps] : caps : caps,
capsOK = is_list(caps) && len(caps)==2
&&
[] == [for(cap=caps)
if (!(is_bool(cap) || is_num(cap) || select(cap,0,1)==["for","offset_sweep"])) 1],
flatcaps = [for(cap=caps) is_bool(cap) ? cap : false],
fancycaps = [for(cap=caps) is_bool(cap) ? false
: is_num(cap) ? os_circle(r=cap,steps=ceil(segs(cap)/4))
: cap]
) )
assert(len(transforms)>=2, "transformation must be length 2 or more") assert(len(transforms)>=2, "transformation must be length 2 or more")
assert(capsOK, "caps must be boolean or a list of two booleans") assert(capsOK, "caps must be boolean, number, an offset_sweep specification, or a list of two of those")
assert(!closed || !caps, "Cannot make closed shape with caps") assert(!closed || caps==[false,false], "Cannot make closed shape with caps")
is_region(shape)? is_region(shape)?
assert(fancycaps==[false,false], "rounded caps are not supported for regions")
assert(is_undef(texture), "textures are not supported for regions, only paths") assert(is_undef(texture), "textures are not supported for regions, only paths")
let( let(
regions = region_parts(shape), regions = region_parts(shape),
@@ -2587,8 +2613,8 @@ function sweep(shape, transforms, closed=false, caps, style="min_edge",
for (rgn=regions) each [ for (rgn=regions) each [
for (path=rgn) for (path=rgn)
sweep(path, transforms, closed=closed, caps=false, style=style), sweep(path, transforms, closed=closed, caps=false, style=style),
if (fullcaps[0]) vnf_from_region(rgn, transform=transforms[0], reverse=true), if (flatcaps[0]) vnf_from_region(rgn, transform=transforms[0], reverse=true),
if (fullcaps[1]) vnf_from_region(rgn, transform=last(transforms)), if (flatcaps[1]) vnf_from_region(rgn, transform=last(transforms)),
], ],
], ],
vnf = vnf_join(vnfs) vnf = vnf_join(vnfs)
@@ -2603,12 +2629,22 @@ function sweep(shape, transforms, closed=false, caps, style="min_edge",
: let( : let(
n = surface_normals(select(points,0,-2), col_wrap=true, row_wrap=true) n = surface_normals(select(points,0,-2), col_wrap=true, row_wrap=true)
) )
[each n, n[0]] [each n, n[0]],
) vva_result = vnf_vertex_array(points, normals=normals,
vnf_vertex_array(points, normals=normals, cap1=flatcaps[0],cap2=flatcaps[1],col_wrap=true,style=style, return_edges=fancycaps!=[false,false],
cap1=fullcaps[0],cap2=fullcaps[1],col_wrap=true,style=style, texture=texture, tex_reps=tex_reps, tex_size=tex_size, tex_samples=tex_samples,
texture=texture, tex_reps=tex_reps, tex_size=tex_size, tex_samples=tex_samples, tex_inset=tex_inset, tex_rot=tex_rot, tex_depth=tex_depth, tex_extra=tex_extra, tex_skip=tex_skip),
tex_inset=tex_inset, tex_rot=tex_rot, tex_depth=tex_depth, tex_extra=tex_extra, tex_skip=tex_skip); vnf = fancycaps==[false,false] ? vva_result
: vnf_join(
[ vva_result[0],
for(ind=[0,1])
if (fancycaps[ind]) let(
polygon = vva_result[1][ind+2],
plane = plane_from_polygon(ind==0? reverse(polygon) : polygon)
)
apply(lift_plane(plane),offset_sweep(project_plane(plane, polygon), top=fancycaps[ind], caps=[false,true]))
])
) vnf;
module sweep(shape, transforms, closed=false, caps, style="min_edge", convexity=10, module sweep(shape, transforms, closed=false, caps, style="min_edge", convexity=10,
@@ -4952,7 +4988,7 @@ module _textured_revolution(
} }
function _textured_point_array(points, texture, tex_reps, tex_size, tex_samples, tex_inset=false, tex_rot=0, triangulate=false, tex_scaling="default", function _textured_point_array(points, texture, tex_reps, tex_size, tex_samples, tex_inset=false, tex_rot=0, triangulate=false, tex_scaling="default",return_edges=false,
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) = 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_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(tex_size==undef || is_num(tex_size) || is_vector(tex_size,2), "tex_size must be a scalar or 2-vector")
@@ -5016,7 +5052,8 @@ function _textured_point_array(points, texture, tex_reps, tex_size, tex_samples,
] ]
] ]
) )
vnf_vertex_array(tex_surf, row_wrap=row_wrap, col_wrap=col_wrap, reverse=reverse,style=style, caps=caps, cap1=cap1, cap2=cap2, triangulate=triangulate) vnf_vertex_array(tex_surf, row_wrap=row_wrap, col_wrap=col_wrap, reverse=reverse,style=style,
caps=caps, cap1=cap1, cap2=cap2, triangulate=triangulate, return_edges=return_edges)
: // VNF case : // VNF case
let( let(
local_scale = [for(y=[-1:1:ptsize.y]) local_scale = [for(y=[-1:1:ptsize.y])
@@ -5068,7 +5105,7 @@ function _textured_point_array(points, texture, tex_reps, tex_size, tex_samples,
) )
base + _tex_height(tex_depth,tex_inset,pt.z) * normal*(reverse?-1:1) * scale, base + _tex_height(tex_depth,tex_inset,pt.z) * normal*(reverse?-1:1) * scale,
fullvnf = vnf_join([ fullvnf = vnf_join([
for(y=[0:1:tex_reps.y-1], x=[0:1:tex_reps.x-1]) for(y=[0:1:tex_reps.y-1], x=[0:1:tex_reps.x-1]) // Main body of the textured shape
[ [
[for(pt=vnf[0]) trans_pt(x,y,pt)], [for(pt=vnf[0]) trans_pt(x,y,pt)],
vnf[1] vnf[1]
@@ -5092,9 +5129,27 @@ function _textured_point_array(points, texture, tex_reps, tex_size, tex_samples,
[for(pt = closed_path) trans_pt(x,y,[x?1:0,pt.y,pt.z])]] [for(pt = closed_path) trans_pt(x,y,[x?1:0,pt.y,pt.z])]]
) )
for(path=cap_paths) [path, [count(path,reverse=x!=0)]] for(path=cap_paths) [path, [count(path,reverse=x!=0)]]
]) ]),
edgepaths = !return_edges ? undef
: [
if (!col_wrap)
for(x=[0, tex_reps.x-1])
[for(y=[0:1:tex_reps.y-1],pt=xedge_paths[0][0])
trans_pt(x,y,[x?1:0,pt.y,pt.z])]
else each [[],[]],
if (!row_wrap && len(yedge_paths[0])>0)
for(ind=[0,1])
if ([cap1,cap2][ind]) []
else let(y=[0,tex_reps.y-1][ind])
[for(x=[0:1:tex_reps.x-1], pt=yedge_paths[0][0])
trans_pt(x,y,[pt.x,y?0:1,pt.z])]
else each [[],[]]
],
revvnf = reverse ? vnf_reverse_faces(fullvnf) : fullvnf
) )
reverse ? vnf_reverse_faces(fullvnf) : fullvnf; !return_edges ? revvnf : [revvnf, edgepaths];
// Resamples a point array to the specified size. // Resamples a point array to the specified size.

View File

@@ -35,10 +35,11 @@ include <geometry.scad>
include <regions.scad> include <regions.scad>
include <strings.scad> include <strings.scad>
include <vnf.scad> include <vnf.scad>
include <structs.scad>
include <rounding.scad>
include <skin.scad> include <skin.scad>
include <utility.scad> include <utility.scad>
include <partitions.scad> include <partitions.scad>
include <structs.scad>
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View File

@@ -73,6 +73,12 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// For creating the texture, `vnf_vertex_array()` uses normals to the surface that it estimates from the surface data itself. // For creating the texture, `vnf_vertex_array()` uses normals to the surface that it estimates from the surface data itself.
// If you have more accurate normals or need the normals to take particular values, you can pass an array of normals // If you have more accurate normals or need the normals to take particular values, you can pass an array of normals
// using the `normals` parameter. // using the `normals` parameter.
// .
// You can set `return_edges=true` to return the paths of the four edges of the output. In this case the return value
// is `[vnf,edgelist]` where edgelist is [left (column 0 of points), right (last column of points), top (points[0]), bottom (last(points)]. If a given
// edge does not exist then it will be the empty list in the output. An edge only exists it is not capped and not wrapped. The main
// need for this feature is when you have added a texture and need a way to interface the shape with something else. In this case you cannot
// easily determine the edges yourself from the input point list. edges are not easily
// Arguments: // Arguments:
// points = A list of vertices to divide into columns and rows. // points = A list of vertices to divide into columns and rows.
// --- // ---
@@ -99,6 +105,7 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// sidecap2 = set sidecap only for the `points[][max]` edge of the output // sidecap2 = set sidecap only for the `points[][max]` edge of the output
// tex_scaling = set to "const" to disable grid size vertical scaling of the texture. Default: "default" // tex_scaling = set to "const" to disable grid size vertical scaling of the texture. Default: "default"
// normals = array of normal vectors to each point in the point array for more accurate texture height calculation // normals = array of normal vectors to each point in the point array for more accurate texture height calculation
// return_edges = if true return [vnf,edgelist] where edgelist is the paths of four edges, [left (column 0 of points), right (last column of points), top (points[0]), bottom (last(points)]. Default: false
// cp = (module) Centerpoint for determining intersection anchors or centering the shape. Determines the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" // cp = (module) Centerpoint for determining intersection anchors or centering the shape. Determines the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid"
// anchor = (module) Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `"origin"` // anchor = (module) Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `"origin"`
// spin = (module) Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` // spin = (module) Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
@@ -326,7 +333,7 @@ function vnf_vertex_array(
row_wrap=false, row_wrap=false,
reverse=false, reverse=false,
style="default", style="default",
triangulate = false, triangulate = false, return_edges=false,
texture, tex_reps, tex_size, tex_samples, tex_inset=false, tex_rot=0, tex_scaling="default", texture, tex_reps, tex_size, tex_samples, tex_inset=false, tex_rot=0, tex_scaling="default",
tex_depth=1, tex_extra, tex_skip, sidecaps,sidecap1,sidecap2, normals tex_depth=1, tex_extra, tex_skip, sidecaps,sidecap1,sidecap2, normals
) = ) =
@@ -336,7 +343,7 @@ function vnf_vertex_array(
assert(is_bool(triangulate)) assert(is_bool(triangulate))
is_def(texture) ? is_def(texture) ?
_textured_point_array(points=points, texture=texture, tex_reps=tex_reps, tex_size=tex_size, _textured_point_array(points=points, texture=texture, tex_reps=tex_reps, tex_size=tex_size,
tex_inset=tex_inset, tex_samples=tex_samples, tex_rot=tex_rot, tex_scaling=tex_scaling, tex_inset=tex_inset, tex_samples=tex_samples, tex_rot=tex_rot, tex_scaling=tex_scaling, return_edges=return_edges,
col_wrap=col_wrap, row_wrap=row_wrap, tex_depth=tex_depth, caps=caps, cap1=cap1, cap2=cap2, reverse=reverse, col_wrap=col_wrap, row_wrap=row_wrap, tex_depth=tex_depth, caps=caps, cap1=cap1, cap2=cap2, reverse=reverse,
style=style, tex_extra=tex_extra, tex_skip=tex_skip, sidecaps=sidecaps, sidecap1=sidecap1, sidecap2=sidecap2,normals=normals,triangulate=triangulate) style=style, tex_extra=tex_extra, tex_skip=tex_skip, sidecaps=sidecaps, sidecap1=sidecap1, sidecap2=sidecap2,normals=normals,triangulate=triangulate)
: :
@@ -431,10 +438,17 @@ function vnf_vertex_array(
) )
rfaces, rfaces,
], ],
vnf = [verts, allfaces] vnf = [verts, allfaces],
) triangulate? vnf_triangulate(vnf) : vnf; tvnf = triangulate? vnf_triangulate(vnf) : vnf
)
!return_edges ? tvnf
: [tvnf, [
if (!col_wrap) deduplicate(column(points,0)) else [],
if (!col_wrap) deduplicate(column(points, len(points[0])-1)) else [],
if (!cap1 && !row_wrap) deduplicate(points[0]) else [],
if (!cap2 && !row_wrap) deduplicate(last(points)) else []
]
];
// Function&Module: vnf_tri_array() // Function&Module: vnf_tri_array()
// Synopsis: Returns a VNF from an array of points. The array need not be rectangular. // Synopsis: Returns a VNF from an array of points. The array need not be rectangular.