diff --git a/beziers.scad b/beziers.scad index 464bf5c4..41c21baf 100644 --- a/beziers.scad +++ b/beziers.scad @@ -1572,6 +1572,8 @@ function bezier_patch_normals(patch, u, v) = // SynTags: VNF // Topics: Bezier Patches // See Also: bezier_patch_normals(), vnf_sheet() +// Usage: +// vnf = bezier_sheet(patch, thickness, [splinesteps=], [balanced=] // Description: // Constructs a thin sheet from a bezier patch by offsetting the given patch along the normal vectors // to the patch surface. The thickness value must be small enough so that no points cross each other @@ -1591,6 +1593,7 @@ function bezier_patch_normals(patch, u, v) = // thickness = amount to offset; can be positive or negative // --- // splinesteps = Number of segments on the border edges of the bezier surface. You can specify [USTEPS,VSTEPS]. Default: 16 +// balanced = if true, then offset the bezier surface by half the specified thickness on each side. This increases execution time because two offsets must be performed. The sign of `thickness` does not matter. Default: false // style = {{vnf_vertex_array()}} style to use. Default: "default" // Example(3D): // patch = [ @@ -1602,7 +1605,7 @@ function bezier_patch_normals(patch, u, v) = // // u=0,v=1 u=1,v=1 // ]; // vnf_polyhedron(bezier_sheet(patch, 10)); -function bezier_sheet(patch, thickness, splinesteps=16, style="default") = +function bezier_sheet(patch, thickness, splinesteps=16, style="default", balanced=false) = assert(is_bezier_patch(patch)) assert(all_nonzero([thickness]), "thickness must be nonzero") let( @@ -1612,8 +1615,9 @@ function bezier_sheet(patch, thickness, splinesteps=16, style="default") = pts = bezier_patch_points(patch, uvals, vvals), normals = bezier_patch_normals(patch, uvals, vvals), dummy=assert(is_matrix(flatten(normals)),"Bezier patch has degenerate normals"), - offset = pts + thickness*normals, - allpoints = [for(i=idx(pts)) concat(pts[i], reverse(offset[i]))], + offset0 = balanced ? pts - 0.5*thickness*normals : pts, + offset1 = pts + (balanced ? 0.5 : 1) * thickness*normals, + allpoints = [for(i=idx(offset0)) concat(offset0[i], reverse(offset1[i]))], vnf = vnf_vertex_array(allpoints, col_wrap=true, caps=true, style=style) ) thickness<0 ? vnf_reverse_faces(vnf) : vnf; diff --git a/isosurface.scad b/isosurface.scad index d2886fe4..278c4530 100644 --- a/isosurface.scad +++ b/isosurface.scad @@ -1801,7 +1801,7 @@ function debug_tetra(r) = let(size=r/norm([1,1,1])) [ // specify `voxel_size` or `voxel_count`, then a default count of 10,000 voxels is used, // which should be reasonable for initial preview. // . -// In 2D, If you don't specify `pixel_size` or `pixel_count`, then a default count of 1024 voxels is used, +// In 2D, If you don't specify `pixel_size` or `pixel_count`, then a default count of 1024 pixels is used, // which is reasonable for initial preview. You may find, however, that 2D metaballs are reasonably fast // even at finer resolution. // . diff --git a/skin.scad b/skin.scad index cd0f2811..2e38f5da 100644 --- a/skin.scad +++ b/skin.scad @@ -41,7 +41,7 @@ __vnf_no_n_mesg=" texture is a VNF so it does not accept n. Set sample rate for // and can result in cryptic CGAL errors upon rendering with a second object present, even though the polyhedron appears // OK during preview or when rendered by itself. The order of points in your profiles must be // consistent from slice to slice so that points match up without creating twists. You can specify -// profiles in any consistent order: if necessary, skin() will reverse the faces to ensure that the final +// profiles in any consistent order: if necessary, skin() reverses the faces to ensure that the final // result has clockwise faces as required by CGAL. Note that the face reversal test may give random results // if you use skin to construct self-intersecting (invalid) polyhedra. // . @@ -70,13 +70,13 @@ __vnf_no_n_mesg=" texture is a VNF so it does not accept n. Set sample rate for // . // Resampling may occur, depending on the `method` parameter, to make profiles compatible. // To force (possibly additional) resampling of the profiles to increase the point density you can set `refine=N`, which -// will multiply the number of points on your profile by `N`. You can choose between two resampling +// multiplies the number of points on your profile by `N`. You can choose between two resampling // schemes using the `sampling` option, which you can set to `"length"` or `"segment"`. // The length resampling method resamples proportional to length. // The segment method divides each segment of a profile into the same number of points. -// This means that if you refine a profile with the "segment" method you will get N points -// on each edge, but if you refine a profile with the "length" method you will get new points -// distributed around the profile based on length, so small segments will get fewer new points than longer ones. +// This means that if you refine a profile with the "segment" method, you get N points +// on each edge, but if you refine a profile with the "length" method, you get new points +// distributed around the profile based on length, so small segments get fewer new points than longer ones. // A uniform division may be impossible, in which case the code computes an approximation, which may result // in arbitrary distribution of extra points. See {{subdivide_path()}} for more details. // Note that when dealing with continuous curves it is always better to adjust the @@ -88,20 +88,20 @@ __vnf_no_n_mesg=" texture is a VNF so it does not accept n. Set sample rate for // like a hexagon or star, because the algorithms' suitability depend on this distinction. // . // The default method for aligning profiles is `method="direct"`. -// If you simply supply a list of compatible profiles it will link them up +// If you simply supply a list of compatible profiles, they link up // exactly as you have provided them. You may find that profiles you want to connect define the // right shapes but the point lists don't start from points that you want aligned in your skinned // polyhedron. You can correct this yourself using `reindex_polygon`, or you can use the "reindex" -// method which will look for the index choice that will minimize the length of all of the edges -// in the polyhedron—it will produce the least twisted possible result. This algorithm has quadratic -// run time so it can be slow with very large profiles. +// method, which looks for the index choice that minimizes the length of all of the edges +// in the polyhedron to produce the least twisted possible result. This algorithm has quadratic +// run time so it can be slow with large profiles. // . // When the profiles are incommensurate, the "direct" and "reindex" resample them to match. As noted above, // for continuous input curves, it is better to generate your curves directly at the desired sample size, // but for mapping between a discrete profile like a hexagon and a circle, the hexagon must be resampled // to match the circle. When you use "direct" or "reindex" the default `sampling` value is -// of `sampling="length"` to approximate a uniform length sampling of the profile. This will generally -// produce the natural result for connecting two continuously sampled profiles or a continuous +// of `sampling="length"` to approximate a uniform length sampling of the profile. This generally +// produces the natural result for connecting two continuously sampled profiles or a continuous // profile and a polygonal one. However depending on your particular case, // `sampling="segment"` may produce a more pleasing result. These two approaches differ only when // the segments of your input profiles have unequal length. @@ -134,7 +134,7 @@ __vnf_no_n_mesg=" texture is a VNF so it does not accept n. Set sample rate for // connects all the points in one group to the same vertex on the polygon. // . // The "tangent" method may fail if the curved profile is non-convex, or doesn't have enough points to distinguish -// all of the tangent points from each other. The algorithm treats whichever input profile has fewer points as the polygon +// all of the tangent points from each other. The algorithm treats whichever input profile has fewer points as the polygon, // and the other one as the curve. Using `refine` with this method will have little effect on the model, so // you should do it only for agreement with other profiles, and these models are linear, so extra slices also // have no effect. For best efficiency set `refine=1` and `slices=0`. As with the "distance" method, refinement @@ -164,7 +164,7 @@ __vnf_no_n_mesg=" texture is a VNF so it does not accept n. Set sample rate for // convexity = convexity setting for use with polyhedron. (module only) Default: 10 // anchor = Translate so anchor point is at the origin. Default: "origin" // spin = Rotate this many degrees around Z axis after anchor. Default: 0 -// orient = Vector to rotate top towards after spin +// orient = Vector to rotate top toward after spin // atype = Select "hull" or "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" // style = vnf_vertex_array style. Default: "min_edge" @@ -312,7 +312,7 @@ __vnf_no_n_mesg=" texture is a VNF so it does not accept n. Set sample rate for // skin([repeat_entries(prof1,[2,2,1,1,1,1,1]), // prof2], // method="distance", slices=10, refine=10); -// Example(FlatSpin,VPD=80,VPT=[0,0,7]): The "distance" method will often produces results similar to the "tangent" method if you use it with a polygon and a curve, but the results can also look like this: +// Example(FlatSpin,VPD=80,VPT=[0,0,7]): The "distance" method often produces results similar to the "tangent" method if you use it with a polygon and a curve, but the results can also look like this: // skin([path3d(circle($fn=128, r=10)), xrot(39, p=path3d(square([8,10]),10))], method="distance", slices=0); // Example(FlatSpin,VPD=80,VPT=[0,0,7]): Using the "tangent" method produces: // skin([path3d(circle($fn=128, r=10)), xrot(39, p=path3d(square([8,10]),10))], method="tangent", slices=0); @@ -448,7 +448,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close assert(methodlistok==[], str("method list contains invalid method at ",methodlistok)) assert(len(method) == profcount,"Method list is the wrong length") assert(in_list(sampling,["length","segment"]), "sampling must be set to \"length\" or \"segment\"") - assert(sampling=="segment" || (!in_list("distance",method) && !in_list("fast_distance",method) && !in_list("tangent",method)), "sampling is set to \"length\" which is only allowed with methods \"direct\" and \"reindex\"") + assert(sampling=="segment" || (!in_list("distance",method) && !in_list("fast_distance",method) && !in_list("tangent",method)), "sampling is set to \"length\", which is allowed only with methods \"direct\" and \"reindex\"") assert(capsOK, "caps must be boolean or a list of two booleans") assert(!closed || !caps, "Cannot make closed shape with caps") let( @@ -537,22 +537,22 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close // twisted extrusions by using `maxseg` to subsample flat faces. // . // Anchoring for linear_sweep is based on the anchors for the swept region rather than from the polyhedron that is created. This can produce more -// predictable anchors for LEFT, RIGHT, FWD and BACK in many cases, but the anchors may only -// be aproximately correct for twisted objects, and corner anchors may point in unexpected directions in some cases. +// predictable anchors for LEFT, RIGHT, FWD and BACK in many cases, but the anchors may be approximately +// correct only for twisted objects, and corner anchors may point in unexpected directions in some cases. // If you need anchors directly computed from the surface you can pass the vnf from linear_sweep -// to {{vnf_polyhedron()}}, which will compute anchors directly from the full VNF. +// to {{vnf_polyhedron()}}, which computes anchors directly from the full VNF. // Arguments: // 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 -// center = If true, the created polyhedron will be vertically centered. If false, it will be extruded upwards from the XY plane. Default: `false` +// center = If true, the created polyhedron is vertically centered. If false, it is extruded upward from the XY plane. Default: `false` // --- // twist = The number of degrees to rotate the top of the shape, clockwise around the Z axis, relative to the bottom. Default: 0 // scale = The amount to scale the top of the shape, in the X and Y directions, relative to the size of the bottom. Default: 1 // shift = The amount to shift the top of the shape, in the X and Y directions, relative to the position of the bottom. Default: [0,0] // slices = The number of slices to divide the shape into along the Z axis, to allow refinement of detail, especially when working with a twist. Default: `twist/5` -// maxseg = If given, then any long segments of the region will be subdivided to be shorter than this length. This can refine twisting flat faces a lot. Default: `undef` (no subsampling) +// maxseg = If given, then any long segments of the region are subdivided to be shorter than this length. This can refine twisting flat faces a lot. Default: `undef` (no subsampling) // 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 for the textures. Actual texture sizes will be 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. Default: `[5,5]` // tex_reps = If given instead of tex_size, a 2-vector giving the number of texture tile repetitions in the horizontal and vertical directions on the extrusion. // 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 @@ -565,7 +565,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close // atype = Set to "hull" or "intersect" to select anchor type. Default: "hull" // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `"origin"` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` +// orient = Vector to rotate top toward, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` // Anchor Types: // "hull" = Anchors to the virtual convex hull of the shape. // "intersect" = Anchors to the surface of the shape. @@ -672,7 +672,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close // linear_sweep(circle(20), texture=tile, // tex_size=[10,10],tex_depth=5, // h=40,convexity=4); -// Example: The same tile from above, turned 90 degrees, Note that it has endcaps on the disconnected components. These will not appear of `caps=false`. +// Example: The same tile from above, turned 90 degrees. Note that it has endcaps on the disconnected components. These do not appear if `caps=false`. // shape = skin([rect(2/5), // rect(2/3), // rect(2/5)], @@ -894,7 +894,7 @@ function linear_sweep( // --- // start = Start extrusion at this angle counterclockwise from the X+ axis. Default:0 // 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 for the textures. Actual texture sizes will be 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. Default: `[5,5]` // tex_reps = If given instead of tex_size, a 2-vector giving the number of texture tile repetitions in the direction perpendicular to extrusion and in the direction parallel to extrusion. // 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 @@ -902,13 +902,13 @@ function linear_sweep( // tex_samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 // tex_taper = If given as a number, tapers the texture height to zero over the first and last given percentage of the path. If given as a lookup table with indices between 0 and 100, uses the percentage lookup table to ramp the texture heights. Default: `undef` (no taper) // style = {{vnf_vertex_array()}} style. Default: "min_edge" -// closed = If false, and shape is given as a path, then the revolved path will be sealed to the axis of rotation with untextured caps. Default: `true` +// closed = If false, and shape is given as a path, then the revolved path is sealed to the axis of rotation with untextured caps. Default: `true` // convexity = (Module only) Convexity setting for use with polyhedron. Default: 10 // 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" // atype = Select "hull" or "intersect" anchor types. Default: "hull" // anchor = Translate so anchor point is at the origin. Default: "origin" // 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 toward after spin (module only) // Named Anchors: // "origin" = The native position of the shape. // Anchor Types: @@ -1042,7 +1042,7 @@ function linear_sweep( // path = [for(y=[-30:30]) [ 20-3*(1-cos((y+30)/60*360)),y]]; // rotate_sweep(path, closed=false, texture=tile, tex_rot=90, // tex_size=[12,8], tex_depth=9, angle=360); -// Example(3D,Med,NoAxes,VPR=[78.1,0,199.3],VPT=[-4.55445,1.37814,-4.39897],VPD=192.044): A basket weave texture, here only half way around the circle to avoid clutter. +// Example(3D,Med,NoAxes,VPR=[78.1,0,199.3],VPT=[-4.55445,1.37814,-4.39897],VPD=192.044): A basket weave texture, here only halfway around the circle to avoid clutter. // diag_weave_vnf = [ // [[0.2, 0, 0], [0.8, 0, 0], [1, 0.2, 0.5], [1, 0.8, 0.5], [0.7, 0.5, 0.5], // [0.5, 0.3, 0], [0.2, 0, 0.5], [0.8, 0, 0.5], [1, 0.2, 1], [1, 0.8, 1], @@ -1226,8 +1226,8 @@ module rotate_sweep( // "smooth" and "cut". // . // The inside argument changes how the extrusion lead-in sections are formed. If it is true then they scale -// towards the outside, like would be needed for internal threading. If internal is fale then the lead-in sections scale -// towards the inside, like would be appropriate for external threads. +// toward the outside, like would be needed for internal threading. If internal is fale then the lead-in sections scale +// toward the inside, like would be appropriate for external threads. // Arguments: // poly = Array of points of a polygon path, to be extruded. // h = height of the spiral extrusion path @@ -1247,10 +1247,10 @@ module rotate_sweep( // lead_in_shape1 = Specify the shape of the thread lead-in at the bottom by giving a text string or function. // lead_in_shape2 = Specify the shape of the thread lead-in at the top by giving a text string or function. // lead_in_sample = Factor to increase sample rate in the lead-in section. Default: 10 -// internal = if true make internal threads. The only effect this has is to change how the extrusion lead-in section are formed. When true, the extrusion scales towards the outside; when false, it scales towards the inside. Default: false +// internal = if true make internal threads. The only effect this has is to change how the extrusion lead-in section are formed. When true, the extrusion scales toward the outside; when false, it scales toward the inside. Default: false // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` +// orient = Vector to rotate top toward, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` // Example: // poly = [[-10,0], [-3,-5], [3,-5], [10,0], [0,-30]]; // spiral_sweep(poly, h=200, r=50, turns=3, $fn=36); @@ -1450,7 +1450,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // vector for the path, so this process is constructing a shape whose normal cross sections are equal to your specified shape. // If you do not supply a list of tangent vectors then an approximate tangent vector is computed // based on the path points you supply using {{path_tangents()}}. -// Figure(3D,Big,VPR=[70,0,345],VPD=20,VPT=[5.5,10.8,-2.7],NoScales): This example shows how the shape, in this case the quadrilateral defined by `[[0, 0], [0, 1], [0.25, 1], [1, 0]]`, appears as the cross section of the swept polyhedron. The blue line shows the path. The normal vector to the shape is shown in black; it is based at the origin and points upwards in the Z direction. The sweep aligns this normal vector with the blue path tangent, which in this case, flips the shape around. Note that for a 2D path like this one, the Y direction in the shape is mapped to the Z direction in the sweep. +// Figure(3D,Big,VPR=[70,0,345],VPD=20,VPT=[5.5,10.8,-2.7],NoScales): This example shows how the shape, in this case the quadrilateral defined by `[[0, 0], [0, 1], [0.25, 1], [1, 0]]`, appears as the cross section of the swept polyhedron. The blue line shows the path. The normal vector to the shape is shown in black; it is based at the origin and points upward in the Z direction. The sweep aligns this normal vector with the blue path tangent, which in this case, flips the shape around. Note that for a 2D path like this one, the Y direction in the shape is mapped to the Z direction in the sweep. // tri= [[0, 0], [0, 1], [.25,1], [1, 0]]; // path = arc(r=5,n=81,angle=[-20,65]); // % path_sweep(tri,path); @@ -1463,7 +1463,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // In the figure you can see that the swept polyhedron, shown in transparent gray, has the quadrilateral as its cross // section. The quadrilateral is positioned perpendicular to the path, which is shown in blue, so that the normal // vector for the quadrilateral is parallel to the tangent vector for the path. The origin for the shape is the point -// which follows the path. For a 2D path, the Y axis of the shape is mapped to the Z axis and in this case, +// that follows the path. For a 2D path, the Y axis of the shape is mapped to the Z axis and in this case, // pointing the quadrilateral's normal vector (in black) along the tangent line of // the path, which is going in the direction of the blue arrow, requires that the quadrilateral be "turned around". If we // reverse the order of points in the path we get a different result: @@ -1478,9 +1478,9 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // stroke([CENTER,UP], width=.07,endcap2="arrow2",color="black"); // Continues: // If your shape is too large for the curves in the path you can create a situation where the shapes cross each -// other. This results in an invalid polyhedron, which may appear OK when previewed or rendered alone, but will give rise -// to cryptic CGAL errors when rendered with a second object in your model. You may be able to use {{path_sweep2d()}} -// to produce a valid model in cases like this. You can debug models like this using the `profiles=true` option which will show all +// other. This results in an invalid polyhedron, which may appear OK when previewed or rendered alone, but can result +// in cryptic CGAL errors when rendered with a second object in your model. You may be able to use {{path_sweep2d()}} +// to produce a valid model in cases like this. You can debug models like this using the `profiles=true` option, which shows all // the cross sections in your polyhedron. If any of them intersect, the polyhedron will be invalid. // Figure(3D,Big,VPR=[47,0,325],VPD=23,VPT=[6.8,4,-3.8],NoScales): We have scaled the path to an ellipse and show a large triangle as the shape. The triangle is sometimes bigger than the local radius of the path, leading to an invalid polyhedron, which you can identify because the red lines cross in the middle. // tri= scale([4.5,2.5],[[0, 0], [0, 1], [1, 0]]); @@ -1508,12 +1508,12 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // joint. By default `twist` is therefore required to be a multiple of 360. However, if your shape has rotational // symmetry, this requirement is overly strict. You can specify the symmetry using the `symmetry` argument, and then // you can choose smaller twists consistent with the specified symmetry. The symmetry argument gives the number of -// rotations that map the shape exactly onto itself, so a pentagon has 5-fold symmetry. This argument is only valid -// for closed sweeps. When you specify symmetry, the twist must be a multiple of 360/symmetry. +// rotations that map the shape exactly onto itself, so a pentagon has 5-fold symmetry. This argument is valid +// only for closed sweeps. When you specify symmetry, the twist must be a multiple of 360/symmetry. // . // The twist is normally spread uniformly along your shape based on the path length. If you set `twist_by_length` to -// false then the twist will be uniform based on the point count of your path. Twisted shapes will produce twisted -// faces, so if you want them to look good you should use lots of points on your path and also lots of points on the +// false, then the twist is uniform based on the point count of your path. Twisted shapes produce twisted +// faces, so if you want them to look good, you should use lots of points on your path and also lots of points on the // shape. If your shape is a simple polygon, use {{subdivide_path()}} to increase // the number of points. // . @@ -1523,7 +1523,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // one we use in 2D. You may find that the shape rotates unexpectedly around its axis as it traverses the path. The // `method` parameter allows you to specify how the shapes are aligned, resulting in different twist in the resulting // polyhedron. You can choose from three different methods for selecting the rotation of your shape. None of these -// methods will produce good, or even valid, results on all inputs, so it is important to select a suitable method. +// methods produce good, or even valid, results on all inputs, so it is important to select a suitable method. // . // The three methods you can choose using the `method` parameter are: // . @@ -1536,12 +1536,12 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // To start the algorithm, we need an initial condition. This is supplied by // using the `normal` argument to give a direction to align the Y axis of your shape. By default the normal points UP if the path // makes an angle of 45 deg or less with the xy plane and it points BACK if the path makes a higher angle with the XY plane. You -// can also supply `last_normal` which provides an ending orientation constraint. Be aware that the curve may still exhibit +// can also supply `last_normal` to provide an ending orientation constraint. Be aware that the curve may still exhibit // twisting in the middle. This method is the default because it is the most robust, not because it generally produces the best result. // . // The "natural" method works by computing the Frenet frame at each point on the path. This is defined by the tangent to the curve and -// the normal which lies in the plane defined by the curve at each point. This normal points in the direction of curvature of the curve. -// The result is a very well behaved set of shape positions without any unexpected twisting—as long as the curvature never falls to zero. At a +// the normal that lies in the plane defined by the curve at each point. This normal points in the direction of curvature of the curve. +// The result is a well-behaved set of shape positions without any unexpected twisting—as long as the curvature never falls to zero. At a // point of zero curvature (a flat point), the curve does not define a plane and the natural normal is not defined. Furthermore, even if // you skip over this troublesome point so the normal is defined, it can change direction abruptly when the curvature is zero, leading to // a nasty twist and an invalid model. A simple example is a circular arc joined to another arc that curves the other direction. Note @@ -1549,7 +1549,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // . // The "manual" method allows you to specify your desired normal either globally with a single vector, or locally with // a list of normal vectors for every path point. The normal you supply is projected to be orthogonal to the tangent to the -// path and the Y direction of your shape will be aligned with the projected normal. (Note this is different from the "natural" method.) +// path, and the Y direction of your shape is aligned with the projected normal. (This is different from the "natural" method.) // Careless choice of a normal may result in a twist in the shape, or an error if your normal is parallel to the path tangent. // If you set `relax=true` then the condition that the cross sections are orthogonal to the path is relaxed and the swept object // uses the actual specified normal. In this case, the tangent is projected to be orthogonal to your supplied normal to define @@ -1558,16 +1558,16 @@ 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. // 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. // . // 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` // uses the shape to correct for twist when the shape closes on itself, so you must include a valid shape. // . -// By default path sweep objects are anchored to the named anchor "origin" which places the swept object right where you created it. -// Generally you will not want to set an anchor for a swept object, but instead change the path if you want to move it to a different location, -// but can also anchor using VNF anchoring. Usi either `atype="hull"` (the default) or `atype="intersect"` to create anchors based on the +// By default path sweep objects are anchored to the named anchor "origin", which places the swept object right where you created it. +// Generally you would not want to set an anchor for a swept object, but instead change the path if you want to move it to a different location, +// but you can also anchor using VNF anchoring. Use either `atype="hull"` (the default) or `atype="intersect"` to create anchors based on the // the object's VNF data. The center of the object is determined based on the `cp` argument and can be "centroid" (the default), "mean" to use the mean of the object, // or "box" to use the center of the bounding box. For complicated objects you may find it difficult to get useful results from the anchoring // system, which is designed for an object whose center is inside the object. When using an anchors, confirm that it is in the location you desire. @@ -1575,7 +1575,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // You can apply a texture to the path sweep object using the usual texture parameters. // See [Texturing](skin.scad#section-texturing) for more details on how textures work. // This works by passing through to {{vnf_vertex_array()}}, which also has more details on -// texturing. Note that textures only work 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. // Arguments: // shape = A 2D polygon path or region describing the shape to be swept. // path = 2D or 3D path giving the path to sweep over @@ -1599,7 +1599,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // transforms = set to true to return transforms instead of a VNF. These transforms can be manipulated and passed to sweep(). (function only) Default: false. // convexity = convexity parameter for polyhedron(). (module only) Default: 10 // 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 for the textures at `points[0][0]`. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]` +// 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. Default: `[5,5]` // 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 @@ -1609,7 +1609,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // tex_skip = number of lines of a heightfield texture to skip when starting. Can be a scalar or two vector to give x and y values. Default: 0 // anchor = Translate so anchor point is at the origin. Default: "origin" // spin = Rotate this many degrees around Z axis after anchor. Default: 0 -// orient = Vector to rotate top towards after spin +// orient = Vector to rotate top toward after spin // atype = Select "hull" or "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" // Side Effects: @@ -1636,7 +1636,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // path = [for(theta=[-180:5:180]) [theta/10, 10*sin(theta)]]; // sq = square(6); // path_sweep(sq,path); -// Example(Med,VPR=[34,0,8],NoScales): It may not be obvious, but the polyhedron in the previous example is invalid. It will eventually give CGAL errors when you combine it with other shapes. To see this, set profiles to true and look at the left side. The profiles cross each other and intersect. Any time this happens, your polyhedron is invalid, even if it seems to be working at first. Another observation from the profile display is that we have more profiles than needed over a lot of the shape, so if the model is slow, using fewer profiles in the flat portion of the curve might speed up the calculation. +// Example(Med,VPR=[34,0,8],NoScales): It may not be obvious, but the polyhedron in the previous example is invalid. It results in CGAL errors when you combine it with other shapes. To see this, set profiles to true and look at the left side. The profiles cross each other and intersect. Any time this happens, your polyhedron is invalid, even if it seems to be working at first. Another observation from the profile display is that we have more profiles than needed over a lot of the shape, so if the model is slow, using fewer profiles in the flat portion of the curve might speed up the calculation. // path = [for(theta=[-180:5:180]) [theta/10, 10*sin(theta)]]; // sq = square(6); // path_sweep(sq,path,profiles=true,width=.1,$fn=8); @@ -1651,7 +1651,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // elliptic_arc = xscale(2, p=arc($fn=64,angle=[0,180], r=30)); // Counter-clockwise // path_sweep(ushape, path3d(elliptic_arc)); -// Example(NoScales): Sweep along a clockwise elliptical arc, using "natural" method, which lines up the X axis of the shape with the direction of curvature. This means the X axis will point inward, so a counterclockwise arc gives: +// Example(NoScales): Sweep along a clockwise elliptical arc, using "natural" method, which lines up the X axis of the shape with the direction of curvature. This means the X axis points inward, so a counterclockwise arc gives: // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // elliptic_arc = xscale(2, p=arc($fn=64,angle=[0,180], r=30)); // Counter-clockwise // path_sweep(ushape, elliptic_arc, method="natural"); @@ -1672,7 +1672,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // // Prints 0.9, but we use pentagon with radius of 1.0 > 0.9 // echo(radius_of_curvature = 1/max(path_curvature(qpath))); // path_sweep(apply(rot(90),pentagon(r=1)), qpath, normal=BACK, method="manual"); -// cube(0.5); // Adding a small cube forces a CGAL computation which reveals +// cube(0.5); // Adding a small cube forces a CGAL computation that reveals // // the error by displaying nothing or giving a cryptic message // Example(NoScales): Using the `relax` option we allow the profiles to deviate from orthogonality to the path. This eliminates the crease that broke the previous example because the sections are all parallel to each other. // qpath = [for(x=[-3:.01:3]) [x,x*x/1.8,0]]; @@ -1753,7 +1753,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // helix = [for (i=[0:helix_steps]) helix(i/helix_steps)]; // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // path_sweep(ushape, helix, method="natural"); -// Example(Med,NoScales): Note that it may look like the shape above is flat, but the profiles are very slightly tilted due to the nonzero torsion of the curve. If you want as flat as possible, specify it so with the "manual" method: +// Example(Med,NoScales): Note that it may look like the shape above is flat, but the profiles are slightly tilted due to the nonzero torsion of the curve. If you want as flat as possible, specify it so with the "manual" method: // function helix(t) = [(t / 1.5 + 0.5) * 30 * cos(6 * 360 * t), // (t / 1.5 + 0.5) * 30 * sin(6 * 360 * t), // 200 * (1 - t)]; @@ -1774,7 +1774,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // yzcircle = yrot(90,p=path3d(circle($fn=64, r=30))); // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // path_sweep(ushape, yzcircle, method="manual", normal=UP, closed=true); -// Example(NoScales): The "natural" method will introduce twists when the curvature changes direction. A warning is displayed. +// Example(NoScales): The "natural" method introduces twists when the curvature changes direction. A warning is displayed. // arc1 = path3d(arc(angle=90, r=30)); // arc2 = xrot(-90, cp=[0,30],p=path3d(arc(angle=[90,180], r=30))); // two_arcs = path_merge_collinear(concat(arc1,arc2)); @@ -1807,7 +1807,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // knot_path = [for (i=[0:ksteps-1]) 50 * knot(a,b,(i/ksteps)*360)]; // ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; // path_sweep(ushape, knot_path, closed=true, method="natural"); -// Example(Med,NoScales): knot with twist. Note if you twist it the other direction the center section untwists because of the natural twist there. Also compare to the "incremental" method which has less twist in the center. +// Example(Med,NoScales): knot with twist. Note if you twist it the other direction the center section untwists because of the natural twist there. Also compare to the "incremental" method, which has less twist in the center. // function knot(a,b,t) = // rolling knot // [ a * cos (3 * t) / (1 - b* sin (2 *t)), // a * sin( 3 * t) / (1 - b* sin (2 *t)), @@ -1914,7 +1914,7 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals // path_sweep(shape,path,method="natural"){ // attach(["start-centroid","end-centroid"]) anchor_arrow(s=5); // } -// Example(Med,NoScales,VPR=[78.1,0,43.2],VPT=[2.18042,-0.485127,1.90371],VPD=74.4017): Note that the "start" anchors are backwards compared to the direction of the sweep, so you have to attach the TOP to align the shape with its ends. +// Example(Med,NoScales,VPR=[78.1,0,43.2],VPT=[2.18042,-0.485127,1.90371],VPD=74.4017): Note that the "start" anchors are backward compared to the direction of the sweep, so you have to attach the TOP to align the shape with its ends. // shape = back_half(right_half(star(n=5,id=5,od=10)),y=-1)[0]; // path = arc(angle=[0,180],d=30); // path_sweep(shape,path,method="natural",scale=[1,1.5]) @@ -2052,9 +2052,9 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0, // The mismatch is the inverse of the last transform times the first one for the closed case, or the inverse of the // desired final transform times the realized final transform in the open case. Note that when closed==true the last transform // is a actually looped around and applies to the first point position, so if we got back exactly where we started - // then it will be the identity, but we might have accumulated some twist which will show up as a rotation around the - // X axis. Similarly, in the closed==false case the desired and actual transformations can only differ in the twist, - // so we can need to calculate the twist angle so we can apply a correction, which we distribute uniformly over the whole path. + // then it will be the identity, but we might have accumulated some twist that appears as a rotation around the + // X axis. Likewise, in the closed==false case the desired and actual transformations can differ only in the twist, + // so we must calculate the twist angle so we can apply a correction, which we distribute uniformly over the whole path. reference_rot = closed ? rotations[0] : is_undef(last_normal) ? last(rotations) : let( @@ -2095,9 +2095,9 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0, // The mismatch is the inverse of the last transform times the first one for the closed case, or the inverse of the // desired final transform times the realized final transform in the open case. Note that when closed==true the last transform // is a actually looped around and applies to the first point position, so if we got back exactly where we started - // then it will be the identity, but we might have accumulated some twist which will show up as a rotation around the - // X axis. Similarly, in the closed==false case the desired and actual transformations can only differ in the twist, - // so we can need to calculate the twist angle so we can apply a correction, which we distribute uniformly over the whole path. + // then it will be the identity, but we might have accumulated some twist that appears as a rotation around the + // X axis. Similarly, in the closed==false case the desired and actual transformations can differ only in the twist, + // so we must calculate the twist angle so we can apply a correction, which we distribute uniformly over the whole path. reference_rot = closed ? rotations[0] : is_undef(last_normal) ? last(rotations) : let( @@ -2174,7 +2174,7 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0, // the path, so if you do not have local self-intersections, use {{path_sweep()}} instead. If xmax is the largest x value (in absolute value) // of the shape, then path_sweep2d() will work as long as the offset of `path` exists at `delta=xmax`. If the offset vanishes, as in the // case of a circle offset by more than its radius, then you will get an error about a degenerate offset. -// Note that global self-intersections will still give rise to CGAL errors. You should be able to handle these by partitioning your model. The y axis of the +// Global self-intersections still give rise to CGAL errors. You can handle these by partitioning your model. The y axis of the // shape is mapped to the z axis in the swept polyhedron, and no twisting can occur. // The quality parameter is passed to offset to determine the offset quality. // Arguments: @@ -2188,7 +2188,7 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0, // convexity = convexity parameter for polyhedron (module only) Default: 10 // anchor = Translate so anchor point is at the origin. Default: "origin" // spin = Rotate this many degrees around Z axis after anchor. Default: 0 -// orient = Vector to rotate top towards after spin +// orient = Vector to rotate top toward after spin // atype = Select "hull" or "intersect" anchor types. Default: "hull" // cp = Centerpoint for determining "intersect" anchors or centering the shape. Determines the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" // Named Anchors: @@ -2305,17 +2305,17 @@ function _ofs_face_edge(face,firstlen,second=false) = // 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. // . -// Note that this is a very 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 -// be invalid and will generate CGAL errors. If you get such errors, most likely you have an -// overlooked self-intersection. Note also that the errors will not occur when your shape is alone -// in your model, but will arise if you add a second object to the model. This may mislead you into -// thinking the second object caused a problem. Even adding a simple cube to the model will reveal the problem. +// be invalid and generate CGAL errors. If you get such errors, most likely you have an +// overlooked self-intersection. The errors do not occur when your shape is alone +// in your model, but errors arise if you add a second object to the model. This may mislead you into +// thinking the second object caused a problem. Even adding a simple cube to the model reveals the problem. // . // You can apply a texture to the sweep object using the usual texture parameters. // See [Texturing](skin.scad#section-texturing) for more details on how textures work. // This works by passing through to {{vnf_vertex_array()}}, which also has more details on -// texturing. Note that textures only work 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. // Arguments: // shape = 2d path or region, describing the shape to be swept. @@ -2324,9 +2324,9 @@ function _ofs_face_edge(face,firstlen,second=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. // 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 // 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 for the textures at `points[0][0]`. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]` +// 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. Default: `[5,5]` // 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 @@ -2338,7 +2338,7 @@ function _ofs_face_edge(face,firstlen,second=false) = // atype = Select "hull" or "intersect" anchor types. Default: "hull" // anchor = Translate so anchor point is at the origin. Default: "origin" // 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 toward after spin (module only) // Named Anchors: // "origin" = The native position of the shape. // Anchor Types: @@ -2465,17 +2465,17 @@ module sweep(shape, transforms, closed=false, caps, style="min_edge", convexity= // Alternative you can use parent anchor mode where give only the parent anchor and the child appears at its // child-specified (default) anchor point. The spin parameter spins the child around the attachment anchor axis. // . -// For a path_sweep() with no scaling, if you give a location or index that is exactly at one of the sections the normal will be in the plane -// of the section. In the general case if you give a location in between sections the normal will be normal to the facet. If you -// give a location at a section in the general case the normal will be the average of the normals of the two adjacent facets. +// For a path_sweep() with no scaling, if you give a location or index that is exactly at one of the sections, the normal is in the plane +// of the section. In the general case if you give a location in between sections the normal is normal to the facet. If you +// give a location at a section in the general case the normal is the average of the normals of the two adjacent facets. // For twisted or other complicated sweeps the normals may not be accurate. If you need accurate normals for such shapes, you must -// use the anchors for the VNF swept shape directly---it is a tradeoff between easy specification of the anchor location on the -// swept object, which may be very difficult with direct anchors, and accuracy of the normal. +// use the anchors for the VNF swept shape directly—it is a tradeoff between easy specification of the anchor location on the +// swept object, which may be difficult with direct anchors, and accuracy of the normal. // . -// For closed sweeps the index will wrap around and can be positive or negative. For sweeps that are not closed the index must -// be positive and no longer than the length of the path. In some cases for closed path_sweeps the shape can be a mobius strip +// For closed sweeps the index wraps around and can be positive or negative. For sweeps that are not closed the index must +// be positive and no longer than the length of the path. In some cases for closed path_sweeps the shape can be a Möbius strip // and it may take more than one cycle to return to the starting point. The extra twist will be properly handled in this case. -// If you construct a mobius strip using the generic {{sweep()}} then information about the amount of twist is not available +// If you construct a Möbius strip using the generic {{sweep()}} then information about the amount of twist is not available // to `sweep_attach()` so it will not be handled automatically. // . // The anchor you give acts as a 2D anchor to the path or region used by the sweep, in the XY plane as that shape appears @@ -2633,7 +2633,7 @@ function _force_int(x) = approx(round(x),x) ? round(x) : x; // interpolation value bewteen them. // anchor_pos and anchor_dir are the anchor data for the 2d shape // Return is [position, ydirection, zdirection], where zdirection -// is normal to the facet. Note that frac is only needed because +// is normal to the facet. Note that frac is needed because // of the possibility of twist. function _find_ps_dir(frac,prevind,nextind,twist,anchor_pos,anchor_dir) = @@ -2674,7 +2674,7 @@ function _find_ps_dir(frac,prevind,nextind,twist,anchor_pos,anchor_dir) = // Subdivides the input profiles to have length `numpoints` where `numpoints` must be at least as // big as the largest input profile. By default `numpoints` is set equal to the length of the // largest profile. You can set `numpoints="lcm"` to sample to the least common multiple of all -// curves, which will avoid sampling artifacts but may produce a huge output. After subdivision, +// curves, which avoids sampling artifacts but may produce a huge output. After subdivision, // profiles are sliced. // Arguments: // profiles = profiles to operate on @@ -2772,10 +2772,10 @@ function _smooth(data,len,closed=false,angle=false) = // the interpolation to be done the "long" way around the rotation instead of the short way. // Note that the rotation matrix cannot distinguish which way you rotate, only the place you // end after rotation. Another ambiguity arises if your rotation is more than 360 degrees. -// You can add turns with the turns parameter, so giving turns=1 will add 360 degrees to the -// rotation so it completes one full turn plus the additional rotation given my the transform. +// You can add turns with the turns parameter, so giving turns=1 add 360s degrees to the +// rotation so it completes one full turn plus the additional rotation given by the transform. // You can give long as a scalar or as a vector. Finally if closed is true then the -// resampling will connect back to the beginning. +// resampling connects back to the beginning. // . // The default is to resample based on the length of the arc defined by each rotation operator. This produces // uniform sampling over all of the transformations. It requires that each rotation has nonzero length. @@ -3094,7 +3094,7 @@ function _skin_distance_match(poly1,poly2) = // This function associates vertices but with the assumption that index 0 is associated between the -// two inputs. This gives only quadratic run time. As above, output is pair of polygons with +// two inputs. This has quadratic run time. As above, output is pair of polygons with // vertices duplicated as suited to use as input to skin(). function _skin_aligned_distance_match(poly1, poly2) = @@ -3116,7 +3116,7 @@ function _skin_aligned_distance_match(poly1, poly2) = /// Description: /// Finds a mapping of the vertices of the larger polygon onto the smaller one. Whichever input is the /// shorter path is the polygon, and the longer input is the curve. For every edge of the polygon, the algorithm seeks a plane that contains that -/// edge and is tangent to the curve. There will be more than one such point. To choose one, the algorithm centers the polygon and curve on their centroids +/// edge and is tangent to the curve. There is always more than one such point. To choose one, the algorithm centers the polygon and curve on their centroids /// and chooses the closer tangent point. The algorithm works its way around the polygon, computing a series of tangent points and then maps all of the /// points on the curve between two tangent points into one vertex of the polygon. This algorithm can fail if the curve has too few points or if it is concave. /// Arguments: @@ -3171,12 +3171,12 @@ function _find_one_tangent(curve, edge, curve_offset=[0,0,0], closed=true) = // that the input can be passed to `skin()`. This allows you to decide how the vertices are linked up rather than accepting // the automatically computed minimal distance linkage. However, the number of vertices in the polygons must not decrease in the list. // The output is a list of polygons that all have the same number of vertices with some duplicates. You specify the vertex splitting -// using the `split` which is a list where each entry corresponds to a polygon: split[i] is a value or list specifying which vertices in polygon i to split. -// Give the empty list if you don't want a split for a particular polygon. If you list a vertex once then it will be split and mapped to -// two vertices in the next polygon. If you list it N times then N copies will be created to map to N+1 vertices in the next polygon. +// using the `split`, which is a list where each entry corresponds to a polygon: split[i] is a value or list specifying which vertices in polygon i to split. +// Give the empty list if you don't want a split for a particular polygon. If you list a vertex once then it is split and mapped to +// two vertices in the next polygon. If you list it N times then N copies are created to map to N+1 vertices in the next polygon. // You must ensure that each mapping produces the correct number of vertices to exactly map onto every vertex of the next polygon. -// Note that if you split (only) vertex i of a polygon that means it will map to vertices i and i+1 of the next polygon. Vertex 0 will always -// map to vertex 0 and the last vertices will always map to each other, so if you want something different than that you'll need to reindex +// If you split only vertex i of a polygon, that means it will map to vertices i and i+1 of the next polygon. Vertex 0 always +// maps to vertex 0 and the last vertices always map to each other, so if you want something different than that you'll need to reindex // your polygons. // Arguments: // polygons = list of polygons to split @@ -3232,16 +3232,16 @@ function associate_vertices(polygons, split, curpoly=0) = // Section: Texturing // Some operations are able to add texture to the objects they create. A texture can be any regularly repeated variation in the height of the surface. -// To define a texture you need to specify how the height should vary over a rectangular block that will be repeated to tile the object. Because textures +// To define a texture you need to specify how the height should vary over a rectangular block that is repeated to tile the object. Because textures // are based on rectangular tiling, this means adding textures to curved shapes may result in distortion of the basic texture unit. For example, if you -// texture a cone, the scale of the texture will be larger at the wide end of the cone and smaller at the narrower end of the cone. +// texture a cone, the scale of the texture is larger at the wide end of the cone and smaller at the narrower end of the cone. // . // You can specify a texture using two methods: a height field or a VNF. For each method you also must specify the scale of the texture, which -// gives the size of the rectangular unit in your object that will correspond to one texture tile. Note that this scale does not preserve -// aspect ratio: you can stretch the texture as desired. +// gives the size of the rectangular unit in your object that corresponds to one texture tile. This scale does not preserve +// aspect ratio: you can stretch the texture as desired. // Subsection: Height Field Texture Maps // The simplest way to specify a texture map is to give a 2d array of -// height values which specify the height of the texture on a grid. +// height values that specify the height of the texture on a grid. // Values in the height field should generally range from 0 to 1. A zero height // in the height field corresponds to the height of the surface and 1 // the highest point in the texture above the surface being textured. @@ -3260,9 +3260,9 @@ function associate_vertices(polygons, split, curpoly=0) = // of the texture is 1 unit above the surface being textured, assuming that the texture // is designed to span the range from 0 to 1. The `tex_depth` parameter can adjust // this dimension of a texture without changing anything else, setting `tex_depth` negative -// will invert a texture, and `tex_inset` will lower a texture into the textured object. +// inverts a texture, and `tex_inset` lowers a texture into the textured object. // Textures that extend beyond the interval [0,1] are accepted, but the behavior of the -// `tex_depth` and `tex_inset` parameters will be less intuitive. +// `tex_depth` and `tex_inset` parameters may be less intuitive. // Figure(2D,Big,NoScales,VPR=[0,0,0],VPT=[6.86022,-1.91238,0],VPD=28.8248): // ftex1 = [0,1,1,0,0]; // left(0)color(.6*[1,1,1])rect([12,1],anchor=BACK+LEFT); @@ -3305,8 +3305,8 @@ function associate_vertices(polygons, split, curpoly=0) = // move([6,-.4])color("black")text("Texture Size", size=0.3,anchor=BACK); // Continues: // A more serious limitation of height field textures is that some shapes, such as hexagons or circles, cannot be accurately represented because -// their points don't fall on any grid. Trying to create such shapes is difficult and will require many points to approximate the -// true point positions for the desired shape. This will make the texture slow to compute. +// their points don't fall on any grid. Trying to create such shapes is difficult and requires many points to approximate the +// true point positions for the desired shape. This makes the texture slow to compute. // Another serious limitation is more subtle. In the 2D examples above, it is obvious how to connect the // dots together. But in 3D example we need to triangulate the points on a grid, and this triangulation is not unique. // The `style` argument lets you specify how the points are triangulated using the styles supported by {{vnf_vertex_array()}}. @@ -3345,15 +3345,15 @@ function associate_vertices(polygons, split, curpoly=0) = // example of such a texture and shape please let us know!) // Subsection: VNF Textures // VNF textures overcome all of the limitations of height field textures, but with two costs. They can be more difficult to construct than -// a simple array of height values, and they are significantly slower to compute for a tile with the same number of points. Note, however, for -// textures that don't neatly lie on a grid, a VNF tile will be more efficient than a finely sampled height field. With VNF textures you can create +// a simple array of height values, and they are significantly slower to compute for a tile with the same number of points. However, for +// textures that don't neatly lie on a grid, a VNF tile is more efficient than a finely sampled height field. With VNF textures you can create // textures that have disconnected components, or concavities that cannot be expressed with a single valued height map. However, you can also // create invalid textures that fail to close at the ends, so care is required to ensure that your resulting shape is valid. // . // A VNF texture is defined by providing a VNF whose projection onto the XY plane is contained in the unit square [0,1] x [0,1] so // that the VNF can be tiled. The VNF is tiled without a gap, matching the edges, so the vertices along corresponding edges must match to make a // consistent triangulation possible. The VNF cannot have any X or Y values outside the interval [0,1]. If you want a valid polyhedron -// that OpenSCAD will render then you need to take care with edges of the tiles that correspond to endcap faces in the textured object. +// that OpenSCAD can render then you need to take care with edges of the tiles that correspond to endcap faces in the textured object. // So for example, in a linear sweep, the top and bottom edges of tiles end abruptly to form the end cap of the object. You can make a valid object // in two ways. One way is to create a tile with a single, complete edge along Y=0, and of course a corresponding edges along Y=1. The second way // to make a valid object is to have no points at all on the Y=0 line, and of course none on Y=1. In this case, the resulting texture produces @@ -3378,7 +3378,7 @@ function associate_vertices(polygons, split, curpoly=0) = // and "folded" so it can follow the curve. This folding is controlled by the `tex_samples` parameter to {{cyl()}}, {{linear_sweep()}}, // and {{rotate_sweep()}}. Note that you specify it when you **use** the texture, not when you create it. This differs from height // fields, where the analogous parameter is the `n=` parameter of the {{texture()}} function. When `tex_samples` is too small, only the -// points given in the VNF will follow the surface, resulting in a blocky look and geometrical artifacts. +// points given in the VNF follows the surface, resulting in a blocky look and geometrical artifacts. // Figure(3D,Med,NoAxes): On the left the `tex_samples` value is small and the texture is blocky. On the right, the default value of 8 allows a reasonable fit to the cylinder. // xdistribute(spacing=5){ // cyl(d=10/PI, h=5, chamfer=0, @@ -3406,17 +3406,17 @@ function associate_vertices(polygons, split, curpoly=0) = // tx = texture(tex, [n=], [inset=], [gap=], [roughness=]); // Description: // Given a texture name, returns a texture. Textures can come in two varieties: -// - Heightfield textures which are 2D arrays of scalars. These are usually faster to render, but can be less precise and prone to triangulation errors. The table below gives the recommended style for the best triangulation. If results are still incorrect, switch to the similar VNF tile by adding the "_vnf" suffix. +// - Heightfield textures, which are 2D arrays of scalars. These are usually faster to render, but can be less precise and prone to triangulation errors. The table below gives the recommended style for the best triangulation. If results are still incorrect, switch to the similar VNF tile by adding the "_vnf" suffix. // - VNF Tile textures, which are VNFs that cover the unit square [0,0] x [1,1]. These tend to be slower to render, but allow greater flexibility and precision for shapes that don't align with a grid. // . // In the descriptions below, imagine the textures positioned on the XY plane, so "horizontal" refers to the "sideways" dimensions of the texture and -// "up" and "down" refer to the depth dimension, perpendicular to the surface being textured. If a texture is placed on a cylinder the "depth" will become the radial direction and the "horizontal" -// direction will be the vertical and tangential directions on the cylindrical surface. All horizontal dimensions for VNF textures are relative to the unit square -// on which the textures are defined, so a value of 0.25 for a gap or border will refer to 1/4 of the texture's full length and/or width. All supported textures appear below in the examples. +// "up" and "down" refer to the depth dimension, perpendicular to the surface being textured. If a texture is placed on a cylinder, the "depth" becomes the radial direction and "horizontal" +// refers to the vertical and tangential directions on the cylindrical surface. All horizontal dimensions for VNF textures are relative to the unit square +// on which the textures are defined, so a value of 0.25 for a gap or border refers to 1/4 of the texture's full length and/or width. All supported textures appear below in the examples. // Arguments: // tex = The name of the texture to get. // --- -// n = The number of samples to use for defining a heightfield texture. Depending on the texture, result will be either n x n or 1 x n. Not allowed for VNF textures. See the `tex_samples` argument to {{cyl()}}, {{linear_sweep()}} and {{rotate_sweep()}} for controlling the sampling of VNF textures. +// n = The number of samples to use for defining a heightfield texture. Depending on the texture, result will be either n×n or 1×n. Not allowed for VNF textures. See the `tex_samples` argument to {{cyl()}}, {{linear_sweep()}} and {{rotate_sweep()}} for controlling the sampling of VNF textures. // border = The size of a border region on some VNF tile textures. Generally between 0 and 0.5. // gap = The gap between logically distinct parts of some VNF tiles. (ie: gap between bricks, gap between truncated ribs, etc.) // roughness = The amount of roughness used on the surface of some heightfield textures. Generally between 0 and 0.5. @@ -3426,7 +3426,7 @@ function associate_vertices(polygons, split, curpoly=0) = // rect(30), texture=tex, h=30, // tex_depth=1/2, tex_size=[10,10] // ); -// Example(3D): **"bricks_vnf"** (VNF) = VNF version of "bricks". Giving `gap=` sets the "mortar" gap between adjacent bricks, default 0.05. Giving `border=` specifies that the top face of the brick is smaller than the bottom of the brick by `border` on each of the four sides. If `gap` is zero then a `border` value close to 0.5 will cause bricks to come to a sharp pointed edge, with just a tiny flat top surface. Note that `gap+border` must be strictly smaller than 0.5. Default is `border=0.05`. +// Example(3D): **"bricks_vnf"** (VNF) = VNF version of "bricks". Giving `gap=` sets the "mortar" gap between adjacent bricks, default 0.05. Giving `border=` specifies that the top face of the brick is smaller than the bottom of the brick by `border` on each of the four sides. If `gap` is zero then a `border` value close to 0.5 causes the bricks to come to a sharp pointed edge, with just a tiny flat top surface. Note that `gap+border` must be strictly smaller than 0.5. Default is `border=0.05`. // tex = texture("bricks_vnf"); // linear_sweep( // rect(30), texture=tex, h=30, @@ -4051,7 +4051,7 @@ function texture(tex, n, border, gap, roughness, inset) = /// Arguments: /// region = The [[Region|regions.scad]] 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 vertical surfaces. See {{texture()}} for what named textures are supported. -/// tex_size = An optional 2D target size for the textures. Actual texture sizes will be 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. Default: `[5,5]` /// h / l = The height to extrude/sweep the path. /// --- /// counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. @@ -4065,7 +4065,7 @@ function texture(tex, n, border, gap, roughness, inset) = /// samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 /// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` /// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -/// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` +/// orient = Vector to rotate top toward, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` /// Named Anchors: /// "centroid_top" = The centroid of the top of the shape, oriented UP. /// "centroid" = The centroid of the center of the shape, oriented UP. @@ -4381,13 +4381,13 @@ 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 will be 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. Default: `[5,5]` /// 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` /// rot = If true, rotates the texture 90º. /// shift = [X,Y] amount to translate the top, relative to the bottom. Default: [0,0] -/// closed = If false, and shape is given as a path, then the revolved path will be sealed to the axis of rotation with untextured caps. Default: `true` +/// closed = If false, and shape is given as a path, then the revolved path are sealed to the axis of rotation with untextured caps. Default: `true` /// taper = If given, and `closed=false`, tapers the texture height to zero over the first and last given percentage of the path. If given as a lookup table with indices between 0 and 100, uses the percentage lookup table to ramp the texture heights. Default: `undef` (no taper) /// angle = The number of degrees counter-clockwise from X+ to revolve around the Z axis. Default: `360` /// style = The triangulation style used. See {{vnf_vertex_array()}} for valid styles. Used only with heightfield type textures. Default: `"min_edge"` @@ -4395,7 +4395,7 @@ function _tile_edge_path_list(vnf, axis, maxopen=1) = /// samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 /// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` /// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -/// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` +/// orient = Vector to rotate top toward, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` /// Anchor Types: /// "hull" = Anchors to the virtual convex hull of the shape. /// "intersect" = Anchors to the surface of the shape. @@ -4701,11 +4701,11 @@ function _textured_point_array(points, texture, tex_reps, tex_size, tex_samples, ptsize=[len(points[0]), len(points)], tex_reps = is_def(tex_reps) ? tex_reps : let( - tex_size = is_undef(tex_sizes) ? [5,5] : force_list(tex_size,2), + tex_size = is_undef(tex_size) ? [5,5] : 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)) ) - [round(xsize/tex_size.x), round(ysize/tex_size.y)], + [max(1,round(xsize/tex_size.x)), max(1,round(ysize/tex_size.y))], normals = default(normals,surfnormals(points, col_wrap=col_wrap, row_wrap=row_wrap)), getscale = function(x,y) (x+y)/2 ) @@ -4873,4 +4873,4 @@ function surfnormals(data, col_wrap=false, row_wrap=false) = -// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap +// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap \ No newline at end of file diff --git a/vnf.scad b/vnf.scad index 67435ce6..d1dd946f 100644 --- a/vnf.scad +++ b/vnf.scad @@ -3,9 +3,9 @@ // The Vertices'N'Faces structure (VNF) holds the data used by polyhedron() to construct objects: a vertex // list and a list of faces. This library makes it easier to construct polyhedra by providing // functions to construct, merge, and modify VNF data, while avoiding common pitfalls such as -// reversed faces. It can find faults in your polyhedrons. Note that this file is for low level manipulation +// reversed faces. It can find faults in your polyhedrons. This file is for low level manipulation // of lists of vertices and faces: it can perform some simple transformations on VNF structures -// but cannot perform boolean operations on the polyhedrons represented by VNFs. +// but *cannot* perform boolean operations on the polyhedrons represented by VNFs. // Includes: // include // FileGroup: Advanced Modeling @@ -51,24 +51,20 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces. // . // You can apply a texture to the vertex array VNF using the usual texture parameters. // See [Texturing](skin.scad#section-texturing) for more details on how textures work. -// The top left corner of the texture tile will be aligned with `points[0][0]`, and the the X and Y directions correspond to `points[y][x]`. +// The top left corner of the texture tile is aligned with `points[0][0]`, and the the X and Y directions correspond to `points[y][x]`. // In practice, it is probably easiest to observe the result and apply a suitable texture tile rotation by setting `tex_rot` if the result // is not what you wanted. The reference scale of your point data is also taken from the square at the [0][0] corner. This determines -// the meaning of `tex_size` and it also affects the vertical texture scale. The size of the texture tiles will be proportional to the point -// spacing of the location where they are placed, so if the points are closer together, you will get small texture elements. The `tex_depth` you -// specify will be correct at the `points[0][0]` but will be different at places in the point array where the scale is different. Note that this -// differs from {{rotate_sweep()}} which uses a uniform resampling of the curve you specify. +// the meaning of `tex_size` and it also affects the vertical texture scale. The size of the texture tiles is proportional to the point +// spacing of the location where they are placed, so if the points are closer together, you get small texture elements. The specified `tex_depth` +// is correct at the `points[0][0]` but would be different at places in the point array where the scale is different. This +// differs from {{rotate_sweep()}}, which uses a uniform resampling of the curve you specify. // . // The point data for `vnf_vertex_array()` is resampled using bilinear interpolation to match the required point density of the tile count, but the // sampling is based on the grid, not on the distance between points. If you want to // avoid resampling, match the point data to the required point number for your tile count. For height field textures this means // the number of data points must equal the tile count times the number of entries in the tile minus `tex_skip` plus `tex_extra`. // Note that `tex_extra` defaults to 1 along dimensions that are not wrapped. For a VNF tile you need to have the the point -// count equal to the tile count times tex_samples, plus one if wrapping is disabled. -// . -// 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 -// using the `normals` parameter. +// count equal to the tile count times tex_samples, plus one if wrapping is disabled. // Arguments: // points = A list of vertices to divide into columns and rows. // --- @@ -82,7 +78,7 @@ 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 for the textures at `points[0][0]`. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]` +// 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. Default: `[5,5]` // 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 @@ -93,7 +89,6 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces. // sidecaps = if `col_wrap==false` this controls whether to cap any floating ends of a VNF tile on the texture. Does not affect the main texture surface. Ignored it doesn't apply. Default: false // sidecap1 = set sidecap only for the `points[][0]` edge of the output // sidecap2 = set sidecap only for the `points[][max]` edge of the output -// normals = array of normal vectors to each point in the point array for more accurate texture height calculation // 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"` // spin = (module) Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` @@ -206,6 +201,78 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces. // apply(m, [ [rgroove[0].x,0,-z], each rgroove, [last(rgroove).x,0,-z] ]) // ], caps=true, col_wrap=true, reverse=true); // vnf_polyhedron(vnf, convexity=8); +// Example(3D,Med,NoAxes,VPD=300): When applying a texture to a vertex array, remember that the density of the texture follows the density of the vertex array grid. Here is a sheet with a wrinkle in both x and y directions, using location data generated by {{smooth_path()}}. The bezier curves have non-uniformly distributed spline points, indicated by the red dots along each edge. This results in a non-uniform distribution of the texture tiling. +// include +// +// xprofile = smooth_path([[0,0,0], [25,0,0], [49,0,-10], [51,0,10], [75,0,0], [100,0,0]], +// relsize=1, method="corners", splinesteps=4); +// yprofile = smooth_path([[0,0,0], [0,25,0], [0,49,-10], [0,51,10], [0,75,0], [0,100,0]], +// relsize=1, method="corners", splinesteps=4); +// polystack = [ +// for(xp=xprofile) [ +// for(yp=yprofile) [xp.x, yp.y, xp.z+yp.z] +// ] +// ]; +// vnf_vertex_array(polystack, texture="checkers", tex_depth=2, tex_reps=[8,8]); +// color("red") { +// for(p=xprofile) translate(p-[0,1,0]) sphere(1.5); +// for(p=yprofile) translate(p-[1,0,0]) sphere(1.5); +// } +// Example(3D,Med,NoAxes,VPD=300): By passing the spline curves into {{resample_path()}}, we can get a uniform distribution of the same number of x and y profile points, as shown by the red dots. This results in a uniform distribution of the texture tiling. +// include +// +// xprof = smooth_path([[0,0,0], [25,0,0], [49,0,-10], [51,0,10], [75,0,0], [100,0,0]], +// relsize=1, method="corners", splinesteps=4); +// yprof = smooth_path([[0,0,0], [0,25,0], [0,49,-10], [0,51,10], [0,75,0], [0,100,0]], +// relsize=1, method="corners", splinesteps=4); +// xprofile = resample_path(xprof, len(xprof), closed=false); +// yprofile = resample_path(yprof, len(yprof), closed=false); +// polystack = [ +// for(xp=xprofile) [ +// for(yp=yprofile) [xp.x, yp.y, xp.z+yp.z] +// ] +// ]; +// vnf_vertex_array(polystack, texture="checkers", tex_depth=2, tex_reps=[8,8]); +// color("red") { +// for(p=xprofile) translate(p-[0,4,0]) sphere(1.5); +// for(p=yprofile) translate(p-[4,0,0]) sphere(1.5); +// } +// Example(3D,NoAxes,VPR=[66,0,24],VPD=285,VPT=[0,2,44]): This is a vase shape that cannot be constructed with rotational or linear sweeps. Using a vertex array to create a stack of polygons is the most practical way to make this and many other shapes. The cross-section is a rounded 9-pointed star that changes size and rotates back and forth as it rises in the z direction. +// include +// +// vprofile = +// smooth_path([[25,0], [35,8], [45,20], [40,40], [25,50], [30,65], [32,70], [37,80]], +// relsize=1, method="corners"); +// ridgepd = 20; // z period of star point wiggle +// ridgeamp = 5; // amplitude of star point wiggle +// polystack = [ +// for(p=vprofile) let(r=p.x, z=p.y) +// path3d( +// smooth_path( +// zrot(ridgeamp*sin(360*z/ridgepd), p=star(11, or=r+ridgeamp, ir=r-ridgeamp)), +// relsize=0.6, splinesteps=5, method="corners", closed=true), +// z) +// ]; +// vnf_polyhedron(vnf_vertex_array(polystack, col_wrap=true, caps=true)); +// Example(3D,NoAxes,VPR=[66,0,24],VPD=285,VPT=[0,2,44]): The previous vase shape with a pebbly texture, simply by adding `texture="dots"` to the `vnf_vertex_array()` call. Because textures are spread over grid units and not measurement units, the data points in the polygon stack should be more or less uniformly spaced. Each star-shaped cross-section has uniformly-spaced points, but the vertical profile `vprofile` in the previous example isn't uniform because the control points aren't evenly spaced. We fix this by passing this profile into `resample_path()`, which results in a uniform texture density. +// include +// +// vprofile = resample_path( +// smooth_path([[25,0], [35,8], [45,20], [40,40], [25,50], [30,65], [32,70], [37,80]], +// relsize=1, method="corners"), +// 81, closed=false); +// ridgepd = 20; // z period of star point wiggle +// ridgeamp = 5; // amplitude of star point wiggle +// polystack = [ +// for(p=vprofile) let(r=p.x, z=p.y) +// path3d( +// smooth_path( +// zrot(ridgeamp*sin(360*z/ridgepd), p=star(11, or=r+ridgeamp, ir=r-ridgeamp)), +// relsize=0.6, splinesteps=5, method="corners", closed=true), +// z) +// ]; +// vnf_polyhedron(vnf_vertex_array(polystack, col_wrap=true, caps=true, +// texture="dots", tex_samples=1)); module vnf_vertex_array( points, @@ -223,8 +290,8 @@ module vnf_vertex_array( col_wrap=col_wrap, row_wrap=row_wrap, reverse=reverse, style=style,triangulate=triangulate, 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, sidecaps=sidecaps,sidecap1=sidecap1,sidecap2=sidecap2 - ); - vnf_polyhedron(vnf, convexity=convexity, cp="centroid", anchor="origin", spin=0, orient=UP, atype="hull") children(); + ); + vnf_polyhedron(vnf, convexity=convexity, cp=cp, anchor=anchor, spin=spin, orient=orient, atype=atype) children(); } @@ -237,7 +304,7 @@ function vnf_vertex_array( style="default", triangulate = false, texture, tex_reps, tex_size, tex_samples, tex_inset=false, tex_rot=0, - tex_depth=1, tex_extra, tex_skip, sidecaps,sidecap1,sidecap2, normals + tex_depth=1, tex_extra, tex_skip, sidecaps,sidecap1,sidecap2 ) = 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") @@ -247,7 +314,7 @@ function vnf_vertex_array( _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, 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,triangulate=triangulate) : assert(!(any([caps,cap1,cap2]) && !col_wrap), "col_wrap must be true if caps are requested (without texture)") assert(!(any([caps,cap1,cap2]) && row_wrap), "Cannot combine caps with row_wrap (without texture)") @@ -344,7 +411,7 @@ function vnf_vertex_array( ) triangulate? vnf_triangulate(vnf) : vnf; -// Function: vnf_tri_array() +// Function&Module: vnf_tri_array() // Synopsis: Returns a VNF from an array of points. The array need not be rectangular. // SynTags: VNF // Topics: VNF Generators, Lists @@ -367,6 +434,17 @@ function vnf_vertex_array( // col_wrap = If true, add faces to connect the last column to the first. // row_wrap = If true, add faces to connect the last row to the first. // reverse = If true, reverse all face normals. +// convexity = (module) Max number of times a line could intersect a wall of the shape. +// 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"` +// spin = (module) Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` +// orient = (module) Vector to rotate top toward, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` +// atype = (module) Select "hull" or "intersect" anchor type. Default: "hull" +// Anchor Types: +// "hull" = Anchors to the virtual convex hull of the shape. +// "intersect" = Anchors to the surface of the shape. +// Named Anchors: +// "origin" = Anchor at the origin, oriented UP. // Example(3D,NoAxes): Each row has one more point than the preceeding one. // pts = [for(y=[1:1:10]) [for(x=[0:y-1]) [x,y,y]]]; // vnf = vnf_tri_array(pts); @@ -415,6 +493,21 @@ function vnf_vertex_array( // vnf_polyhedron(vnf); // cylinder(30, d=8); // } + +module vnf_tri_array( + points, + caps, cap1, cap2, + col_wrap=false, + row_wrap=false, + reverse=false, + convexity=2, cp="centroid", anchor="origin", spin=0, orient=UP, atype="hull") +{ + vnf = vnf_tri_array(points=points, caps=caps, cap1=cap2, cap2=cap2, + col_wrap=col_wrap, row_wrap=row_wrap, reverse=reverse + ); + vnf_polyhedron(vnf, convexity=convexity, cp=cp, anchor=anchor, spin=spin, orient=orient, atype=atype) children(); +} + function vnf_tri_array( points, caps, cap1, cap2, @@ -525,7 +618,7 @@ function _lofttri(p1, p2, i1offset, i2offset, n1, n2, reverse=false, trilist=[], // duplicated. It is valid to repeat points in a VNF, but if you // with to remove the duplicates that occur along joined edges, use {{vnf_merge_points()}}. // . -// Note that this is a tool for manipulating polyhedron data. It is for +// This is a tool for manipulating polyhedron data. It is for // building up a full polyhedron from partial polyhedra. // It is *not* a union operator for VNFs. The VNFs to be joined must not intersect each other, // except at edges, otherwise the result is an invalid polyhedron. Also, the @@ -535,8 +628,8 @@ function _lofttri(p1, p2, i1offset, i2offset, n1, n2, reverse=false, trilist=[], // has no holes, no intersecting faces or edges, and obeys all the requirements // that CGAL expects. // . -// For example, if you combine two pyramids to try to make an octahedron, the result will -// be invalid because of the two internal faces created by the pyramid bases. A valid +// For example, if you combine two pyramids to try to make an octahedron, the result is +// invalid because of the two internal faces created by the pyramid bases. A valid // use would be to build a cube missing one face and a pyramid missing its base and // then join them into a cube with a point. // Arguments: @@ -957,9 +1050,8 @@ function vnf_merge_points(vnf,eps=EPSILON) = // Usage: // clean_vnf = vnf_drop_unused_points(vnf); // Description: -// Remove all unreferenced vertices from a VNF. Note that in most -// cases unreferenced vertices cause no harm, and this function may -// be slow on large VNFs. +// Remove all unreferenced vertices from a VNF. In most cases, unreferenced vertices cause no harm, +// and this function may be slow on large VNFs. function vnf_drop_unused_points(vnf) = let( flat = flatten(vnf[1]), @@ -1382,7 +1474,7 @@ function _vnf_centroid(vnf,eps=EPSILON) = // actual object. The returned list has the form `[[MINX, MINY, MINZ], [MAXX, MAXY, MAXZ]]`. // Arguments: // vnf = vnf to get the bounds of -// fast = if true then ignore face data and process all vertices; if false only look at vertices actually used in the geometry. Default: false +// fast = if true then ignore face data and process all vertices; if false, look only at vertices actually used in the geometry. Default: false // Example: // echo(vnf_bounds(cube([2,3,4],center=true))); // Displays [[-1, -1.5, -2], [1, 1.5, 2]] function vnf_bounds(vnf,fast=false) = @@ -1402,8 +1494,8 @@ function vnf_bounds(vnf,fast=false) = // region = projection(vnf, [cut]); // Description: // When `cut=false`, which is the default, projects the input VNF -// onto the XY plane, returning a region. Note that as currently implemented, this operation -// involves the 2D union of all the projected faces and can be very +// onto the XY plane, returning a region. As currently implemented, this operation +// involves the 2D union of all the projected faces and can be // slow if the VNF has many faces. Minimize the face count of the VNF for best performance. // . // When `cut=true`, returns the intersection of the VNF with the @@ -1469,7 +1561,7 @@ function projection(vnf,cut=false,eps=EPSILON) = // plane = [A,B,C,D], taking the side where the normal [A,B,C] points: Ax+By+Cz≥D. // If closed is set to false then the cut face is not included in the vnf. This could // allow further extension of the vnf by join with other vnfs using {{vnf_join()}}. -// Note that if your given VNF has holes (missing faces) or is not a complete polyhedron +// If your given VNF has holes (missing faces) or is not a complete polyhedron // then closed=true is may produce invalid results when it tries to construct closing faces // on the cut plane. Set closed=false for such inputs. // . @@ -1479,7 +1571,7 @@ function projection(vnf,cut=false,eps=EPSILON) = // This makes it possible to construct mating shapes, e.g. with {{skin()}} or {{vnf_vertex_array()}} that // can be combined using {{vnf_join()}} to make a valid polyhedron. // . -// Note that the input to vnf_halfspace() does not need to be a closed, manifold polyhedron. +// The input to vnf_halfspace() does not need to be a closed, manifold polyhedron. // Because it adds the faces on the cut surface, you can use vnf_halfspace() to cap off an open shape if you // slice through a region that excludes all of the gaps in the input VNF. // Arguments: @@ -1669,7 +1761,7 @@ function _triangulate_planar_convex_polygons(polys) = // Description: // Bend a VNF around the X, Y or Z axis, splitting up faces as necessary. Returns the bent // VNF. For bending around the Z axis the input VNF must not cross the Y=0 plane. For bending -// around the X or Y axes the VNF must not cross the Z=0 plane. Note that if you wrap a VNF all the way around +// around the X or Y axes the VNF must not cross the Z=0 plane. If you wrap a VNF all the way around // it may intersect itself, which produces an invalid polyhedron. It is your responsibility to // avoid this situation. The 1:1 // radius is where the curved length of the bent VNF matches the length of the original VNF. If the @@ -1784,7 +1876,7 @@ function vnf_bend(vnf,r,d,axis="Z") = // Description: // Given a VNF or a list of 3d points, compute the convex hull // and return it as a VNF. This differs from {{hull()}} and {{hull3d_faces()}}, which -// return just the face list referenced to the input point list. Note that the returned +// return just the face list referenced to the input point list. The returned // point list contains all the points that are actually used in the input // VNF, which may be many more points than are needed to represent the convex hull. // This is not usually a problem, but you can run the somewhat slow {{vnf_drop_unused_points()}} @@ -1937,7 +2029,7 @@ function vnf_boundary(vnf,merge=true,idx=false) = // vnf_polyhedron(vnf); // vnf_polyhedron(vnf_small_offset(vnf,18)); // } -// Example(3D): The polyhedron on the left is enlarged to match the size of the offset polyhedron on the right. Note that the offset does **not** preserve coplanarity of faces. This is because the vertices all move independently, so nothing constrains faces to remain coplanar. +// Example(3D): The polyhedron on the left is enlarged to match the size of the offset polyhedron on the right. The offset does **not** preserve coplanarity of faces. This is because the vertices all move independently, so nothing constrains faces to remain coplanar. // include // vnf = regular_polyhedron_info("vnf","pentagonal icositetrahedron",d=25); // xdistribute(spacing=300){ @@ -1977,7 +2069,7 @@ function vnf_small_offset(vnf, delta, merge=true) = // Topics: VNF Manipulation // See Also: vnf_small_offset(), vnf_boundary(), vnf_merge_points() // Usage: -// newvnf = vnf_sheet(vnf, thickness, [style=], [merge=]); +// newvnf = vnf_sheet(vnf, thickness, [style=], [merge=], [balanced=]); // Description: // Constructs a thin sheet from a vnf by offsetting the vnf along the normal vectors estimated at // each vertex by averaging the normals of the adjacent faces. This is done using {{vnf_small_offset()}. @@ -1989,8 +2081,8 @@ function vnf_small_offset(vnf, delta, merge=true) = // Once the offset to the original VNF is computed the original and offset VNF are connected by filling // in the boundary strip(s) between them // . -// When thickness is positive, the given bezier patch is extended toward its "inside", which is the -// side that appears purple in the "thrown together" view. Note that this is the opposite direction +// When thickness is positive, the given surface is extended toward its "inside", which is the +// side that appears purple in the "thrown together" view. This is the opposite direction // of {{vnf_small_offset()}}. Extending toward the inside means that your original VNF remains unchanged // in the output. You can extend the patch in the other direction // using a negative thickness value. When you extend to the outside with a negative thickness, your VNF needs to have all @@ -2006,14 +2098,38 @@ function vnf_small_offset(vnf, delta, merge=true) = // --- // style = {{vnf_vertex_array()}} style to use. Default: "default" // merge = if false then do not run {{vnf_merge_points()}}. Default: true -// Example(3D): -// pts = [for(x=[30:5:180]) [for(y=[-6:0.5:6]) [7*y,x, sin(x)*y^2]]]; +// balanced = if true, then offset the input surface by half the specified thickness on each side. This increases execution time because two offsets must be performed. The sign of `thickness` does not matter. Default: false +// Example(3D): In this example, the top of the surface is "interior", so a negative thickness extends that side upward. +// pts = [ +// for(x=[30:5:180]) [ +// for(y=[-6:0.5:6]) +// [7*y,x, sin(x)*y^2] +// ] +// ]; // vnf=vnf_vertex_array(pts); // vnf_polyhedron(vnf_sheet(vnf,-10)); +// Example(3D): Same as previous example, but with `balanced=true` to offset both sides of the surface equally by half the specified thickness. The output is shown transparent with the original surface inside. +// pts = [ +// for(x=[30:5:180]) [ +// for(y=[-6:0.5:6]) +// [7*y,x, sin(x)*y^2] +// ] +// ]; +// vnf=vnf_vertex_array(pts); +// vnf_polyhedron(vnf); +// %vnf_polyhedron(vnf_sheet(vnf,-10, +// merge=false, balanced=true)); // Example(3D): This example has multiple holes -// pts = [for(x=[-10:2:10]) [ for(y=[-10:2:10]) [x,1.4*y,(-abs(x)^3+y^3)/250]]]; +// pts = [ +// for(x=[-10:2:10]) [ +// for(y=[-10:2:10]) +// [x,1.4*y,(-abs(x)^3+y^3)/250] +// ] +// ]; // vnf = vnf_vertex_array(pts); -// newface = list_remove(vnf[1], [43,42,63,88,108,109,135,134,129,155,156,164,165]); +// newface = list_remove(vnf[1], +// [43,42,63,88,108,109,135, +// 134,129,155,156,164,165]); // newvnf = [vnf[0],newface]; // vnf_polyhedron(vnf_sheet(newvnf,2)); // Example(3D): When applied to a sphere the sheet is constructed inward, so the object appears unchanged, but cutting it in half reveals that we have changed the sphere into a shell. @@ -2021,14 +2137,15 @@ function vnf_small_offset(vnf, delta, merge=true) = // left_half() // vnf_polyhedron(vnf_sheet(vnf,15)); -function vnf_sheet(vnf, thickness, style="default", merge=true) = +function vnf_sheet(vnf, thickness, style="default", merge=true, balanced=false) = let( vnf = merge ? vnf_merge_points(vnf) : vnf, - offset = vnf_small_offset(vnf, -thickness, merge=false), - boundary = vnf_boundary(vnf,merge=false,idx=true), - newvnf = vnf_join([vnf, - vnf_reverse_faces(offset), - for(p=boundary) vnf_vertex_array([select(offset[0],p),select(vnf[0],p)],col_wrap=true,style=style) + offset0 = balanced ? vnf_small_offset(vnf, thickness/2, merge=false) : vnf, + offset1 = vnf_small_offset(vnf, balanced ? -thickness/2 : -thickness, merge=false), + boundary = vnf_boundary(offset0,merge=false,idx=true), + newvnf = vnf_join([offset0, + vnf_reverse_faces(offset1), + for(p=boundary) vnf_vertex_array([select(offset1[0],p),select(offset0[0],p)],col_wrap=true,style=style) ]) ) thickness < 0 ? vnf_reverse_faces(newvnf) : newvnf;