From 940e1eea6a1ca541154348d2f80ee3c8d51292ea Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Fri, 4 Apr 2025 16:17:27 -0400 Subject: [PATCH] textured_tile & prismoid attachment spin fixes --- attachments.scad | 21 ++- shapes3d.scad | 386 +++++++++++++++++++++++++++++++++++++++++------ skin.scad | 24 --- 3 files changed, 356 insertions(+), 75 deletions(-) diff --git a/attachments.scad b/attachments.scad index a0c3c23c..f26d8149 100644 --- a/attachments.scad +++ b/attachments.scad @@ -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 diff --git a/shapes3d.scad b/shapes3d.scad index 607c324f..a7346ffb 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -20,7 +20,7 @@ use // 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,302 @@ 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: +// Create a cuboid or trapezoidal prism and place 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. Note that 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 will be cut into the parent object. +// The texture itself will be 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. Note that the cutout does not extend out to the sides, so if the parent shape +// has the exact same dimensions as the texture tile, you will have exatly 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. +// This is the normal behavior for these textures. If you need to disable this feature you can set the `extra` parameter to false, 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 one side that makes the texture unbalanced. This can +// be removed with the `skip` parameter, which defaults to zero and similarly specifies the number of lines to skip in the X and Y directions at +// the edge of the tile. Note that you have to 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 +1815,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 diff --git a/skin.scad b/skin.scad index a184e7b8..249e0565 100644 --- a/skin.scad +++ b/skin.scad @@ -4278,30 +4278,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=], ...);