Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Richard Milewski
2025-04-09 13:25:07 -07:00
4 changed files with 420 additions and 120 deletions

View File

@@ -3908,6 +3908,8 @@ function _find_anchor(anchor, geom)=
: len(facevecs)==1? unit(facevecs[0],UP)
: len(facevecs)==2? vector_bisect(facevecs[0],facevecs[1])
: _three_edge_corner_dir(facevecs,[FWD,LEFT])*anch.z,
edgedir = len(facevecs)!=2 ? undef
: rot(from=UP,to=axis,p=unit(cross(facevecs[0], facevecs[1]))),
edgeang = len(facevecs)==2 ? 180-vector_angle(facevecs[0], facevecs[1]) : undef,
edgelen = anch.z==0 ? norm(edge)
: anch.z>0 ? abs([size2.y,size2.x]*axy)
@@ -3932,15 +3934,18 @@ function _find_anchor(anchor, geom)=
final_dir = default(override[1],anch==CENTER?UP:rot(from=UP, to=axis, p=dir)),
final_pos = default(override[0],rot(from=UP, to=axis, p=pos)),
// If the anchor is on a face or horizontal edge we take the oang value for spin
// If the anchor is on a vertical or sloped edge or corner we want to align the spin to point upward along the edge
// If the anchor is an edge anchor and not horizontal we point spin UP
// If the anchor is horizontal edge we point spin clockwise:
// cross product of UP with the edge direction will point OUT if we are on top and edge direction
// is correct. We check if it points out by comparing to the final_dir which points out at that edge,
// with a correction for top/bottom (anchor.z).
// Otherwise use the standard BACK/UP definition
// The precomputed oang value seems to be wrong, at least when axis!=UP
// Set "vertical" edge and corner anchors point along the edge
spin = anch.x!=0 && anch.y!=0 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=edge))
// Horizontal anchors point clockwise
: anch.z!=0 && sum(v_abs(anch))==2 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=anch.z*[anch.y,-anch.x,0]))
: norm(anch)==3 ? _compute_spin(final_dir, final_dir==DOWN || final_dir==UP ? BACK : UP)
: oang // face anchors point UP/BACK
spin = is_def(edgedir) && !approx(edgedir.z,0) ? _compute_spin(final_dir, edgedir * (edgedir*UP>0?1:-1))
: is_def(edgedir) ? _compute_spin(final_dir,
edgedir * (approx(unit(cross(UP,edgedir)),unit([final_dir.x,final_dir.y,0])*anchor.z) ? 1 : -1))
: _compute_spin(final_dir, final_dir==DOWN || final_dir==UP ? BACK : UP)
) [anchor, final_pos, final_dir, default(override[2],spin),
if (is_def(edgeang)) [["edge_angle",edgeang],["edge_length",edgelen], ["vec", endvecs]]]
) : type == "conoid"? ( //r1, r2, l, shift, axis

View File

@@ -1764,10 +1764,12 @@ function debug_tetra(r) = let(size=r/norm([1,1,1])) [
// For metaballs with flat surfaces or sides, avoid letting any side of the bounding box coincide with one
// of these flat surfaces or sides, otherwise unpredictable triangulation around the edge may result.
// .
// **Parameter `isovalue`:** The `isovalue` parameter applies globally to **all** your metaballs and changes the appearance of your
// entire metaball object, possibly dramatically. It defaults to 1 and you don't usually need to change
// it. If you increase the isovalue, then all the objects in your model shrink, causing some melded
// objects to separate. If you decrease it, each metaball grows and melds more with others.
// **Parameter `isovalue`:** The `isovalue` parameter applies globally to **all** your metaballs and changes
// the appearance of your entire metaball object, possibly dramatically. It defaults to 1 and you don't usually
// need to change it. If you increase the isovalue, then all the objects in your model shrink, causing some melded
// objects to separate. If you decrease it, each metaball grows and melds more with others. As with `isosurface()`,
// a range may be specified for isovalue, which can result in hollow metaballs, although this isn't particularly
// useful except possibly in 2D.
// .
// ***Metaballs debug view***
// .
@@ -1951,7 +1953,7 @@ function debug_tetra(r) = let(size=r/norm([1,1,1])) [
// voxel_size = Size of the voxels used to sample the bounding box volume, can be a scalar or 3-vector, or omitted if `voxel_count` is set. You may get a non-cubical voxels of a slightly different size than requested if `exact_bounds=true`.
// ---
// voxel_count = Approximate number of voxels in the bounding box. If `exact_bounds=true` then the voxels may not be cubes. Use with `show_stats=true` to see the corresponding voxel size. Default: 10000 (if `voxel_size` not set)
// isovalue = A scalar value specifying the isosurface value (threshold value) of the metaballs. At the default value of 1.0, the internal metaball functions are designd so the size arguments correspond to the size parameter (such as radius) of the metaball, when rendered in isolation with no other metaballs. Default: 1.0
// isovalue = A scalar value specifying the isosurface value (threshold value) of the metaballs. At the default value of 1.0, the internal metaball functions are designd so the size arguments correspond to the size parameter (such as radius) of the metaball, when rendered in isolation with no other metaballs. You can also specify an isovalue range such as `[1,1.1]`, which creates hollow metaballs, where the hollow is evident when clipped by the bounding box. A scalar isovalue is equivalent to the range `[isovalue,INF]`. Default: 1.0
// closed = When true, close the surface if it intersects the bounding box by adding a closing face. When false, do not add a closing face, possibly producing non-manfold metaballs with holes where the bounding box intersects them. Default: true
// exact_bounds = When true, shrinks voxels as needed to fit whole voxels inside the requested bounding box. When false, enlarges `bounding_box` as needed to fit whole voxels of `voxel_size`, and centers the new bounding box over the requested box. Default: false
// show_stats = If true, display statistics about the metaball isosurface in the console window. Besides the number of voxels that the surface passes through, and the number of triangles making up the surface, this is useful for getting information about a possibly smaller bounding box to improve speed for subsequent renders. Enabling this parameter has a small speed penalty. Default: false
@@ -2867,7 +2869,7 @@ function mb_ring(r1,r2, cutoff=INF, influence=1, negative=false, hide_debug=fals
// pixel_size = Size of the pixels used to sample the bounding box area, can be a scalar or 2-vector, or omitted if `pixel_count` is set. You may get a non-square pixels of a slightly different size than requested if `exact_bounds=true`.
// ---
// pixel_count = Approximate number of pixels in the bounding box. If `exact_bounds=true` then the pixels may not be squares. Use with `show_stats=true` to see the corresponding pixel size. Default: 1024 (if `pixel_size` not set)
// isovalue = A scalar value specifying the isosurface value (threshold value) of the metaballs. At the default value of 1.0, the internal metaball functions are designd so the size arguments correspond to the size parameter (such as radius) of the metaball, when rendered in isolation with no other metaballs. Default: 1.0
// isovalue = A scalar value specifying the isosurface value (threshold value) of the metaballs. At the default value of 1.0, the internal metaball functions are designd so the size arguments correspond to the size parameter (such as radius) of the metaball, when rendered in isolation with no other metaballs. You can also specify a range for isovalue, such as `[1,1.1]` in which case the metaball is displayed as a shell with the hollow inside corresponding to the higher isovalue. A scalar isovalue is equivalent to the vector `[isovalue,INF]`. Default: 1.0
// closed = (Function only) When true, close the path if it intersects the bounding box by adding a closing side. When false, do not add a closing side. Default: true, and always true when called as a module.
// use_centers = When true, uses the center value of each pixel as an additional data point to refine the contour path through the pixel. Default: false
// smoothing = Number of times to apply a 2-point moving average to the contours. This can remove small zig-zag artifacts resulting from a contour that follows the profile of a triangulated 3D surface when `use_centers` is set. Default: 2 if `use_centers=true`, 0 otherwise.
@@ -3674,7 +3676,16 @@ function _showstats_isosurface(voxsize, bbox, isoval, cubes, triangles, faces) =
// a=4; b=4.1;
// f = function(x,y) (x^2+y^2)^2 - 2*a^2*(x^2-y^2) + a^4;
// contour(f,bounding_box=[[-6,-3],[6,3]], isovalue=[-INF,b^4]);
// Example(2D,NoAxes,VPD=65,VPT=[-7,0,0]): A contour of a function that looks like the contour should intersect itself at the origin, but if you zoom in, you see that it doesn't actually cross or intersect. It is theoretically possible to obtain a crossing path with `contour()` although the algorithm attempts to avoid it, primarily by disallowing the function values at the sample points to be equal to the specified isovalue.
// g = function(x,y)
// let(
// theta=atan2(y,x),
// r = norm([x,y])
// )
// r*sin(3*theta-theta^2/20+40*r);
// contour(g, bounding_box=[[-23,-13],[9,13]],
// isovalue=[0,INF], pixel_size=0.2);
module contour(f, isovalue, bounding_box, pixel_size, pixel_count=undef, use_centers=true, smoothing=undef, exact_bounds=false, cp="centroid", anchor="origin", spin=0, atype="hull", show_stats=false, show_box=false, _mball=false) {
pathlist = contour(f, isovalue, bounding_box, pixel_size, pixel_count, use_centers, smoothing, true, exact_bounds, show_stats, _mball);
assert(len(pathlist)>0, "\nNo contour lines found! Cannot generate polygon. Check your isovalue.")

View File

@@ -20,7 +20,7 @@ use <builtins.scad>
// Function&Module: cube()
// Synopsis: Creates a cube with anchors for attaching children.
// SynTags: Geom, VNF, Ext
// Topics: Shapes (3D), Attachable, VNF Generators
// Topics: Shapes (3D), Attachable, VNF Generators, Textures
// See Also: cuboid(), prismoid()
// Usage: As Module (as in native OpenSCAD)
// cube(size, [center]);
@@ -790,48 +790,6 @@ function prismoid(
: reorient(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift, p=vnf);
// Function&Module: octahedron()
// Synopsis: Creates an octahedron with axis-aligned points.
// SynTags: Geom, VNF
// Topics: Shapes (3D), Attachable, VNF Generators
// See Also: prismoid()
// Usage: As Module
// octahedron(size, ...) [ATTACHMENTS];
// Usage: As Function
// vnf = octahedron(size, ...);
// Description:
// When called as a module, creates an octahedron with axis-aligned points.
// When called as a function, creates a [VNF](vnf.scad) of an octahedron with axis-aligned points.
// Arguments:
// size = Width of the octahedron, tip to tip. Can be a 3-vector. Default: [1,1,1]
// ---
// 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`
// Example:
// octahedron(size=40);
// Example: Anchors
// octahedron(size=40) show_anchors();
// Example:
// octahedron([10,15,25]);
module octahedron(size=1, anchor=CENTER, spin=0, orient=UP) {
vnf = octahedron(size=size);
attachable(anchor,spin,orient, vnf=vnf, extent=true) {
vnf_polyhedron(vnf, convexity=2);
children();
}
}
function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
let(
s = force_list(size,3)/2,
dummy=assert(is_vector(s,3) && all_positive(s), "\nsize must be a positive scalar or 3-vector"),
vnf = [
[ [0,0,s.z], [s.x,0,0], [0,s.y,0], [-s.x,0,0], [0,-s.y,0], [0,0,-s.z] ],
[ [0,2,1], [0,3,2], [0,4,3], [0,1,4], [5,1,2], [5,2,3], [5,3,4], [5,4,1] ]
]
) reorient(anchor,spin,orient, vnf=vnf, extent=true, p=vnf);
// Function&Module: regular_prism()
// Synopsis: Creates a regular prism with roundovers and chamfering
@@ -1191,6 +1149,303 @@ function regular_prism(n,
: final_vnf;
// Function&Module: textured_tile()
// Synopsis: Creates a cube or trapezoidal prism with a textured top face for attaching to objects.
// SynTags: Geom, VNF
// Topics: Shapes (3D), Attachable, VNF Generators
// See Also: cuboid(), prismoid(), texture(), cyl(), rotate_sweep(), linear_sweep()
// Usage:
// textured_tile(texture, [size], [w1=], [w2=], [ang=], [shift=], [h=/height=/thickness=], [atype=], [diff=], [extra=], [skip=], ...) [ATTACHMENTS];
// vnf = textured_tile(texture, [size], [w1=], [w2=], [ang=], [shift=], [h=/height=/thickness=], [atype=], [extra=], [skip=], ...);
// Description:
// Creates a cuboid or trapezoidal prism and places a texture on the top face. You can specify the size by giving a `size` scalar or vector as is
// usual for a cube. If you give a scalar, however, it applies only to the X and Y dimensions: the default is to create a thin tile, not a cube.
// The Z size specifies the size of the shape **not** including the applied texture (in the same way that other textured objects work).
// If you omit the Z value then for regular textures, the default thickness will be 0.1 which provides a thin backing layer. A zero thickness
// base layer can produce invalid geometry when the texture contacts the bottom layer, so some non-zero base is necessary. If you use a positive `inset`
// then the texture actually sinks into its base, so the default is set to the 0.1 more than the inset depth. To ensure a valid geometry, with a positive
// `inset` or a texture that has negative values you must select a thickness strictly **larger** than the depth the texture extends below zero.
// .
// You can also specify a trapzoidal prism using parameters equivalent to those accepted by {{trapezoid()}}, with one change:
// `ysize` specifies the width of the prism in the Y direction, and `h`, `height` or `thickness` are used to specify the height
// in the Z direction. When you texture a trapezoid the texture will be scaled to the `w1` length if you specify it by size using `tex_size`. The
// scaling transformation that maps the texture onto the trapezoid is not linear and will result in curvature of your texture.
// .
// Two anchor types are available. The default atype is "tex" which assumes you want to place the texture on another object using
// {{attach()}}. It provides anchors that ignore the base object and place the BOTTOM anchor at the bottom of the texture. The TOP anchor
// will be at the top face of the texture. Note that if your texture doesn't span the range from [0,1] these anchors won't be correctly located.
// For an inset texture, the "tex" anchors are all at the top of the texture. This anchor type works with `anchor(face,BOT)` where `face` is some
// face on a parent object that needs a texture. If you want to use the textured object directly the "std" anchors are probably more useful.
// These anchors are the usual anchors for the base object, ignoring the applied texture. If you want the anchors to be on top of the texture,
// set `tex_inset=true`.
// .
// To aid in the application of inset anchors into parent objects with the module form, you can set `diff=true`, which causes the module
// to create a "remove" tagged cuboid or prism to carve out space for the texture so that inset textures are cut into the parent object.
// The texture itself is given a "keep" tag. For this to work you must specify {{diff()}} above the parent; if you don't do that, the
// tags will be ignored and the tile will appear as a solid object with no texture visible. The cutout object extends 0.1 units above the surface
// of the texture to prevent problems with exactly aligned faces. The cutout does not extend out beyond the sides, so if the parent shape
// has the exact same dimensions as the texture tile, you will have exactly aligned faces along the edges.
// .
// Most of the heightfield textures are designed to repeat in a way that requires one extra line of the texture to complete the pattern.
// The `extra` parameter specifies the number of extra lines to repeat at the end of the texture and it defaults to 1 because most textures
// do requires this extra line. If you need to disable this feature you can set the `extra` parameter to 0, or you can set it to a list of two
// booleans to control the extra line of texture in the X and Y directions independently. The extra parameter
// is ignored for VNF textures. A heightfield texture may also have extra margin along a starting side that makes the texture unbalanced. You can
// removed this using the `skip` parameter, which defaults to zero and similarly specifies the number of lines to skip in the X and Y directions at
// the starting edges of the tile. You must have enough tile repetitions to accomodate the specified skip.
// Anchor Types:
// "tex" = Anchors around the texture, ignoring the base object. (default)
// "std" = Standard object anchors that ignore any applied texture.
// Arguments:
// 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.
// size = The size the object when a cube is desired, a scalar, 2-vector or 3-vector. If you give a scalar or 2-vector the default height is 0.1 or 0.1 more than the inset depth
// ---
// ysize = The Y axis length of the trapezoidal prism
// w1 = The X axis width of the front end of the trapezoidal prism.
// w2 = The X axis width of the back end of the trapezoidal prism
// ang = Specify the front angle(s) of the trapezoidal prism. Can give a scalar for an isosceles trapezoidal prism or a list of two angles, the left angle and right angle. You must omit one of `h`, `w1`, or `w2` to allow the freedom to control the angles.
// shift = Scalar value to shift the back of the trapezoidal prism along the X axis by. Cannot be combined with ang. Default: 0
// h / height / thickness = The thickness in the Z direction of the base that the texture sits on. Default: 0.1 or for inset textures 0.1 more than the inset depth
// tex_size = An optional 2D target size for the textures. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[1,1]`
// tex_reps = If given instead of tex_size, a 2-vector giving the number of texture tile repetitions in the horizontal and vertical directions.
// tex_inset = If numeric, lowers the texture into the surface by the specified proportion, e.g. 0.5 would lower it half way into the surface. If `true`, insets by exactly its full depth. Default: `false`
// tex_rot = Rotate texture by specified angle, which must be a multiple of 90 degrees. Default: 0
// tex_depth = Specify texture depth; if negative, invert the texture. Default: 1.
// diff = if set to true then "remove" and "keep" tags are set to cut out a space for the texture so that inset textures can be attached. Default: false
// extra = number of extra lines of a hightfield texture to add at the end. Can be a scalar or 2-vector to give x and y values. Default: 1
// 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
// style = {{vnf_vertex_array()}} style used to triangulate heightfield textures. Default: "min_edge"
// 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`
// Example(3D,NoScale,VPT=[-0.257402,0.467403,-0.648606],VPR=[46.6,0,16.6],VPD=29.2405): Basic textured tile
// textured_tile("trunc_diamonds", 10, tex_reps=[5,5]);
// Example(3D,NoScale,VPT=[-0.0852782,0.259593,0.139667],VPR=[58.5,0,345.1],VPD=36.0994): Attaching a tile to a cube
// cuboid([12,12,4]) attach(TOP,BOT)
// textured_tile("trunc_pyramids", 10, tex_reps=[5,5], style="convex");
// Example(3D,NoScale,VPT = [-0.0788193, 0.10015, -0.0938629], VPR = [57.8, 0, 34.1], VPD = 29.2405): This inset texture doesn't look obviously different, but you can see that the object is below the XY plane.
// textured_tile("trunc_pyramids_vnf", 10, tex_reps=[5,5], tex_inset=true);
// Example(3D,NoAxes,VPT=[0.242444,0.170054,-0.0714754],VPR=[67.6,0,33.4],VPD=36.0994): Here we use the `diff` option combined with {{diff()}} to attach the inset texture to the front of a parent cuboid.
// diff()
// cuboid([12,5,10]) attach(FRONT, BOT)
// textured_tile("trunc_pyramids_vnf", [10,8],
// tex_reps=[5,5], tex_inset=true, diff=true);
// Example(3D,NoAxes,VPT=[5.86588,-0.107082,-0.311155],VPR=[17.2,0,9.6],VPD=32.4895): Tile shaped like a rhombic prism
// textured_tile("ribs", w1=10, w2=10, shift=4,ysize=7);
// Example(3D,NoAxes,VPT=[-0.487417,-0.398897,-0.143258],VPR=[10.2,0,12.4],VPD=26.3165): A tile shaped like a trapezoidal prism. Note that trapezoidal tiles will always distort the texture, resulting in curves
// textured_tile("diamonds", w1=10, w2=7, ysize=7) show_anchors(2);
// Example(3D,NoAxes,VPT=[-0.0889877,-0.31974,0.554444],VPR=[22.1,0,22.2],VPD=32.4895): An inset trapezoidal tile placed into a cube
// diff()cuboid([10,10,2])
// attach(TOP,BOT)
// textured_tile("trunc_diamonds", tex_reps=[5,5], tex_inset=true,
// w1=8, w2=4, ysize=8, diff=true);
// Example(3D,NoScales,VPT=[-0.0889877,-0.31974,0.554444],VPR=[58.5,0,21.5],VPD=32.4895): This example shows what happens if you set `extra` to zero for the "pyramids" texture. Note that the texture doesn't finish. The default of `extra=1` produces the correct result.
// textured_tile("pyramids", 10, tex_reps=[5,5], extra=0);
// Example(3D,NoScales,VPT=[-0.212176,-0.651766,0.124004],VPR=[58.5,0,21.5],VPD=29.2405): This texture has an asymmetry even with the default `extra=1`.
// textured_tile("trunc_ribs", 10, tex_reps=[5,2]);
// Example(3D,NoScales,VPT=[-0.212176,-0.651766,0.124004],VPR=[58.5,0,21.5],VPD=29.2405): It could be fixed by setting `extra=2`, which would place an extra flat strip on the right. But another option is to use the `skip` parameter to trim the flat part from the left. Note that we are also skipping in the y direction, but it doesn't make a difference for this texture, except that you need to have enough texture tiles to accommodate the skip. You can also set `skip` to a vector.
// textured_tile("trunc_ribs", 10, tex_reps=[5,2], skip=1);
// This is like _tile_edge_path_list in that it finds the paths
// on the x=0 or y=0 face of a VNF tile. But instead of returning
// paths it turns them into a VNF. The open path is closed to the specified
// z coordinate.
function _tile_edge_vnf(vnf, axis, z, maxopen=1) =
let(
verts = vnf[0],
faces = vnf[1],
segs = [for(face=faces, edge=pair(select(verts,face),wrap=true)) if (approx(edge[0][axis],0) && approx(edge[1][axis],0)) [edge[1],edge[0]]],
paths = _assemble_partial_paths(segs),
facelist = [
for(path=paths)
if (!(len(path)<=3 && path[0]==last(path)))
path[0]==len(path) ? [list_unwrap(path),[count(len(path)-1)]]
: [
[
point3d(point2d(path[0]),z),
each path,
point3d(point2d(last(path)),z)
],
[count(len(path)+2)]
]
],
openlist = [for(entry=facelist) if (!are_ends_equal(entry[0])) 1]
)
assert(len(openlist)<=maxopen, str("VNF has ",len(openlist)," open paths on an edge and at most ",maxopen," is supported."))
vnf_join(facelist);
module textured_tile(
texture,
size,
ysize, height, w1, w2, ang, h, shift,
tex_size=[1,1],
tex_reps,
tex_inset=false,
tex_rot=0,
tex_depth=1,
diff=false,
extra=1,
skip=0,
style="min_edge",
atype="tex",
anchor=CENTER, spin=0, orient=UP
)
{
vnf_data = textured_tile(size=size,
ysize=ysize, height=height, w1=w1, w2=w2, ang=ang, h=h, shift=shift,
texture=texture, tex_size=tex_size, tex_reps=tex_reps,extra=extra,
tex_inset=tex_inset, tex_rot=tex_rot, tex_depth=tex_depth,skip=skip,
style=style, atype="std",_return_anchor=true);
h_w1_w2_shift = vnf_data[2];
is_trap = is_def(h_w1_w2_shift);
ysize=h_w1_w2_shift[0];
w1=h_w1_w2_shift[1];
w2=h_w1_w2_shift[2];
shift=h_w1_w2_shift[3];
size = vnf_data[1];
inset = is_num(tex_inset)? tex_inset : tex_inset? 1 : 0;
extra_ht = max(0,abs(tex_depth)*(1-inset));
anch_ht = atype=="tex" ? extra_ht : size.z;
geom = is_def(h_w1_w2_shift) ? atype=="std" ? attach_geom(axis=BACK, size=[w1,anch_ht,ysize],size2=[w2,anch_ht],shift=[-shift,0])
: attach_geom(axis=BACK, size=[w1,anch_ht,ysize],size2=[w2,anch_ht],shift=[-shift,0])
: attach_geom(size=[size.x,size.y,anch_ht]);
attachable(anchor=anchor,orient=orient, spin=spin, geom=geom, expose_tags=true){
down(atype=="tex" ? size.z/2+extra_ht/2 : 0)
if (diff) {
tag("keep") vnf_polyhedron(vnf_data[0]);
tag("remove")up(.05)
if (!is_trap) cuboid([size.x,size.y,size.z+0.1]);
else linear_sweep(trapezoid(w1=w1,w2=w2,h=ysize,shift=shift), h=size.z+0.1,center=true);
}
else vnf_polyhedron(vnf_data[0]);
children();
}
}
function textured_tile(
texture,
size,
ysize, height, w1, w2, ang, h, shift, thickness,
tex_size=[1,1],
tex_reps,
tex_inset=false,
tex_rot=0,
tex_depth=1,
style="min_edge",
atype="tex",
extra=1,
skip=0,
anchor=CENTER, spin=0, orient=UP,
_return_anchor=false
) =
assert(tex_reps==undef || is_vector(tex_reps,2))
assert(tex_size==undef || is_vector(tex_size,2))
assert(in_list(tex_rot,[0,90,180,270]))
assert(is_undef(size) || is_num(size) || is_vector(size,2) || is_vector(size,3), "size must be a 2-vector or 3-vector")
assert(is_undef(size) || num_defined([ysize,h, height, thickness, w1,w2,ang])==0, "Cannot combine size with any other dimensional specifications")
let(
extra = is_list(extra) ? extra : [extra,extra],
skip = is_list(skip) ? skip : [skip,skip],
inset = is_num(tex_inset)? tex_inset : tex_inset? 1 : 0,
default_thick = inset>0 ? 0.1+abs(tex_depth)*inset : 0.1,
extra_ht = max(0,abs(tex_depth)*(1-inset)),
h_w1_w2_shift = is_def(size) ? undef
: assert(is_undef(ysize) || is_finite(ysize))
assert(is_undef(w1) || is_finite(w1))
assert(is_undef(w2) || is_finite(w2))
assert(is_undef(ang) || is_finite(ang) || is_vector(ang,2))
assert(num_defined([ysize, w1, w2, ang]) == 3, "Must give exactly 3 of the arguments ysize, w1, w2, and angle.")
assert(is_undef(shift) || is_finite(shift))
assert(num_defined([shift,ang])<2, "Cannot specify shift and ang together")
_trapezoid_dims(ysize,w1,w2,shift,ang),
ysize=h_w1_w2_shift[0],
w1=h_w1_w2_shift[1],
w2=h_w1_w2_shift[2],
shift=h_w1_w2_shift[3],
height = is_def(size) ? default(size.z,default_thick) : one_defined([h,height,thickness],"h,height,thickness",dflt=default_thick),
size = is_def(size) ? is_num(size) ? [size,size,1] : point3d(size,1) // We only use the x and y components of size
: [w1,ysize],
tex = is_string(texture)? texture(texture,$fn=_tex_fn_default()) : texture,
texture = tex_rot==0? tex
: is_vnf(tex)? zrot(tex_rot, cp=[1/2,1/2], p=tex)
: tex_rot==180? reverse([for (row=tex) reverse(row)])
: tex_rot==270? [for (row=transpose(tex)) reverse(row)]
: reverse(transpose(tex)),
check_tex = _validate_texture(texture),
tex_reps = is_def(tex_reps) ? tex_reps
: [round(size.x/tex_size.x), round(size.y/tex_size.y)],
scale = [size.x/tex_reps.x, size.y/tex_reps.y],
vnf = !is_vnf(texture) ?
let(
texsteps = [len(texture[0]), len(texture)],
xn=tex_reps.x*texsteps.x+extra.x-skip.x,
yn=tex_reps.y*texsteps.y+extra.y-skip.y,
checks = assert(yn>=2, "Skipped too many points in the y direction: decrease skip.y")
assert(xn>=2, "Skipped too many points in the x direction: decrease skip.x"),
xpts=lerpn(-size.x/2,size.x/2,xn),
ypts=lerpn(size.y/2,-size.y/2,yn),
scaled_tex = tex_depth < 0 ? [for(row=texture) [for(p=row) -(1-p-inset)*tex_depth]]
: [for(row=texture) [for(p=row) (p-inset)*tex_depth]],
check = [for(row=scaled_tex, p=row) if (p<=-height) p],
dummy=assert(check==[], str("texture extends too far below zero (",min([each check,0]),") to fit in cube with height ",height)),
pts=[for(y=idx(ypts))
[ [xpts[0],ypts[y],-height/2],
for(x=idx(xpts))
[xpts[x],ypts[y], height/2 + scaled_tex[(y+skip.y)%texsteps.y][(x+skip.x)%texsteps.x]],
[last(xpts), ypts[y], -height/2]
]
]
)
vnf_vertex_array(pts,col_wrap=true,caps=true,style=style)
:
let(
zadj_vnf = [
[for(p=texture[0]) [p.x, p.y, tex_depth<0 ? height/2-(1-p.z-inset)*tex_depth : height/2+(p.z-inset)*tex_depth]],
texture[1]
],
scaled_vnf = scale(scale, zadj_vnf),
tiled_vnf = [for(i=[0:1:tex_reps.x-1], j=[0:1:tex_reps.y-1]) move([scale.x*i,scale.y*j], scaled_vnf)],
unscaled_hedge=_tile_edge_vnf(zadj_vnf,1,-height/2),
hedge = unscaled_hedge==EMPTY_VNF ? [] : xscale(scale.x, unscaled_hedge),
unscaled_vedge=_tile_edge_vnf(zadj_vnf,0,-height/2),
vedge = unscaled_vedge==EMPTY_VNF ? [] : yscale(scale.y, unscaled_vedge),
hedge_flip = hedge==[] ? hedge : back(size.y,vnf_reverse_faces(hedge)),
vedge_flip = vedge==[] ? vedge : right(size.x,vnf_reverse_faces(vedge)),
front_edge = hedge==[] ? [] : [for(i=[0:1:tex_reps.x-1]) xmove(scale.x*i, hedge)],
left_edge = vedge==[] ? [] : [for(j=[0:1:tex_reps.y-1]) ymove(scale.y*j, vedge)],
back_edge = hedge==[] ? [] : [for(i=[0:1:tex_reps.x-1]) xmove(scale.x*i, hedge_flip)],
right_edge = vedge==[] ? [] : [for(j=[0:1:tex_reps.y-1]) ymove(scale.y*j, vedge_flip)],
bottom = [path3d(rect(point2d(size),anchor=FWD+LEFT),-height/2), [[3,2,1,0]]],
result = vnf_join(concat(tiled_vnf, front_edge, left_edge, right_edge, back_edge, [bottom]))
)
move([-size.x/2,-size.y/2],result),
trans_vnf = is_undef(h_w1_w2_shift) ? vnf
: let(
newpts = [for(p=vnf[0])
let(factor=p.y/ysize+1/2)
[lerp(1,w2/w1,factor)*p.x+factor*shift, p.y, p.z]]
)
[newpts, vnf[1]],
anch_ht = atype=="tex" ? extra_ht : height,
shifted_vnf = down(atype=="tex" ? height/2+extra_ht/2 : 0, trans_vnf),
geom = is_def(h_w1_w2_shift) ? atype=="std" ? attach_geom(axis=BACK, size=[w1,anch_ht,ysize],size2=[w2,anch_ht],shift=[-shift,0])
: attach_geom(axis=BACK, size=[w1,anch_ht,ysize],size2=[w2,anch_ht],shift=[-shift,0])
: attach_geom(size=[size.x,size.y,anch_ht])
)
_return_anchor ? [reorient(anchor,spin,orient,geom=geom,p=shifted_vnf), [size.x,size.y,height],h_w1_w2_shift]
: reorient(anchor,spin,orient,geom=geom,p=shifted_vnf);
// Module: rect_tube()
// Synopsis: Creates a rectangular tube.
// SynTags: Geom
@@ -1561,6 +1816,52 @@ function wedge(size=[1,1,1], center, anchor, spin=0, orient=UP) =
reorient(anchor,spin,orient, size=size, anchors=anchors, p=vnf);
// Function&Module: octahedron()
// Synopsis: Creates an octahedron with axis-aligned points.
// SynTags: Geom, VNF
// Topics: Shapes (3D), Attachable, VNF Generators
// See Also: prismoid()
// Usage: As Module
// octahedron(size, ...) [ATTACHMENTS];
// Usage: As Function
// vnf = octahedron(size, ...);
// Description:
// When called as a module, creates an octahedron with axis-aligned points.
// When called as a function, creates a [VNF](vnf.scad) of an octahedron with axis-aligned points.
// Arguments:
// size = Width of the octahedron, tip to tip. Can be a 3-vector. Default: [1,1,1]
// ---
// 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`
// Example:
// octahedron(size=40);
// Example: Anchors
// octahedron(size=40) show_anchors();
// Example:
// octahedron([10,15,25]);
module octahedron(size=1, anchor=CENTER, spin=0, orient=UP) {
vnf = octahedron(size=size);
attachable(anchor,spin,orient, vnf=vnf, extent=true) {
vnf_polyhedron(vnf, convexity=2);
children();
}
}
function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
let(
s = force_list(size,3)/2,
dummy=assert(is_vector(s,3) && all_positive(s), "\nsize must be a positive scalar or 3-vector"),
vnf = [
[ [0,0,s.z], [s.x,0,0], [0,s.y,0], [-s.x,0,0], [0,-s.y,0], [0,0,-s.z] ],
[ [0,2,1], [0,3,2], [0,4,3], [0,1,4], [5,1,2], [5,2,3], [5,3,4], [5,4,1] ]
]
) reorient(anchor,spin,orient, vnf=vnf, extent=true, p=vnf);
// Section: Cylinders

107
skin.scad
View File

@@ -1563,6 +1563,13 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals
// 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
// 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.
// 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
@@ -2148,7 +2155,7 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0,
// spin = Rotate this many degrees around Z axis after anchor. Default: 0
// orient = Vector to rotate top towards 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"
// 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:
// "origin" = The native position of the shape.
// Anchor Types:
@@ -3157,7 +3164,7 @@ function associate_vertices(polygons, split, curpoly=0) =
// 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.
// Values in the height field should range from 0 to 1. A zero height
// 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.
// Figure(2D,Big,NoScales,VPT=[6.21418,0.242814,0],VPD=28.8248,VPR=[0,0,0]): Here is a 2d texture described by a "grid" that just contains a single row. Such a texture can be used to create ribbing. The texture is `[[0, 1, 1, 0]]`, and the fixture shows three repetitions of the basic texture unit.
@@ -3173,9 +3180,11 @@ function associate_vertices(polygons, split, curpoly=0) =
// Note that the grid is always uniformly spaced.
// By default textures are created with unit depth, meaning that the top surface
// of the texture is 1 unit above the surface being textured, assuming that the texture
// is correctly designed to span the range from 0 to 1. The `tex_depth` parameter can adjust
// this dimension of a texture without changing anything else, and setting `tex_depth` negative
// will invert a 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.
// 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.
// 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);
@@ -3263,15 +3272,15 @@ function associate_vertices(polygons, split, curpoly=0) =
// 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 defining the texture tile with 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
// 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.
// 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
// a collection of disconnected objects. Note that the Z coordinates of your tile can be anything, but for the dimensional settings on textures
// to work intuitively, you should construct your tile so that Z ranges from 0 to 1.
// a collection of disconnected objects. Note that the Z coordinates of your tile can be anything, but as with height fields, for the dimensional settings on textures
// to work intuitively, you should construct your tile so that Z ranges from 0 to 1. You can then use `tex_depth` to control the depth of the tile in use.
// Figure(3D): This is the "hexgrid" VNF tile, which creates a hexagonal grid texture, something which doesn't work well with a height field because the edges of the hexagon don't align with the grid. Note how the tile ranges between 0 and 1 in both X, Y and Z. In fact, to get a proper aspect ratio in your final texture you need to use the `tex_size` parameter to introduct a sqrt(3) scale factor.
// tex = texture("hex_grid");
// vnf_polyhedron(tex);
@@ -3333,11 +3342,11 @@ function associate_vertices(polygons, split, curpoly=0) =
// 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.
// Example(3D): **"bricks"** (Heightfield) = A brick-wall pattern. Giving `n=` sets the number of heightfield samples to `n x n`. Default: 24. Giving `roughness=` adds a level of height randomization to add roughness to the texture. Default: 0.05. Use `style="convex"`.
// Example(3D): **"bricks"** (Heightfield) = A brick-wall pattern. Giving `n=` sets the number of heightfield samples to `n` by `n`. Default: 24. Giving `roughness=` creates a rough surface texture to the top brick faces by randomizing the brick height to a band of the specified height (relative to the tile range of 0 to 1), so with the default of 0.1 it means the top level varies randomly in [0.9,1]. Default: 0.1. Use `style="convex"`.
// tex = texture("bricks");
// linear_sweep(
// rect(30), texture=tex, h=30,
// tex_size=[10,10]
// 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`.
// tex = texture("bricks_vnf");
@@ -3405,17 +3414,17 @@ function associate_vertices(polygons, split, curpoly=0) =
// rect(30), texture=tex, h=30,
// tex_size=[10,10]
// );
// Example(3D): **"dimples"** (VNF) = Round divots. Specify `$fn` to set the number of segments on the dimples (will be rounded to a multiple of 4). The default is `$fn=16`. Note that `$fa` and `$fs` are ignored, since the scale of the texture is unknown at the time of definition. Giving `border=` specifies the horizontal width of the flat border region between the tile edges and the edge of the dimple. Must be nonnegative and strictly less than 0.5. Default: 0.05.
// tex = texture("dimples", $fn=16);
// linear_sweep(
// rect(30), texture=tex, h=30,
// tex_size=[10,10]
// );
// Example(3D): **"dots"** (VNF) = Raised round bumps. Specify `$fn` to set the number of segments on the dots (will be rounded to a multiple of 4). The default is `$fn=16`. Note that `$fa` and `$fs` are ignored, since the scale of the texture is unknown at the time of definition. Giving `border=` specifies the horizontal width of the flat border region between the tile edge and the edge of the dots. Must be nonnegative and strictly less than 0.5. Default: 0.05.
// tex = texture("dots", $fn=16);
// linear_sweep(
// rect(30), texture=tex, h=30,
// tex_size=[10,10]
// tex_depth=2, tex_size=[10,10]
// );
// Example(3D): "dots" (VNF) = You can use the "dots" texture to create dimples (which used to exist as a separate texture) by specifying `tex_inset` and a negative `tex_depth`, which inverts the texture.
// tex = texture("dots", $fn=16);
// linear_sweep(
// rect(30), texture=tex, h=30, tex_depth=-2,
// tex_inset=1, tex_size=[10,10]
// );
// Example(3D): **"hex_grid"** (VNF) = A hexagonal grid defined by V-grove borders. Giving `border=` specifies that the top face of the hexagon is smaller than the bottom by `border` on the left and right sides. This means the V-groove top width for grooves running parallel to the Y axis will be double the border value. If the texture is scaled in the Y direction by sqrt(3) then the groove will be uniform on all six sides of the hexagon. Border must be strictly between 0 and 0.5, default: 0.1.
// tex = texture("hex_grid");
@@ -3471,10 +3480,10 @@ function associate_vertices(polygons, split, curpoly=0) =
// rect(30), texture=tex, h=30, tex_depth=3,
// tex_size=[10,10], style="concave"
// );
// Example(3D): **"rough"** (Heightfield) = A pseudo-randomized rough texture. Giving `n=` sets the number of heightfield samples to `n` by `n`. Default: 32. The `roughness=` parameter specifies the height of the random texture. Default: 0.2.
// Example(3D): **"rough"** (Heightfield) = A pseudo-randomized rough texture. Giving `n=` sets the number of heightfield samples to `n` by `n`. Default: 32. The texture is filled with random values ranging from 0 to 1. To control the height of the random texture use the `tex_depth` parameter.
// tex = texture("rough");
// linear_sweep(
// rect(30), texture=tex, h=30,
// rect(30), texture=tex, h=30, tex_depth=0.2,
// tex_size=[10,10], style="min_edge"
// );
// Example(3D): **"tri_grid"** (VNF) = A triangular grid defined by V-groove borders Giving `border=` specifies that the top face of the triangular surface is smaller than the bottom by `border` along the horizontal edges (parallel to the X axis). This means the V-groove top width of the grooves parallel to the X axis will be double the border value. (The other grooves are wider.) If the tile is scaled in the Y direction by sqrt(3) then the groove will be uniform on the three sides of the triangle. The border must be strictly between 0 and 1/6, default: 0.05.
@@ -3590,8 +3599,7 @@ function texture(tex, n, border, gap, roughness, inset) =
assert(is_undef(n), str(tex,__vnf_no_n_mesg))
let(
border = default(border,1/4)*2,
gap = default(gap,1/4),
f=echo(gap, border, gap+border, gap+2*border)
gap = default(gap,1/4)
)
assert(all_nonnegative([border,gap]), "trunc_ribs_vnf texture requires gap>=0 and border>=0")
assert(gap+border <= 1, "trunc_ribs_vnf texture requires that gap+2*border<=1")
@@ -3703,14 +3711,15 @@ function texture(tex, n, border, gap, roughness, inset) =
assert(num_defined([gap,border])==0, "bricks texture does not accept gap or border")
let(
n = quantup(default(n,24),2),
rough = default(roughness,0.05)
rough = default(roughness,0.1)
) [
for (y = [0:1:n-1])
rands(-rough/2, rough/2, n, seed=12345+y*678) + [
let(rand = rands(1-rough, 1, n, seed=12345+y*678))
[
for (x = [0:1:n-1])
(y%(n/2) <= max(1,n/16))? 0 :
let( even = floor(y/(n/2))%2? n/2 : 0 )
(x+even) % n <= max(1,n/16)? 0 : 0.5
(y%(n/2) <= max(1,n/16)) ? 0 :
let( even = floor(y/(n/2))%2 ? n/2 : 0 )
(x+even) % n <= max(1,n/16)? 0 : rand[x]
]
] :
tex=="bricks_vnf"?
@@ -3818,7 +3827,8 @@ function texture(tex, n, border, gap, roughness, inset) =
[4,5,6,7],
]
] :
tex=="dimples" || tex=="dots" ?
tex=="dimples" ? assert(false, "The dimples texture has been removed; use \"dots\" with 'tex_inset=1' and negative 'tex_depth' instead.") 0 :
tex=="dots" ?
assert(is_undef(n),str("To set number of segments on ",tex," use $fn. ", tex,__vnf_no_n_mesg))
assert(num_defined([gap,roughness])==0, str(tex," texture does not accept gap or roughness"))
let(
@@ -3829,16 +3839,13 @@ function texture(tex, n, border, gap, roughness, inset) =
let(
rows=ceil(n/4),
r=adj_ang_to_hyp(1/2-border,45),
dots = tex=="dots",
cp = [1/2, 1/2, r*sin(45)*(dots?-1:1)],
dots = true,
cp = [1/2, 1/2, -r*sin(45)],
sc = 1 / (r - abs(cp.z)),
uverts = [
for (p=[0:1:rows-1], t=[0:360/n:359.999])
cp + (
dots? spherical_to_xyz(r, -t, 45-45*p/rows) :
spherical_to_xyz(r, -t, 135+45*p/rows)
),
cp + r * (dots?UP:DOWN),
cp + spherical_to_xyz(r, -t, 45-45*p/rows),
cp + r * UP,
each border>0 ? path3d(subdivide_path(square(1),refine=2,closed=true))
: path3d(square(1)),
@@ -3933,12 +3940,12 @@ function texture(tex, n, border, gap, roughness, inset) =
] :
tex=="rough"?
assert(num_defined([gap,border])==0, str(tex," texture does not accept gap or border"))
assert(num_defined([roughness])==0, str(tex," texture no longer accepts 'roughness'. Use tex_depth to control roughness (0.2 was the old default)"))
let(
n = default(n,32),
rough = default(roughness, 0.2)
n = default(n,32)
) [
for (y = [0:1:n-1])
rands(0, rough, n, seed=123456+29*y)
rands(0, 1, n, seed=123456+29*y)
] :
assert(false, str("Unrecognized texture name: ", tex));
@@ -4278,30 +4285,6 @@ function _tile_edge_path_list(vnf, axis, maxopen=1) =
function _find_vnf_tile_edge_path(vnf, val) =
let(
verts = vnf[0],
fragments = [
for(edge = _get_vnf_tile_edges(vnf))
let(v0 = verts[edge[0]], v1 = verts[edge[1]])
if (approx(v0.y, val) && approx(v1.y, val))
v0.x <= v1.x? [[v0.x,v0.z], [v1.x,v1.z]] :
[[v1.x,v1.z], [v0.x,v0.z]]
],
sfrags = sort(fragments, idx=[0,1]),
rpath = _assemble_a_path_from_fragments(sfrags)[0],
opath = rpath==[]? []
: rpath[0].x > last(rpath).x ? reverse(rpath)
: rpath
) opath;
/// Function&Module: _textured_revolution()
/// Usage: As Function
/// vnf = _textured_revolution(shape, texture, tex_size, [tex_scale=], ...);