diff --git a/bottlecaps.scad b/bottlecaps.scad index 8e398f90..a3cb99e2 100644 --- a/bottlecaps.scad +++ b/bottlecaps.scad @@ -198,9 +198,9 @@ module pco1810_cap(h, r, d, wall, texture="none", anchor=BOTTOM, spin=0, orient= difference() { union() { if (texture == "knurled") { - cyl(d=w, h=hh, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT); + cyl(d=w, h=hh, texture="diamonds", tex_size=[3,3], style="concave", anchor=BOT); } else if (texture == "ribbed") { - cyl(d=w, h=hh, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT); + cyl(d=w, h=hh, texture="ribs", tex_size=[3,3], style="min_edge", anchor=BOT); } else { cyl(d=w, l=hh, anchor=BOTTOM); } @@ -387,9 +387,9 @@ module pco1881_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP) difference() { union() { if (texture == "knurled") { - cyl(d=w, h=11.2+wall, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT); + cyl(d=w, h=11.2+wall, texture="diamonds", tex_size=[3,3], style="concave", anchor=BOT); } else if (texture == "ribbed") { - cyl(d=w, h=11.2+wall, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT); + cyl(d=w, h=11.2+wall, texture="ribs", tex_size=[3,3], style="min_edge", anchor=BOT); } else { cyl(d=w, l=11.2+wall, anchor=BOTTOM); } @@ -610,9 +610,9 @@ module generic_bottle_cap( // thickness so the wall+texture are the specified wall thickness. That // seems wrong so this does specified thickness+texture if (texture == "knurled") - cyl(d=w + 1.5*diamMagMult, l=h, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT); + cyl(d=w + 1.5*diamMagMult, l=h, texture="diamonds", tex_size=[3,3], style="concave", anchor=BOT); else if (texture == "ribbed") - cyl(d=w + 1.5*diamMagMult, l=h, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT); + cyl(d=w + 1.5*diamMagMult, l=h, texture="ribs", tex_size=[3,3], style="min_edge", anchor=BOT); else cyl(d = w, l = h, anchor = BOTTOM); } @@ -1314,9 +1314,9 @@ module sp_cap(diam,type,wall,style="L",top_adj=0, bot_adj=0, texture="none", anc difference(){ up(wall){ if (texture=="knurled") - cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,texture="trunc_pyramids", tex_size=[3,3], tex_style="convex"); + cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,texture="trunc_pyramids", tex_size=[3,3], style="convex"); else if (texture == "ribbed") - cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,chamfer2=.8,tex_taper=0,texture="trunc_ribs", tex_size=[3,3], tex_style="min_edge"); + cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,chamfer2=.8,tex_taper=0,texture="trunc_ribs", tex_size=[3,3], style="min_edge"); else cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,chamfer2=.8); } diff --git a/drawing.scad b/drawing.scad index 5a4d360d..f0d84e30 100644 --- a/drawing.scad +++ b/drawing.scad @@ -1050,7 +1050,7 @@ function _normal_segment(p1,p2) = // "left" | [angle] | Same as "turn" // "right" | [angle] | Same as "turn", -angle // "angle" | angle | Set the default turn angle. -// "setdir" | dir | Set turtle direction. The parameter `dir` can be an angle or a vector. +// "setdir" | dir | Set turtle direction. The parameter `dir` can be an angle or a vector. (A 3d vector with zero Z component is allowed.) // "length" | length | Change the turtle move distance to `length` // "scale" | factor | Multiply turtle move distance by `factor` // "addlength" | length | Add `length` to the turtle move distance @@ -1184,12 +1184,12 @@ function _turtle_command(command, parm, parm2, state, index) = needeither = ["setdir"], chvec = !in_list(command,needvec) || is_vector(parm,2), chnum = !in_list(command,neednum) || is_num(parm), - vec_or_num = !in_list(command,needeither) || (is_num(parm) || is_vector(parm,2)), + vec_or_num = !in_list(command,needeither) || (is_num(parm) || is_vector(parm,2) || (is_vector(parm,3)&&parm.z==0)), lastpt = last(state[path]) ) assert(chvec,str("\"",command,"\" requires a vector parameter at index ",index)) assert(chnum,str("\"",command,"\" requires a numeric parameter at index ",index)) - assert(vec_or_num,str("\"",command,"\" requires a vector or numeric parameter at index ",index)) + assert(vec_or_num,str("\"",command,"\" requires a 2-vector or numeric parameter at index ",index)) command=="move" ? list_set(state, path, concat(state[path],[default(parm,1)*state[step]+lastpt])) : command=="untilx" ? ( @@ -1219,7 +1219,7 @@ function _turtle_command(command, parm, parm2, state, index) = command=="angle" ? list_set(state, angle, parm) : command=="setdir" ? ( is_vector(parm) ? - list_set(state, step, norm(state[step]) * unit(parm)) : + list_set(state, step, norm(state[step]) * unit(point2d(parm))) : list_set(state, step, norm(state[step]) * [cos(parm),sin(parm)]) ) : command=="length" ? list_set(state, step, parm*unit(state[step])) : diff --git a/rounding.scad b/rounding.scad index 6ea92329..44a023a9 100644 --- a/rounding.scad +++ b/rounding.scad @@ -3475,6 +3475,35 @@ Access to the derivative smoothing parameter? // attach(RIGHT,"root") // join_prism(circle(r=8,$fn=32), // l=10, base="plane", fillet=4); +// Example(3D,NoScales,VPR=[47.3,0,14.5],VPT=[-2.8467,-2.05938,-10.6999],VPD=220): Two join_prism objects are placed on the parent cylinder using anchors, and then their descriptions are used to contruct a curved handle with a bezier. +// $fs=.5; $fa=4; +// vspace=25; +// bezlen=40; +// cylr=20; +// straightlen=10; +// circ = circle(r=6); +// cyl(r=cylr,h=50, rounding=3) +// let(cyl=parent()) +// down(vspace/2) +// attach(RIGHT+FWD, "root", spin=90) +// join_prism(circ, base="cyl", base_r=20, height=straightlen, fillet=4) +// let(base1=parent()) +// restore(cyl) +// up(vspace/2) +// attach(LEFT+FWD, "root", spin=90) +// join_prism(circ, base="cyl", base_r=20, height=straightlen, fillet=4) +// let(base2=parent()) +// let( +// avg_dir = desc_dir(base1,anchor=TOP)+desc_dir(base2,anchor=TOP), +// bez=[ +// desc_point(base1,anchor=TOP), +// desc_point(base1,anchor=TOP)+bezlen*desc_dir(base1,anchor=TOP), +// (cylr+straightlen+bezlen)*avg_dir, +// desc_point(base2,anchor=TOP)+bezlen*desc_dir(base2,anchor=TOP), +// desc_point(base2,anchor=TOP)] +// ) +// path_sweep(circ,bezier_curve(bez,40)); + module join_prism(polygon, base, base_r, base_d, base_T=IDENT, scale=1, prism_end_T=IDENT, short=false, length, l, height, h, @@ -4045,6 +4074,9 @@ function _prism_fillet_prism(name, basepoly, bot, top, d, k, N, overlap, uniform // n = number of facets to use for the fillets. Default: 15 // n1 = number of facets at object1 // n2 = number of facets at object2 +// fillet = fillet for both ends of the prism. Default: 0 +// fillet1 = fillet for the joint at object1 +// fillet2 = fillet for the joint at object2 // k = fillet curvature parameter for both ends. Default: 0.7 // k1 = fillet curvature parameter at object1 // k2 = fillet curvature parameter at object2 @@ -4513,8 +4545,8 @@ module prism_connector(profile, desc1, anchor1, desc2, anchor2, shift1=0, shift2 smooth_normals, smooth_normals1, smooth_normals2, debug=false, debug_pos=false) { - base_fillet = default(fillet1,fillet); - aux_fillet = default(fillet2,fillet); + base_fillet = first_defined([fillet1,fillet,0]); + aux_fillet = first_defined([fillet2,fillet,0]); base_overlap = first_defined([overlap1,overlap,1]); aux_overlap = first_defined([overlap2,overlap,1]); diff --git a/shapes3d.scad b/shapes3d.scad index d62152a7..0f1c6f87 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -819,7 +819,8 @@ function prismoid( // You can specify the size of the ends using diameter or radius measured either inside or outside. Alternatively // you can give the length of the side of the polygon. You can specify chamfers and roundings for the ends, but not // the vertical edges. See {{rounded_prism()}} for prisms with rounded vertical edges. You can also specify texture for the side -// faces, but note that texture is not compatible with any roundings or chamfers. +// faces, but note that texture is not compatible with any roundings or chamfers. +// See [Texturing](skin.scad#section-texturing) for more details on how textures work. // . // Anchors are based on the VNF of the prism. Especially for tapered or shifted prisms, this may give unexpected anchor positions, such as top side anchors // being located at the bottom of the shape, so confirm anchor positions before use. @@ -1155,10 +1156,12 @@ function regular_prism(n, // 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=], ...); +// textured_tile(texture, [size], [w1=], [w2=], [ang=], [shift=], [h=/height=/thickness=], [atype=], [diff=], [tex_extra=], [tex_skip=], ...) [ATTACHMENTS]; +// vnf = textured_tile(texture, [size], [w1=], [w2=], [ang=], [shift=], [h=/height=/thickness=], [atype=], [tex_extra=], [tex_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 +// Creates a cuboid or trapezoidal prism and places a texture on the top face. +// See [Texturing](skin.scad#section-texturing) for more details on how textures work. +// You can specify the size of the object 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 @@ -1187,11 +1190,11 @@ function regular_prism(n, // 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 +// The `tex_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 `tex_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 `tex_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 +// removed this using the `tex_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) @@ -1206,24 +1209,24 @@ function regular_prism(n, // 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_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_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 +// tex_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 +// 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 // 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 +// Example(3D,NoScales,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 +// Example(3D,NoAxes,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. +// Example(3D,NoScales,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() @@ -1233,18 +1236,18 @@ function regular_prism(n, // 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); +// textured_tile("diamonds", w1=10, w2=7, ysize=7); // 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); +// Example(3D,NoAxes,VPT=[-0.0889877,-0.31974,0.554444],VPR=[58.5,0,21.5],VPD=32.4895): This example shows what happens if you set `tex_extra` to zero for the "pyramids" texture. Note that the texture doesn't finish. The default of `tex_extra=1` produces the correct result. +// textured_tile("pyramids", 10, tex_reps=[5,5], tex_extra=0); +// Example(3D,NoAxes,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 `tex_extra=1`. +// textured_tile("trunc_ribs", 10, tex_reps=[5,1]); +// Example(3D,NoAxes,VPT=[-0.212176,-0.651766,0.124004],VPR=[58.5,0,21.5],VPD=29.2405): It could be fixed by setting `tex_extra=2`, which would place an extra flat strip on the right. But another option is to use the `tex_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, so we increased the Y reps value to 2. You can also set `tex_skip` to a vector. +// textured_tile("trunc_ribs", 10, tex_reps=[5,2], tex_skip=1); // This is like _tile_edge_path_list in that it finds the paths @@ -1287,8 +1290,8 @@ module textured_tile( tex_rot=0, tex_depth=1, diff=false, - extra=1, - skip=0, + tex_extra=1, + tex_skip=0, style="min_edge", atype="tex", anchor=CENTER, spin=0, orient=UP @@ -1297,8 +1300,8 @@ module textured_tile( 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, + texture=texture, tex_size=tex_size, tex_reps=tex_reps,tex_extra=tex_extra, + tex_inset=tex_inset, tex_rot=tex_rot, tex_depth=tex_depth,tex_skip=tex_skip, style=style, atype="std",_return_anchor=true); h_w1_w2_shift = vnf_data[2]; is_trap = is_def(h_w1_w2_shift); @@ -1333,15 +1336,15 @@ function textured_tile( texture, size, ysize, height, w1, w2, ang, h, shift, thickness, - tex_size=[1,1], + tex_size=[5,5], tex_reps, tex_inset=false, tex_rot=0, tex_depth=1, style="min_edge", atype="tex", - extra=1, - skip=0, + tex_extra=1, + tex_skip=0, anchor=CENTER, spin=0, orient=UP, _return_anchor=false ) = @@ -1352,8 +1355,8 @@ function textured_tile( 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], + extra = is_list(tex_extra) ? tex_extra : [tex_extra,tex_extra], + skip = is_list(tex_skip) ? tex_skip : [tex_skip,tex_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)), @@ -1373,14 +1376,9 @@ function textured_tile( 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), + + texture = _get_texture(texture, tex_rot), + 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], @@ -1989,8 +1987,10 @@ function cylinder(h, r1, r2, center, r, d, d1, d2, anchor, spin=0, orient=UP) = // the cylinder or cone's sloped side. The more specific parameters like chamfer1 or rounding2 override the more // general ones like chamfer or rounding, so if you specify `rounding=3, chamfer2=3` you will get a chamfer at the top and // rounding at the bottom. You can specify extra height at either end for use with difference(); the extra height is ignored by -// anchoring. +// anchoring. // . +// You can apply a texture to the cylinder using the usual texture parameters. +// See [Texturing](skin.scad#section-texturing) for more details on how textures work. // When creating a textured cylinder, the number of facets is determined by the sampling of the texture. Any `$fn`, `$fa` or `$fs` values in // effect are ignored. To create a textured prism with a specified number of flat facets use {{regular_prism()}}. Anchors for cylinders // appear on the ideal cylinder, not on actual discretized shape the module produces. For anchors on the shape surface, use {{regular_prism()}}. @@ -2293,8 +2293,8 @@ function cyl( extra, extra1, extra2, anchor, spin=0, orient=UP ) = - assert(num_defined([style,tex_style])<2, "In cyl() the 'tex_style' parameters has been replaced by 'style'. You cannot give both.") - assert(num_defined([tex_reps,tex_counts])<2, "In cyl() the 'tex_counts' parameters has been replaced by 'tex_reps'. You cannot give both.") + assert(num_defined([style,tex_style])<2, "In cyl() the 'tex_style' parameter has been replaced by 'style'. You cannot give both.") + assert(num_defined([tex_reps,tex_counts])<2, "In cyl() the 'tex_counts' parameter has been replaced by 'tex_reps'. You cannot give both.") assert(num_defined([tex_scale,tex_depth])<2, "In cyl() the 'tex_scale' parameter has been replaced by 'tex_depth'. You cannot give both.") let( style = is_def(tex_style)? echo("In cyl() the 'tex_style' parameter is deprecated and has been replaced by 'style'")tex_style @@ -2393,7 +2393,7 @@ module cyl( assert(num_defined([style,tex_style])<2, "In cyl() the 'tex_style' parameters has been replaced by 'style'. You cannot give both.") assert(num_defined([tex_reps,tex_counts])<2, "In cyl() the 'tex_counts' parameters has been replaced by 'tex_reps'. You cannot give both.") assert(num_defined([tex_scale,tex_depth])<2, "In cyl() the 'tex_scale' parameter has been replaced by 'tex_depth'. You cannot give both."); - style = is_def(tex_style)? echo("In cyl the 'tex_style()' parameters is deprecated and has been replaced by 'style'")tex_style + style = is_def(tex_style)? echo("In cyl() the 'tex_style' parameter is deprecated and has been replaced by 'style'")tex_style : default(style,"min_edge"); tex_reps = is_def(tex_counts)? echo("In cyl() the 'tex_counts' parameter is deprecated and has been replaced by 'tex_reps'")tex_counts : tex_reps; diff --git a/skin.scad b/skin.scad index 39cf1505..fae5a53d 100644 --- a/skin.scad +++ b/skin.scad @@ -651,7 +651,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close // linear_sweep(path, texture=tex, tex_size=[5,5], h=40); // Example: Textured with twist and scale. // linear_sweep(regular_ngon(n=3, d=50), -// texture="rough", h=100, tex_depth=2, +// texture="rough", h=100, tex_depth=.4, // tex_size=[20,20], style="min_edge", // convexity=10, scale=0.2, twist=120); // Example: As Function @@ -717,7 +717,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close // [24, 25, 35, 33], [33, 35, 34], [24, 33, 6, 31], [34, 35, 11, 7], // [35, 25, 32, 11], [30, 32, 25, 23]] // ]; -// front_half(y=3){ +// front_half(y=33){ // cyl(d=14.5,h=1,anchor=BOT,rounding=1/3,$fa=1,$fs=.5); // linear_sweep(circle(d=12), h=12, scale=1.3, texture=diag_weave_vnf, // tex_size=[5,5], convexity=12); @@ -1041,7 +1041,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: 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 half way 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], @@ -3150,7 +3150,7 @@ function associate_vertices(polygons, split, curpoly=0) = -// DefineHeader(Table;Headers=Texture Name|Type|Description): Texture Values + // 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. @@ -4016,7 +4016,6 @@ function _validate_texture(texture) = min_xy = point2d(bounds[0]), max_xy = point2d(bounds[1]) ) - //assert(min_xy==[0,0] && max_xy==[1,1],"VNF tiles must span exactly from [0,0] to [1,1] in the X and Y components.")) assert(all_nonnegative(concat(min_xy,[1,1]-max_xy)), "VNF tile X and Y components must be between 0 and 1.") let( verts = texture[0], @@ -4037,6 +4036,29 @@ function _validate_texture(texture) = true; + +function _tex_height(scale, inset, z) = scale<0 ? -(1-z - inset) * scale + : (z - inset) * scale; + +function _get_texture(texture, tex_rot) = + let( + tex_rot=!is_bool(tex_rot)? tex_rot + : echo("boolean value for tex_rot is deprecated. Use a numerical angle divisible by 90.") tex_rot?90:0 + ) + assert(is_num(tex_rot) && posmod(tex_rot,90)==0, "tex_rot must be a multiple of 90 degrees") + let( + tex = is_string(texture)? texture(texture,$fn=_tex_fn_default()) : texture, + check_tex = _validate_texture(tex), + tex_rot = posmod(tex_rot,360) + ) + 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)); + + + function _textured_linear_sweep( region, texture, tex_size=[5,5], h, counts, inset=false, rot=0, @@ -4059,21 +4081,15 @@ function _textured_linear_sweep( frac = pos-ind, texh = scale<0 ? -(1-tilez - inset) * scale : (tilez - inset) * scale, - base = lerp(bases[ind], select(bases,ind+1), frac), - norm = unit(lerp(norms[ind], select(norms,ind+1), frac)) + base = lerp(select(bases,ind), select(bases,ind+1), frac), + norm = unit(lerp(select(norms,ind), select(norms,ind+1), frac)) ) base + norm * texh, caps = is_bool(caps) ? [caps,caps] : caps, regions = is_path(region,2)? [[region]] : region_parts(region), - tex = is_string(texture)? texture(texture,$fn=_tex_fn_default()) : texture, - dummy = assert(is_undef(samples) || is_vnf(tex), "You gave the tex_samples argument with a heightfield texture, which is not permitted. Use the n= argument to texture() instead"), - dummy2=is_bool(rot)?echo("boolean value for tex_rot is deprecated. Use a numerical angle, one of 0, 90, 180, or 270.")0:0, - texture = !rot? tex : - is_vnf(tex)? zrot(is_num(rot)?rot:90, cp=[1/2,1/2], p=tex) : - rot==180? reverse([for (row=tex) reverse(row)]) : - rot==270? [for (row=transpose(tex)) reverse(row)] : - reverse(transpose(tex)), + texture = _get_texture(texture, rot), + dummy = assert(is_undef(samples) || is_vnf(texture), "You gave the tex_samples argument with a heightfield texture, which is not permitted. Use the n= argument to texture() instead"), h = first_defined([h, l, height, length, 1]), inset = is_num(inset)? inset : inset? 1 : 0, twist = default(twist, 0), @@ -4082,39 +4098,36 @@ function _textured_linear_sweep( is_num(scale)? [scale,scale,1] : scale, samples = !is_vnf(texture)? len(texture[0]) : is_num(samples)? samples : 8, - check_tex = _validate_texture(texture), - sorted_tile = - !is_vnf(texture)? texture : + vnf_tile = + !is_vnf(texture) || samples==1 ? texture + : let( s = 1 / max(1, samples), - vnf = samples<=1? texture : - let( - slice_us = list([s:s:1-s/2]), - vnft1 = vnf_slice(texture, "X", slice_us), - vnft = twist? vnf_slice(vnft1, "Y", slice_us) : vnft1, - zvnf = [ - [ - for (p=vnft[0]) [ + slice_us = list([s:s:1-s/2]), + vnft1 = vnf_slice(texture, "X", slice_us), + vnft = twist? vnf_slice(vnft1, "Y", slice_us) : vnft1, + zvnf = [ + [ + for (p=vnft[0]) [ approx(p.x,0)? 0 : approx(p.x,1)? 1 : p.x, approx(p.y,0)? 0 : approx(p.y,1)? 1 : p.y, p.z ] - ], - vnft[1] - ] - ) zvnf - ) _vnf_sort_vertices(vnf, idx=[1,0]), - vertzs = !is_vnf(sorted_tile)? undef : - group_sort(sorted_tile[0], idx=1), - edge_paths = is_vnf(sorted_tile) ? _tile_edge_path_list(sorted_tile,1) : undef, + ], + vnft[1] + ] + ) zvnf, + edge_paths = is_vnf(texture) ? _tile_edge_path_list(vnf_tile,1) : undef, tpath = is_def(edge_paths) ? len(edge_paths[0])==0 ? [] : hstack([column(edge_paths[0][0],0), column(edge_paths[0][0],2)]) : let( - row = sorted_tile[0], + row = texture[0], rlen = len(row) ) [for (i = [0:1:rlen]) [i/rlen, row[i%rlen]]], edge_closed_paths = is_def(edge_paths) ? edge_paths[1] : [], tmat = scale(scale) * zrot(twist) * up(h/2), + texcnt = is_vnf(texture) ? undef + : [len(texture[0]), len(texture)], pre_skew_vnf = vnf_join([ for (rgn = regions) let( walls_vnf = vnf_join([ @@ -4125,19 +4138,16 @@ function _textured_linear_sweep( is_vector(tex_size,2) ? [round(plen/tex_size.x), max(1,round(h/tex_size.y)), ] : [ceil(6*plen/h), 6], - obases = resample_path(path, n=counts.x * samples, closed=true), - onorms = path_normals(obases, closed=true), - bases = list_wrap(obases), - norms = list_wrap(onorms), + bases = resample_path(path, n=counts.x * samples, closed=true), + norms = path_normals(bases, closed=true), vnf = is_vnf(texture) ? vnf_join( // VNF tile texture let( row_vnf = vnf_join([ for (i = [0:1:(scale==1?0:counts.y-1)], j = [0:1:counts.x-1]) [ [ - for (group = vertzs) - each [ - for (vert = group) let( + for (vert=vnf_tile[0]) + let( xy = transform_pt(j,vert.x,vert.z,samples, inset, tex_scale, bases, norms), pt = point3d(xy,vert.y), v = vert.y / counts.y, @@ -4149,9 +4159,8 @@ function _textured_linear_sweep( zrot(twist*(v+vv)) * zscale(h/counts.y) ) apply(mat, pt) - ] ], - sorted_tile[1] + vnf_tile[1] ] ]) ) [ @@ -4168,7 +4177,6 @@ function _textured_linear_sweep( ] ) : let( // Heightfield texture - texcnt = [len(texture[0]), len(texture)], tile_rows = [ for (ti = [0:1:texcnt.y-1]) path3d([ @@ -4204,10 +4212,8 @@ function _textured_linear_sweep( is_vector(tex_size,2) ? [round(plen/tex_size.x), max(1,round(h/tex_size.y)), ] : [ceil(6*plen/h), 6], - obases = resample_path(path, n=counts.x * samples, closed=true), - onorms = path_normals(obases, closed=true), - bases = list_wrap(obases), - norms = list_wrap(onorms), + bases = resample_path(path, n=counts.x * samples, closed=true), + norms = path_normals(bases, closed=true), nupath = [ for (j = [0:1:counts.x-1], vert = tpath) transform_pt(j,vert.x,vert.y,samples,inset,tex_scale,bases,norms) @@ -4224,10 +4230,8 @@ function _textured_linear_sweep( is_vector(tex_size,2) ? [round(plen/tex_size.x), max(1,round(h/tex_size.y)), ] : [ceil(6*plen/h), 6], - obases = resample_path(path, n=counts.x * samples, closed=true), - onorms = path_normals(obases, closed=true), - bases = list_wrap(obases), - norms = list_wrap(onorms), + bases = resample_path(path, n=counts.x * samples, closed=true), + norms = path_normals(bases, closed=true), modpaths = [for (j = [0:1:counts.x-1], cpath = edge_closed_paths) [for(vert = cpath) transform_pt(j,vert.x,vert.z,samples,inset,tex_scale,bases, norms)] @@ -4364,15 +4368,8 @@ function _textured_revolution( ) assert(closed || is_path(shape,2)) let( - tex = is_string(texture)? texture(texture,$fn=_tex_fn_default()) : texture, - dummy = assert(is_undef(samples) || is_vnf(tex), "You gave the tex_samples argument with a heightfield texture, which is not permitted. Use the n= argument to texture() instead"), - dummy2=is_bool(rot)?echo("boolean value for tex_rot is deprecated. Use a numerical angle, one of 0, 90, 180, or 270.")0:0, - texture = !rot? tex : - is_vnf(tex)? zrot(is_num(rot)?rot:90, cp=[1/2,1/2], p=tex) : - rot==180? reverse([for (row=tex) reverse(row)]) : - rot==270? [for (row=transpose(tex)) reverse(row)] : - reverse(transpose(tex)), - check_tex = _validate_texture(texture), + texture = _get_texture(texture, rot), + dummy = assert(is_undef(samples) || is_vnf(texture), "You gave the tex_samples argument with a heightfield texture, which is not permitted. Use the n= argument to texture() instead"), inset = is_num(inset)? inset : inset? 1 : 0, samples = !is_vnf(texture)? len(texture) : is_num(samples)? samples : 8, @@ -4382,28 +4379,25 @@ function _textured_revolution( maxy = bounds[1].y, h = maxy - miny, circumf = 2 * PI * maxx, - tile = !is_vnf(texture)? texture : + texcnt = is_vnf(texture) ? undef : [len(texture[0]), len(texture)], + tile = !is_vnf(texture) || samples==1 ? texture : let( - utex = samples<=1? texture : - let( - s = 1 / samples, - slices = list([s : s : 1-s/2]), - vnfx = vnf_slice(texture, "X", slices), - vnfy = inhibit_y_slicing? vnfx : vnf_slice(vnfx, "Y", slices), - vnft = vnf_triangulate(vnfy), - zvnf = [ - [ - for (p=vnft[0]) [ - approx(p.x,0)? 0 : approx(p.x,1)? 1 : p.x, - approx(p.y,0)? 0 : approx(p.y,1)? 1 : p.y, - p.z - ] - ], - vnft[1] + s = 1 / samples, + slices = list([s : s : 1-s/2]), + vnfx = vnf_slice(texture, "X", slices), + vnfy = inhibit_y_slicing? vnfx : vnf_slice(vnfx, "Y", slices), + vnft = vnf_triangulate(vnfy), + zvnf = [ + [ + for (p=vnft[0]) [ + approx(p.x,0)? 0 : approx(p.x,1)? 1 : p.x, + approx(p.y,0)? 0 : approx(p.y,1)? 1 : p.y, + p.z ] - ) zvnf - ) _vnf_sort_vertices(utex, idx=[0,1]), - vertzs = is_vnf(texture)? group_sort(tile[0], idx=0) : undef, + ], + vnft[1] + ] + ) zvnf, edge_paths = is_vnf(tile) ? _tile_edge_path_list(tile,1) : undef, bpath = is_def(edge_paths) ? len(edge_paths[0])==0 ? [] : hstack([column(edge_paths[0][0],0), column(edge_paths[0][0],2)]) @@ -4436,8 +4430,8 @@ function _textured_revolution( part = tileind * samples, ind = floor(part), frac = part - ind, - base = lerp(bases[ind], select(bases,ind+1), frac), - norm = unit(lerp(norms[ind], select(norms,ind+1), frac)), + base = lerp(select(bases,ind), select(bases,ind+1), frac), + norm = unit(lerp(select(norms,ind), select(norms,ind+1), frac)), scale = tex_scale * lookup(tileind/counts_y, taper_lup) * base.x/maxx, texh = scale<0 ? -(1-tilez - inset) * scale : (tilez - inset) * scale @@ -4452,26 +4446,21 @@ function _textured_revolution( is_vector(tex_size,2)? max(1,round(plen/tex_size.y)) : 6, obases = resample_path(path, n=counts_y * samples + (closed?0:1), closed=closed), onorms = path_normals(obases, closed=closed), - rbases = closed? list_wrap(obases) : obases, - rnorms = closed? list_wrap(onorms) : onorms, - bases = xrot(90, p=path3d(rbases)), - norms = xrot(90, p=path3d(rnorms)), + bases = xrot(90, p=path3d(obases)), + norms = xrot(90, p=path3d(onorms)), vnf = is_vnf(texture) ? vnf_join([ // VNF tile texture for (j = [0:1:counts_y-1]) [ [ - for (group = vertzs) each [ - for (vert = group) + for (vert = tile[0]) let(xyz = transform_point(j + (1-vert.y),vert.z,counts_y,bases, norms)) zrot(vert.x*angle/counts_x, p=xyz) - ] ], tile[1] ] ]) : let( // Heightfield texture - texcnt = [len(texture[0]), len(texture)], tiles = transpose([ for (j = [0,1], tj = [0:1:texcnt.x-1]) if (j == 0 || tj == 0) @@ -4502,23 +4491,18 @@ function _textured_revolution( plen = path_length(path, closed=closed), counts_y = is_vector(counts,2)? counts.y : is_vector(tex_size,2)? max(1,round(plen/tex_size.y)) : 6, - obases = resample_path(path, n=counts_y * samples + (closed?0:1), closed=closed), - onorms = path_normals(obases, closed=closed), - bases = closed? list_wrap(obases) : obases, - norms = closed? list_wrap(onorms) : onorms, + bases = resample_path(path, n=counts_y * samples + (closed?0:1), closed=closed), + norms = path_normals(bases, closed=closed), ppath = is_vnf(texture) ? [ // VNF tile texture - for (j = [0:1:counts_y-1]) - //for (group = vertzs, vert = reverse(group)) - for(vert=side_open_path) + for (j = [0:1:counts_y-1], vert=side_open_path) transform_point(j + (1 - vert.y),vert.z,counts_y,bases, norms) ] - : let( // Heightfield texture - texcnt = [len(texture[0]), len(texture)] - ) [ + : + [ // Heightfield texture for (i = [0:1:counts_y-(closed?1:0)], ti = [0:1:texcnt.y-1]) - if (i != counts_y || ti == 0) - transform_point(i + (ti/texcnt.y),texture[ti][0],counts_y,bases, norms) + if (i != counts_y || ti == 0) + transform_point(i + (ti/texcnt.y),texture[ti][0],counts_y,bases, norms) ], path = closed? ppath : [ [0, ppath[0].y], @@ -4534,10 +4518,8 @@ function _textured_revolution( plen = path_length(path, closed=closed), counts_y = is_vector(counts,2)? counts.y : is_vector(tex_size,2)? max(1,round(plen/tex_size.y)) : 6, - obases = resample_path(path, n=counts_y * samples + (closed?0:1), closed=closed), - onorms = path_normals(obases, closed=closed), - bases = closed? list_wrap(obases) : obases, - norms = closed? list_wrap(onorms) : onorms, + bases = resample_path(path, n=counts_y * samples + (closed?0:1), closed=closed), + norms = path_normals(bases, closed=closed), modpaths = [for (j = [0:1:counts_y-1], cpath=side_closed_paths) [for(vert=cpath) transform_point(j + (1 - vert.y),vert.z,counts_y,bases, norms)] @@ -4557,10 +4539,8 @@ function _textured_revolution( is_vector(tex_size,2)? max(1,round(plen/tex_size.y)) : 6, obases = resample_path(rgn[0], n=counts_y * samples + (closed?0:1), closed=closed), onorms = path_normals(obases, closed=closed), - rbases = closed? list_wrap(obases) : obases, - rnorms = closed? list_wrap(onorms) : onorms, - bases = xrot(90, p=path3d(rbases)), - norms = xrot(90, p=path3d(rnorms)), + bases = xrot(90, p=path3d(obases)), + norms = xrot(90, p=path3d(onorms)), caps_vnf = vnf_join([ for (epath=edge_closed_paths, j = [-1,0]) let( @@ -4649,5 +4629,203 @@ module _textured_revolution( } +function _texture_point_array(points, texture, tex_reps, tex_size, tex_samples, tex_inset=false, tex_rot=0, triangulate=false, + col_wrap=false, tex_depth=1, row_wrap=false, caps, cap1, cap2, reverse=false, style="min_edge", tex_extra, tex_skip, sidecaps,sidecap1,sidecap2) = + assert(tex_reps==undef || is_vector(tex_reps,2)) + assert(tex_size==undef || is_num(tex_size) || is_vector(tex_size,2), "tex_size must be a scalar or 2-vector") + assert(num_defined([tex_size, tex_reps])<2, "Cannot give both tex_size and tex_reps") + assert(in_list(style,["default","alt","quincunx", "convex","concave", "min_edge","min_area","flip1","flip2"])) + assert(is_matrix(points[0], n=3),"Point array has the wrong shape or points are not 3d") + assert(is_consistent(points), "Non-rectangular or invalid point array") + let( + cap1 = first_defined([cap1,caps,false]), + cap2 = first_defined([cap2,caps,false]), + sidecap1 = first_defined([sidecap1,sidecaps,false]), + sidecap2 = first_defined([sidecap2,sidecaps,false]), + tex_inset = is_num(tex_inset)? tex_inset : tex_inset? 1 : 0, + texture = _get_texture(texture, tex_rot), + dummy = assert(is_undef(tex_samples) || is_vnf(texture), + "You gave the tex_samples argument with a heightfield texture, which is not permitted. Use the n= argument to texture() instead"), + 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), + 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)], + normals = surfnormals(points, col_wrap=col_wrap, row_wrap=row_wrap), + getscale = function(x,y) (x+y)/2 + ) + !is_vnf(texture) ? // heightmap case + let( + extra = is_def(tex_extra) ? force_list(tex_extra,2) + : [col_wrap?0:1, row_wrap?0:1], + skip = is_def(tex_skip) ? force_list(tex_skip,2) : [0,0], + texsize = [len(texture[0]), len(texture)], + fullsize = [texsize.x*tex_reps.x+extra.x-skip.x, texsize.y*tex_reps.y+extra.y-skip.y], + res_points = resample(points,fullsize, col_wrap=col_wrap, row_wrap=row_wrap), + res_normals=resample(normals,fullsize, col_wrap=col_wrap, row_wrap=row_wrap), + local_scale = [for(y=[0:1:fullsize.y-1]) + [for(x=[0:1:fullsize.x-1]) + let( + xlen = [ + if(x>0 || col_wrap) norm(res_points[y][x] - select(res_points[y], x-1)), + if(x0 || row_wrap) norm(res_points[y][x] - select(res_points,y-1)[x]), + if(y0) + [for(x=[0:1:tex_reps.x-1], pt=yedge_paths[0][0]) + trans_pt(x,y,[pt.x,y?0:1,pt.z])], + if (!row_wrap) + for(closed_path=yedge_paths[1], x=[0:1:tex_reps.x-1]) + [for(pt = closed_path) trans_pt(x,y,[pt.x,y?0:1,pt.z])] + ] + ) + for(path=cap_paths) [path, [count(path,reverse=y==0)]], + if (!col_wrap) + for(x=[if (sidecap1) 0, if (sidecap2) tex_reps.x-1]) + let( + cap_paths = [for(closed_path=xedge_paths[1], y=[0:1:tex_reps.y-1]) + [for(pt = closed_path) trans_pt(x,y,[x?1:0,pt.y,pt.z])]] + ) + for(path=cap_paths) [path, [count(path,reverse=x!=0)]] + ]) + ) + reverse ? vnf_reverse_faces(fullvnf) : fullvnf; + +///// These need to be either hidden or documented and placed somewhere. + + +function bilerp(pts,x,y) = + [1,x,y,x*y]*[[1, 0, 0, 0],[-1, 0, 1, 0],[-1,1,0,0],[1,-1,-1,1]]*pts; + + +function resample(data, size, col_wrap=false, row_wrap=false) = + let( + xL=len(data[0]), + yL=len(data), + lastx=xL-(col_wrap?0:1), + lasty=yL-(row_wrap?0:1), + lastoutx = size.x - (col_wrap?0:1), + lastouty = size.y - (row_wrap?0:1), + xscale = lastx/lastoutx, + yscale = lasty/lastouty + ) + [ + for(y=[0:1:lastouty]) + [ + for(x=[0:1:lastoutx]) + let( + sx = xscale*x, + sy = yscale*y, + xind=floor(sx), + yind=floor(sy) + ) + bilerp([data[yind%yL][xind%xL], data[(yind+1)%yL][xind%xL], + data[yind%yL][(xind+1)%xL], data[(yind+1)%yL][(xind+1)%xL]], + sx-xind, sy-yind) + ] + ]; + + + + +function surfnormals(data, col_wrap=false, row_wrap=false) = + let( + rowderivs = [for(y=[0:1:len(data)-1]) path_tangents(data[y],closed=col_wrap)], + colderivs = [for(x=[0:1:len(data[0])-1]) path_tangents(column(data,x), closed=row_wrap)] + ) + [for(y=[0:1:len(data)-1]) + [for(x=[0:1:len(data[0])-1]) + cross(colderivs[x][y],rowderivs[y][x])]]; + + + + // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/vnf.scad b/vnf.scad index ddc0a03a..c791c130 100644 --- a/vnf.scad +++ b/vnf.scad @@ -27,13 +27,14 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces. -// Function: vnf_vertex_array() +// Function&Module: vnf_vertex_array() // Synopsis: Returns a VNF structure from a rectangular vertex list. -// SynTags: VNF +// SynTags: VNF, Geom // Topics: VNF Generators, Lists // See Also: vnf_tri_array(), vnf_join(), vnf_from_polygons(), vnf_from_region() // Usage: // vnf = vnf_vertex_array(points, [caps=], [cap1=], [cap2=], [style=], [reverse=], [col_wrap=], [row_wrap=], [triangulate=]); +// vnf_vertex_array(points, [caps=], [cap1=], [cap2=], [style=], [reverse=], [col_wrap=], [row_wrap=], [triangulate=],...) [ATTACHMENTS]; // Description: // Creates a VNF structure from a rectangular vertex list, creating edges that connect the adjacent vertices in the vertex list // and creating the faces defined by those edges. You can optionally create the edges and faces to wrap the last column @@ -47,6 +48,23 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces. // adds a vertex in the center of each quadrilateral and creates four triangles, and the "convex" and "concave" styles // choose the locally convex/concave subdivision. The "min_area" option creates the triangulation with the minimal area. Degenerate faces // are not included in the output, but if this results in unused vertices they still appear in the output. +// . +// 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]`. +// 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 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. // Arguments: // points = A list of vertices to divide into columns and rows. // --- @@ -58,6 +76,29 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces. // reverse = If true, reverse all face normals. // style = The style of subdividing the quads into faces. Valid options are "default", "alt", "flip1", "flip2", "min_edge", "min_area", "quincunx", "convex" and "concave". // 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_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. +// tex_samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 +// tex_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 +// 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 +// 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 +// 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): // vnf = vnf_vertex_array( // points=[ @@ -160,6 +201,28 @@ 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); + +module vnf_vertex_array( + points, + caps, cap1, cap2, + col_wrap=false, + row_wrap=false, + reverse=false, + 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, + convexity=2, cp="centroid", anchor="origin", spin=0, orient=UP, atype="hull") +{ + vnf = vnf_vertex_array(points=points, caps=caps, cap1=cap2, cap2=cap2, + 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=2, cp="centroid", anchor="origin", spin=0, orient=UP, atype="hull") children(); +} + + function vnf_vertex_array( points, caps, cap1, cap2, @@ -167,14 +230,22 @@ function vnf_vertex_array( row_wrap=false, reverse=false, style="default", - triangulate = false + 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 ) = - assert(!(any([caps,cap1,cap2]) && !col_wrap), "col_wrap must be true if caps are requested") - assert(!(any([caps,cap1,cap2]) && row_wrap), "Cannot combine caps with row_wrap") assert(in_list(style,["default","alt","quincunx", "convex","concave", "min_edge","min_area","flip1","flip2"])) assert(is_matrix(points[0], n=3),"Point array has the wrong shape or points are not 3d") assert(is_consistent(points), "Non-rectangular or invalid point array") assert(is_bool(triangulate)) + is_def(texture) ? + _texture_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,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)") let( pts = flatten(points), pcnt = len(pts), @@ -1013,20 +1084,6 @@ function _detri_combine_faces(edgelist,faces,normals,facelist,curface) = -function _vnf_sort_vertices(vnf, idx=[2,1,0]) = - let( - verts = vnf[0], - faces = vnf[1], - vidx = sortidx(verts, idx=idx), - rvidx = sortidx(vidx), - sorted_vnf = [ - [ for (i = vidx) verts[i] ], - [ for (face = faces) [ for (i = face) rvidx[i] ] ], - ] - ) sorted_vnf; - - - // Function: vnf_slice() // Synopsis: Slice the faces of a VNF along an axis.