From 4b533144a9ee668b75300c209e34950f53d8e106 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 15 Oct 2025 20:01:17 -0400 Subject: [PATCH] masks reorg --- attachments.scad | 802 +-------------------------------- std.scad | 3 +- tutorials/Rounding_the_Cube.md | 8 +- 3 files changed, 11 insertions(+), 802 deletions(-) diff --git a/attachments.scad b/attachments.scad index 661f8e0..c267aa1 100644 --- a/attachments.scad +++ b/attachments.scad @@ -748,7 +748,8 @@ function _quant_anch(x) = approx(x,0) ? 0 : sign(x); // Make arbitrary anchor legal for a given geometry function _make_anchor_legal(anchor,geom) = - in_list(geom[0], ["prismoid","trapezoid"]) ? [for(v=anchor) _quant_anch(v)] + is_string(anchor) ? anchor + : in_list(geom[0], ["prismoid","trapezoid"]) ? [for(v=anchor) _quant_anch(v)] : in_list(geom[0], ["conoid", "extrusion_extent"]) ? [anchor.x,anchor.y, _quant_anch(anchor.z)] : anchor; @@ -2021,797 +2022,6 @@ module show_int(tags) } -// Section: Mask Attachment - - -// Module: face_mask() -// Synopsis: Ataches a 3d mask shape to the given faces of the parent. -// SynTags: Trans -// Topics: Attachments, Masking -// See Also: attachable(), position(), attach(), edge_mask(), corner_mask(), face_profile(), edge_profile(), corner_profile() -// Usage: -// PARENT() face_mask(faces) CHILDREN; -// Description: -// Takes a 3D mask shape, and attaches it to the given faces, with the appropriate orientation to be -// differenced away. The mask shape should be vertically oriented (Z-aligned) with the bottom half -// (Z-) shaped to be diffed away from the face of parent attachable shape. If no tag is set then -// `face_mask()` sets the tag for children to "remove" so that it works with the default {{diff()}} tag. -// For details on specifying the faces to mask see [Specifying Faces](attachments.scad#subsection-specifying-faces). -// For a step-by-step explanation of masking attachments, see the [Attachments Tutorial](Tutorial-Attachment-Edge-Profiling). -// Arguments: -// edges = Faces to mask. See [Specifying Faces](attachments.scad#subsection-specifying-faces) for information on specifying faces. Default: All faces -// Side Effects: -// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. -// `$idx` is set to the index number of each face in the list of faces given. -// `$attach_anchor` is set for each face given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor. -// Example: -// diff() -// cylinder(r=30, h=60) -// face_mask(TOP) { -// rounding_cylinder_mask(r=30,rounding=5); -// cuboid([5,61,10]); -// } -// Example: Using `$idx` -// diff() -// cylinder(r=30, h=60) -// face_mask([TOP, BOT]) -// zrot(45*$idx) zrot_copies([0,90]) cuboid([5,61,10]); -module face_mask(faces=[LEFT,RIGHT,FRONT,BACK,BOT,TOP]) { - req_children($children); - faces = is_vector(faces)? [faces] : faces; - assert(all([for (face=faces) is_vector(face) && sum([for (x=face) x!=0? 1 : 0])==1]), "\nVector in faces doesn't point at a face."); - assert($parent_geom != undef, "\nNo object to attach to!"); - attach(faces) { - default_tag("remove") children(); - } -} - - -// Module: edge_mask() -// Synopsis: Attaches a 3D mask shape to the given edges of the parent. -// SynTags: Trans -// Topics: Attachments, Masking -// See Also: attachable(), position(), attach(), face_mask(), corner_mask(), face_profile(), edge_profile(), corner_profile() -// Usage: -// PARENT() edge_mask([edges], [except]) CHILDREN; -// Description: -// Takes a 3D mask shape, and attaches it to the given edges of a cuboid parent, with the appropriate orientation to be -// differenced away. The mask shape should be vertically oriented (Z-aligned) with the back-right -// quadrant (X+Y+) shaped to be diffed away from the edge of parent attachable shape. If no tag is set -// then `edge_mask` sets the tag for children to "remove" so that it works with the default {{diff()}} tag. -// For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges). -// For a step-by-step explanation of masking attachments, see the [Attachments Tutorial](Tutorial-Attachment-Edge-Profiling). -// Figure: A Typical Edge Rounding Mask -// module roundit(l,r) difference() { -// translate([-1,-1,-l/2]) -// cube([r+1,r+1,l]); -// translate([r,r]) -// cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4)); -// } -// roundit(l=30,r=10); -// Arguments: -// edges = Edges to mask. See [Specifying Edges](attachments.scad#subsection-specifying-edges). Default: All edges. -// except = Edges to explicitly NOT mask. See [Specifying Edges](attachments.scad#subsection-specifying-edges). Default: No edges. -// Side Effects: -// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. -// `$idx` is set to the index number of each edge. -// `$attach_anchor` is set for each edge given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor. -// `$parent_size` is set to the size of the parent object. -// Example: -// diff() -// cube([50,60,70],center=true) -// edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT]) -// rounding_edge_mask(l=71,r=10); -module edge_mask(edges=EDGES_ALL, except=[]) { - req_children($children); - assert($parent_geom != undef, "\nNo object to attach to!"); - edges = _edges(edges, except=except); - vecs = [ - for (i = [0:3], axis=[0:2]) - if (edges[axis][i]>0) - EDGE_OFFSETS[axis][i] - ]; - for ($idx = idx(vecs)) { - vec = vecs[$idx]; - vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0); - dummy=assert(vcount == 2, "\nNot an edge vector!"); - anch = _find_anchor(vec, $parent_geom); - $edge_angle = len(anch)==5 ? struct_val(anch[4],"edge_angle") : undef; - $edge_length = len(anch)==5 ? struct_val(anch[4],"edge_length") : undef; - $attach_to = undef; - $attach_anchor = anch; - rotang = - vec.z<0? [90,0,180+v_theta(vec)] : - vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) : - vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] : - [-90,0,180+v_theta(vec)]; - translate(anch[1]) rot(rotang) - default_tag("remove") children(); - } -} - - -// Module: corner_mask() -// Synopsis: Attaches a 3d mask shape to the given corners of the parent. -// SynTags: Trans -// Topics: Attachments, Masking -// See Also: attachable(), position(), attach(), face_mask(), edge_mask(), face_profile(), edge_profile(), corner_profile() -// Usage: -// PARENT() corner_mask([corners], [except]) CHILDREN; -// Description: -// Takes a 3D mask shape, and attaches it to the specified corners, with the appropriate orientation to -// be differenced away. The 3D corner mask shape should be designed to mask away the X+Y+Z+ octant. If no tag is set -// then `corner_mask` sets the tag for children to "remove" so that it works with the default {{diff()}} tag. -// See [Specifying Corners](attachments.scad#subsection-specifying-corners) for information on how to specify corner sets. -// For a step-by-step explanation of masking attachments, see the [Attachments Tutorial](Tutorial-Attachment-Edge-Profiling). -// Arguments: -// corners = Corners to mask. See [Specifying Corners](attachments.scad#subsection-specifying-corners). Default: All corners. -// except = Corners to explicitly NOT mask. See [Specifying Corners](attachments.scad#subsection-specifying-corners). Default: No corners. -// Side Effects: -// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. -// `$idx` is set to the index number of each corner. -// `$attach_anchor` is set for each corner given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor. -// Example: -// diff() -// cube(100, center=true) -// corner_mask([TOP,FRONT],LEFT+FRONT+TOP) -// difference() { -// translate(-0.01*[1,1,1]) cube(20); -// translate([20,20,20]) sphere(r=20); -// } -module corner_mask(corners=CORNERS_ALL, except=[]) { - req_children($children); - assert($parent_geom != undef, "\nNo object to attach to!"); - corners = _corners(corners, except=except); - vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]]; - for ($idx = idx(vecs)) { - vec = vecs[$idx]; - vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0); - dummy=assert(vcount == 3, "\nNot an edge vector!"); - anch = _find_anchor(vec, $parent_geom); - $attach_to = undef; - $attach_anchor = anch; - rotang = vec.z<0? - [ 0,0,180+v_theta(vec)-45] : - [180,0,-90+v_theta(vec)-45]; - translate(anch[1]) rot(rotang) - default_tag("remove") children(); - } -} - - -// Module: face_profile() -// Synopsis: Extrudes a 2D edge profile into a mask for all edges and corners of the given faces on the parent. -// SynTags: Geom -// Topics: Attachments, Masking -// See Also: attachable(), position(), attach(), edge_profile(), corner_profile(), face_mask(), edge_mask(), corner_mask() -// Usage: -// PARENT() face_profile(faces, r|d=, [convexity=]) CHILDREN; -// Description: -// Given a 2D edge profile, extrudes it into a mask for all edges and corners bounding each given face. If no tag is set -// then `face_profile` sets the tag for children to "remove" so that it works with the default {{diff()}} tag. -// See [Specifying Faces](attachments.scad#subsection-specifying-faces) for information on specifying faces. -// For a step-by-step explanation of masking attachments, see the [Attachments Tutorial](Tutorial-Attachment-Edge-Profiling). -// Arguments: -// faces = Faces to mask edges and corners of. -// r = Radius of corner mask. -// --- -// d = Diameter of corner mask. -// excess = Excess length to extrude the profile to make edge masks. Default: 0.01 -// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10 -// Side Effects: -// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. -// `$idx` is set to the index number of each face. -// `$attach_anchor` is set for each edge or corner given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor. -// `$profile_type` is set to `"edge"` or `"corner"`, depending on what is being masked. -// Example: -// diff() -// cube([50,60,70],center=true) -// face_profile(TOP,r=10) -// mask2d_roundover(r=10); -module face_profile(faces=[], r, d, excess=0.01, convexity=10) { - req_children($children); - faces = is_vector(faces)? [faces] : faces; - assert(all([for (face=faces) is_vector(face) && sum([for (x=face) x!=0? 1 : 0])==1]), "\nVector in faces doesn't point at a face."); - r = get_radius(r=r, d=d, dflt=undef); - assert(is_num(r) && r>=0); - edge_profile(faces, excess=excess) children(); - corner_profile(faces, convexity=convexity, r=r) children(); -} - - -// Module: edge_profile() -// Synopsis: Extrudes a 2d edge profile into a mask on the given edges of the parent. -// SynTags: Geom -// Topics: Attachments, Masking -// See Also: attachable(), position(), attach(), face_profile(), edge_profile_asym(), corner_profile(), edge_mask(), face_mask(), corner_mask() -// Usage: -// PARENT() edge_profile([edges], [except], [convexity]) CHILDREN; -// Description: -// Takes a 2D mask shape and attaches it to the selected edges, with the appropriate orientation and -// extruded length to be `diff()`ed away, to give the edge a matching profile. If no tag is set -// then `edge_profile` sets the tag for children to "remove" so that it works with the default {{diff()}} tag. -// For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges). -// For a step-by-step explanation of masking attachments, see the [Attachments Tutorial](Tutorial-Attachment-Edge-Profiling). -// Arguments: -// edges = Edges to mask. See [Specifying Edges](attachments.scad#subsection-specifying-edges). Default: All edges. -// except = Edges to explicitly NOT mask. See [Specifying Edges](attachments.scad#subsection-specifying-edges). Default: No edges. -// excess = Excess length to extrude the profile to make edge masks. Default: 0.01 -// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10 -// Side Effects: -// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. -// `$idx` is set to the index number of each edge. -// `$attach_anchor` is set for each edge given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor. -// `$profile_type` is set to `"edge"`. -// `$edge_angle` is set to the inner angle of the current edge. -// Example: -// diff() -// cube([50,60,70],center=true) -// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) -// mask2d_roundover(r=10, inset=2); -// Example: Using $edge_angle on a conoid -// diff() -// cyl(d1=50, d2=30, l=40, anchor=BOT) { -// edge_profile([TOP,BOT], excess=10, convexity=6) { -// mask2d_roundover(r=8, inset=1, excess=1, mask_angle=$edge_angle); -// } -// } -// Example: Using $edge_angle on a prismoid -// diff() -// prismoid([60,50],[30,20],h=40,shift=[-25,15]) { -// edge_profile(excess=10, convexity=20) { -// mask2d_roundover(r=5,inset=1,mask_angle=$edge_angle,$fn=32); -// } -// } - -module edge_profile(edges=EDGES_ALL, except=[], excess=0.01, convexity=10) { - req_children($children); - check1 = assert($parent_geom != undef, "\nNo object to attach to!"); - conoid = $parent_geom[0] == "conoid"; - edges = !conoid? _edges(edges, except=except) : - edges==EDGES_ALL? [TOP,BOT] : - assert(all([for (e=edges) in_list(e,[TOP,BOT])]), "\nInvalid conoid edge spec.") - edges; - vecs = conoid - ? [for (e=edges) e+FWD] - : [ - for (i = [0:3], axis=[0:2]) - if (edges[axis][i]>0) - EDGE_OFFSETS[axis][i] - ]; - all_vecs_are_edges = all([for (vec = vecs) sum(v_abs(vec))==2]); - check2 = assert(all_vecs_are_edges, "\nAll vectors must be edges."); - default_tag("remove") - for ($idx = idx(vecs)) { - vec = vecs[$idx]; - anch = _find_anchor(vec, $parent_geom); - path_angs_T = _attach_geom_edge_path($parent_geom, vec); - path = path_angs_T[0]; - vecs = path_angs_T[1]; - post_T = path_angs_T[2]; - $attach_to = undef; - $attach_anchor = anch; - $profile_type = "edge"; - multmatrix(post_T) { - for (i = idx(path,e=-2)) { - pt1 = select(path,i); - pt2 = select(path,i+1); - cp = (pt1 + pt2) / 2; - v1 = vecs[i][0]; - v2 = vecs[i][1]; - $edge_angle = 180 - vector_angle(v1,v2); - if (!approx(pt1,pt2)) { - seglen = norm(pt2-pt1) + 2 * excess; - move(cp) { - frame_map(x=-v2, z=unit(pt2-pt1)) { - linear_extrude(height=seglen, center=true, convexity=convexity) - mirror([-1,1]) children(); - } - } - } - } - } - } -} - - -// Module: edge_profile_asym() -// Synopsis: Extrudes an asymmetric 2D profile into a mask on the given edges and corners of the parent. -// SynTags: Geom -// Topics: Attachments, Masking -// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_profile(), edge_mask(), face_mask(), corner_mask() -// Usage: -// PARENT() edge_profile([edges], [except], [convexity=], [flip=], [corner_type=]) CHILDREN; -// Description: -// Takes an asymmetric 2D mask shape and attaches it to the selected edges and corners, with the appropriate -// orientation and extruded length to be `diff()`ed away, to give the edges and corners a matching profile. -// If no tag is set then `edge_profile_asym()` sets the tag for children to "remove" so that it works -// with the default {{diff()}} tag. For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges). -// For a step-by-step explanation of masking attachments, see the [Attachments Tutorial](Tutorial-Attachment-Edge-Profiling). -// The asymmetric profiles are joined consistently at the corners. This is impossible if all three edges at a corner use the profile, hence -// this situation is not permitted. The profile orientation can be inverted using the `flip=true` parameter. -// . -// The standard profiles are located in the first quadrant and have positive X values. If you provide a profile located in the second quadrant, -// where the X values are negative, then it produces a fillet. You can flip any of the standard profiles using {{xflip()}}. -// Do **not** flip one of the standard first quadrant masks into the 3rd quadrant $(y<0)$ using {{yflip()}}, as this will not work correctly. -// Fillets are always asymmetric because at a given edge, they can blend in two different directions, so even for symmetric profiles, -// the asymmetric logic is required. You can set the `corner_type` parameter to select rounded, chamfered or sharp corners. -// However, when the corners are inside (concave) corners, you must provide the size of the profile ([width,height]), because the -// this information is required to produce the correct corner and cannot be obtain from the profile itself, which is a child object. -// . -// Because the profiles are asymmetric they can be placed on a given edge in two different orientations. It is easiest to understand -// the orientation by thinking about fillets and in which direction a filleted cube will make a smooth joint. Given a string of connected -// edges, we must identify the orientation of the fillet at just one edge; the orentation of the fillets on the remaining edges is forced -// to maintain consistency across the string of edges. The module uses a set of priority rules as follows: -// . -// 1. Bottom -// 2. Top -// 3. Front or Back -// . -// What this means is that if an edge string contains any edge on the bottom then the bottom edges will be oriented to join the bottom face -// to something, and the rest of the string consistently oriented. If the string contains no bottom edges but it has top edges then -// the edge string will be oriented so that the object can join its top face to something. If the string has no top or bottom edges then it -// must be just a single edge and it will be is oriented so that either the front or back face of the cube can make a smooth joint. -// If the edge orientation is reversed from what you need, set `flip=true`. If these rules seem complicated, just create your model, -// examine the edges, and flip them as required. Note that creating fillets with {{yflip()}} may seem similar to setting `flip=true` and -// may partially work but is **not** the correct way to flip edge profile; it can produce incomplete results. -// -// Arguments: -// edges = Edges to mask. See [Specifying Edges](attachments.scad#subsection-specifying-edges). Default: All edges. -// except = Edges to explicitly NOT mask. See [Specifying Edges](attachments.scad#subsection-specifying-edges). Default: No edges. -// --- -// excess = Excess length to extrude the profile to make edge masks. Default: 0.01 -// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10 -// flip = If true, reverses the orientation of any external profile parts at each edge. Default false -// corner_type = Specifies how exterior corners should be formed. Must be one of `"none"`, `"chamfer"`, `"round"`, or `"sharp"`. Default: `"none"` -// size = If given the width and height of the 2D profile, enable rounding and chamfering of internal corners when given a negative profile. -// Side Effects: -// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. -// `$idx` is set to the index number of each edge. -// `$attach_anchor` is set for each edge given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor. -// `$profile_type` is set to `"edge"`. -// `$edge_angle` is set to the inner angle of the current edge. -// Example: -// ogee = [ -// "xstep",1, "ystep",1, // Starting shoulder. -// "fillet",5, "round",5, // S-curve. -// "ystep",1, "xstep",1 // Ending shoulder. -// ]; -// diff() -// cuboid(50) { -// edge_profile_asym(FRONT) -// mask2d_ogee(ogee); -// } -// Example: Flipped -// ogee = [ -// "xstep",1, "ystep",1, // Starting shoulder. -// "fillet",5, "round",5, // S-curve. -// "ystep",1, "xstep",1 // Ending shoulder. -// ]; -// diff() -// cuboid(50) { -// edge_profile_asym(FRONT, flip=true) -// mask2d_ogee(ogee); -// } -// Example: Negative Chamfering -// cuboid(50) { -// edge_profile_asym(FWD, flip=false) -// xflip() mask2d_chamfer(10); -// edge_profile_asym(BACK, flip=true, corner_type="sharp") -// xflip() mask2d_chamfer(10); -// } -// Example: Negative Roundings -// cuboid(50) { -// edge_profile_asym(FWD, flip=false) -// xflip() mask2d_roundover(10); -// edge_profile_asym(BACK, flip=true, corner_type="round") -// xflip() mask2d_roundover(10); -// } -// Example: Cornerless -// cuboid(50) { -// edge_profile_asym( -// "ALL", except=[TOP+FWD+RIGHT, BOT+BACK+LEFT] -// ) xflip() mask2d_roundover(10); -// } -// Example: More complicated edge sets -// cuboid(50) { -// edge_profile_asym( -// [FWD,BACK,BOT+RIGHT], except=[FWD+RIGHT,BOT+BACK], -// corner_type="round" -// ) xflip() mask2d_roundover(10); -// } -// Example: Mixing it up a bit. -// diff() -// cuboid(60) { -// tag("keep") edge_profile_asym(LEFT, flip=true, corner_type="chamfer") -// xflip() mask2d_chamfer(10); -// edge_profile_asym(RIGHT) -// mask2d_roundover(10); -// } -// Example: Chamfering internal corners. -// cuboid(40) { -// edge_profile_asym( -// [FWD+DOWN,FWD+LEFT], -// corner_type="chamfer", size=[10,10]/sqrt(2) -// ) xflip() mask2d_chamfer(10); -// } -// Example: Rounding internal corners. -// cuboid(40) { -// edge_profile_asym( -// [FWD+DOWN,FWD+LEFT], -// corner_type="round", size=[10,10] -// ) xflip() mask2d_roundover(10); -// } -// Example(3D,NoScales): This string of 3 edges rounds so that the cuboid joins smoothly to the bottom -// color_this("lightblue")cuboid([70,70,10]) -// attach(TOP,BOT,align=RIGHT+BACK) -// cuboid(50) -// edge_profile_asym([BOT+FRONT, RIGHT+FRONT, TOP+RIGHT],corner_type="round") -// xflip()mask2d_roundover(10); -// Example(3D,NoScales): No top or bottom edges appear in the edge set, so the edges are oriented to joint smoothly to the FRONT and BACK -// color_this("lightblue") cuboid([90,10,50]) -// align(FWD) cuboid(50){ -// edge_profile_asym("Z",corner_type="round") -// xflip() mask2d_roundover(10); -// align(FWD) -// color_this("lightblue") cuboid([90,10,50]); -// } - -module edge_profile_asym( - edges=EDGES_ALL, except=[], - excess=0.01, convexity=10, - flip=false, corner_type="none", - size=[0,0] -) { - function _corner_orientation(pos,pvec) = - let( - j = [for (i=[0:2]) if (pvec[i]) i][0], - T = (pos.x>0? xflip() : ident(4)) * - (pos.y>0? yflip() : ident(4)) * - (pos.z>0? zflip() : ident(4)) * - rot(-120*(2-j), v=[1,1,1]) - ) T; - - function _default_edge_orientation(edge) = - edge.z < 0? [[-edge.x,-edge.y,0], UP] : - edge.z > 0? [[-edge.x,-edge.y,0], DOWN] : - edge.y < 0? [[-edge.x,0,0], BACK] : - [[-edge.x,0,0], FWD] ; - - function _edge_transition_needs_flip(from,to) = - let( - flip_edges = [ - [BOT+FWD, [FWD+LEFT, FWD+RIGHT]], - [BOT+BACK, [BACK+LEFT, BACK+RIGHT]], - [BOT+LEFT, []], - [BOT+RIGHT, []], - [TOP+FWD, [FWD+LEFT, FWD+RIGHT]], - [TOP+BACK, [BACK+LEFT, BACK+RIGHT]], - [TOP+LEFT, []], - [TOP+RIGHT, []], - [FWD+LEFT, [TOP+FWD, BOT+FWD]], - [FWD+RIGHT, [TOP+FWD, BOT+FWD]], - [BACK+LEFT, [TOP+BACK, BOT+BACK]], - [BACK+RIGHT, [TOP+BACK, BOT+BACK]], - ], - i = search([from], flip_edges, num_returns_per_match=1)[0], - check = assert(i!=[], "\nBad edge vector.") - ) in_list(to,flip_edges[i][1]); - - function _edge_corner_numbers(vec) = - let( - v2 = [for (i=idx(vec)) vec[i]? (vec[i]+1)/2*pow(2,i) : 0], - off = v2.x + v2.y + v2.z, - xs = [0, if (!vec.x) 1], - ys = [0, if (!vec.y) 2], - zs = [0, if (!vec.z) 4] - ) [for (x=xs, y=ys, z=zs) x+y+z + off]; - - function _gather_contiguous_edges(edge_corners) = - let( - no_tri_corners = all([for(cn = [0:7]) len([for (ec=edge_corners) if(in_list(cn,ec[1])) 1])<3]), - check = assert(no_tri_corners, "\nCannot have three edges that meet at the same corner.") - ) - _gather_contiguous_edges_r( - [for (i=idx(edge_corners)) if(i) edge_corners[i]], - edge_corners[0][1], - [edge_corners[0][0]], []); - - function _gather_contiguous_edges_r(edge_corners, ecns, curr, out) = - len(edge_corners)==0? [each out, curr] : - let( - i1 = [ - for (i = idx(edge_corners)) - if (in_list(ecns[0], edge_corners[i][1])) - i - ], - i2 = [ - for (i = idx(edge_corners)) - if (in_list(ecns[1], edge_corners[i][1])) - i - ] - ) !i1 && !i2? _gather_contiguous_edges_r( - [for (i=idx(edge_corners)) if(i) edge_corners[i]], - edge_corners[0][1], - [edge_corners[0][0]], - [each out, curr] - ) : let( - nu_curr = [ - if (i1) edge_corners[i1[0]][0], - each curr, - if (i2) edge_corners[i2[0]][0], - ], - nu_ecns = [ - if (!i1) ecns[0] else [ - for (ecn = edge_corners[i1[0]][1]) - if (ecn != ecns[0]) ecn - ][0], - if (!i2) ecns[1] else [ - for (ecn = edge_corners[i2[0]][1]) - if (ecn != ecns[1]) ecn - ][0], - ], - rem = [ - for (i = idx(edge_corners)) - if (i != i1[0] && i != i2[0]) - edge_corners[i] - ] - ) - _gather_contiguous_edges_r(rem, nu_ecns, nu_curr, out); - - function _edge_transition_inversions(edge_string) = - let( - // boolean cumulative sum - bcs = function(list, i=0, inv=false, out=[]) - i>=len(list)? out : - let( nu_inv = list[i]? !inv : inv ) - bcs(list, i+1, nu_inv, [each out, nu_inv]), - inverts = bcs([ - false, - for(i = idx(edge_string)) if (i) - _edge_transition_needs_flip( - edge_string[i-1], - edge_string[i] - ) - ]), - boti = [for(i = idx(edge_string)) if (edge_string[i].z<0) i], - topi = [for(i = idx(edge_string)) if (edge_string[i].z>0) i], - lfti = [for(i = idx(edge_string)) if (edge_string[i].x<0) i], - rgti = [for(i = idx(edge_string)) if (edge_string[i].x>0) i], - idx = [for (m = [boti, topi, lfti, rgti]) if(m) m[0]][0], - rinverts = inverts[idx] == false? inverts : [for (x = inverts) !x] - ) rinverts; - - function _is_closed_edge_loop(edge_string) = - let( - e1 = edge_string[0], - e2 = last(edge_string) - ) - len([for (i=[0:2]) if (abs(e1[i])==1 && e1[i]==e2[i]) 1]) == 1 && - len([for (i=[0:2]) if (e1[i]==0 && abs(e2[i])==1) 1]) == 1 && - len([for (i=[0:2]) if (e2[i]==0 && abs(e1[i])==1) 1]) == 1; - - function _edge_pair_perp_vec(e1,e2) = - [for (i=[0:2]) if (abs(e1[i])==1 && e1[i]==e2[i]) -e1[i] else 0]; - - req_children($children); - check1 = assert($parent_geom != undef, "\nNo object to attach to!") - assert(in_list(corner_type, ["none", "round", "chamfer", "sharp"])) - assert(is_bool(flip)); - edges = _edges(edges, except=except); - vecs = [ - for (i = [0:3], axis=[0:2]) - if (edges[axis][i]>0) - EDGE_OFFSETS[axis][i] - ]; - all_vecs_are_edges = all([for (vec = vecs) sum(v_abs(vec))==2]); - check2 = assert(all_vecs_are_edges, "\nAll vectors must be edges."); - edge_corners = [for (vec = vecs) [vec, _edge_corner_numbers(vec)]]; - edge_strings = _gather_contiguous_edges(edge_corners); - default_tag("remove") - for (edge_string = edge_strings) { - inverts = _edge_transition_inversions(edge_string); - flipverts = [for (x = inverts) flip? !x : x]; - vecpairs = [ - for (i = idx(edge_string)) - let (p = _default_edge_orientation(edge_string[i])) - flipverts[i]? [p.y,p.x] : p - ]; - is_loop = _is_closed_edge_loop(edge_string); - for (i = idx(edge_string)) { - if (corner_type!="none" && (i || is_loop)) { - e1 = select(edge_string,i-1); - e2 = select(edge_string,i); - vp1 = select(vecpairs,i-1); - vp2 = select(vecpairs,i); - pvec = _edge_pair_perp_vec(e1,e2); - pos = [for (i=[0:2]) e1[i]? e1[i] : e2[i]]; - mirT = _corner_orientation(pos, pvec); - $attach_to = undef; - $attach_anchor = _find_anchor(pos, $parent_geom); - $profile_type = "corner"; - position(pos) { - multmatrix(mirT) { - if (vp1.x == vp2.x && size.y > 0) { - zflip() { - if (corner_type=="chamfer") { - fn = $fn; - move([size.y,size.y]) { - rotate_extrude(angle=90, $fn=4) - left_half(planar=true, $fn=fn) - zrot(-90) fwd(size.y) children(); - } - difference() { - down(0.01) cube([size.x, size.x, size.y+0.01]); - move([size.x+0.01, size.x+0.01]) - zrot(180) - rotate_extrude(angle=90, $fn=4) - square([size.x+0.01, size.y+0.01]); - } - } else if (corner_type=="round") { - move([size.y,size.y]) { - rotate_extrude(angle=90) - left_half(planar=true) - zrot(-90) fwd(size.y) children(); - } - difference() { - down(0.01) cube([size.x, size.x, size.y+0.01]); - move([size.x+0.01, size.x+0.01]) - zrot(180) - rotate_extrude(angle=90) - square([size.x+0.01, size.y+0.01]); - } - } - } - } else if (vp1.y == vp2.y) { - if (corner_type=="chamfer") { - fn = $fn; - rotate_extrude(angle=90, $fn=4) - right_half(planar=true, $fn=fn) - children(); - rotate_extrude(angle=90, $fn=4) - left_half(planar=true, $fn=fn) - children(); - } else if (corner_type=="round") { - rotate_extrude(angle=90) - right_half(planar=true) - children(); - rotate_extrude(angle=90) - left_half(planar=true) - children(); - } else { //corner_type == "sharp" - intersection() { - rot([90,0, 0]) linear_extrude(height=100,center=true,convexity=convexity) children(); - rot([90,0,90]) linear_extrude(height=100,center=true,convexity=convexity) children(); - } - } - } - } - } - } - } - for (i = idx(edge_string)) { - $attach_to = undef; - $attach_anchor = _find_anchor(edge_string[i], $parent_geom); - $profile_type = "edge"; - edge_profile(edge_string[i], excess=excess, convexity=convexity) { - if (flipverts[i]) { - mirror([-1,1]) children(); - } else { - children(); - } - } - } - } -} - - - -// Module: corner_profile() -// Synopsis: Rotationally extrudes a 2d edge profile into corner mask on the given corners of the parent. -// SynTags: Geom -// Topics: Attachments, Masking -// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_mask(), face_mask(), edge_mask() -// Usage: -// PARENT() corner_profile([corners], [except], [r=|d=], [convexity=]) CHILDREN; -// Description: -// Takes a 2D mask shape, rotationally extrudes and converts it into a corner mask, and attaches it -// to the selected corners with the appropriate orientation. If no tag is set then `corner_profile()` -// sets the tag for children to "remove" so that it works with the default {{diff()}} tag. -// See [Specifying Corners](attachments.scad#subsection-specifying-corners) for information on how to specify corner sets. -// For a step-by-step explanation of masking attachments, see the [Attachments Tutorial](Tutorial-Attachment-Edge-Profiling). -// Arguments: -// corners = Corners to mask. See [Specifying Corners](attachments.scad#subsection-specifying-corners). Default: All corners. -// except = Corners to explicitly NOT mask. See [Specifying Corners](attachments.scad#subsection-specifying-corners). Default: No corners. -// --- -// r = Radius of corner mask. -// d = Diameter of corner mask. -// axis = Can be set to "X", "Y", or "Z" to specify the axis that the corner mask will be rotated around. Default: "Z" -// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10 -// Side Effects: -// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. -// `$idx` is set to the index number of each corner. -// `$attach_anchor` is set for each corner given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor. -// `$profile_type` is set to `"corner"`. -// Example: -// diff() -// cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) { -// corner_profile(TOP,r=10) -// mask2d_teardrop(r=10, angle=40); -// } -// Example: Rotate the mask around the X axis instead. -// diff() -// cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) { -// corner_profile(TOP,r=10,axis="X") -// mask2d_teardrop(r=10, angle=40); -// } -module corner_profile(corners=CORNERS_ALL, except=[], r, d, axis="Z", convexity=10) { - check1 = assert($parent_geom != undef, "\nNo object to attach to!"); - r = max(0.01, get_radius(r=r, d=d, dflt=undef)); - check2 = assert(is_num(r), "\nBad r/d argument."); - corners = _corners(corners, except=except); - vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]]; - all_vecs_are_corners = all([for (vec = vecs) sum(v_abs(vec))==3]); - check3 = assert(all_vecs_are_corners, "\nAll vectors must be corners."); - module rot_to_axis(axis) { - if (axis == "X") - rot(120, v=[1,1,1]) children(); - else if (axis == "Y") - rot(-120, v=[1,1,1]) children(); - else - children(); - } - module mirror_if(cond,plane) { - if (cond) mirror(plane) children(); - else children(); - } - module mirror_to_corner(corner) { - mirror_if(corner.x > 0, RIGHT) - mirror_if(corner.y > 0, BACK) - mirror_if(corner.z > 0, UP) - children(); - } - module corner_round_mask2d(r) { - excess = 0.01; - path = [ - [-excess,-excess], - [-excess, r], - each arc(cp=[r,r], r=r, start=180, angle=90), - [r, -excess] - ]; - polygon(path); - } - for ($idx = idx(vecs)) { - vec = vecs[$idx]; - anch = _find_anchor(vec, $parent_geom); - $attach_to = undef; - $attach_anchor = anch; - $profile_type = "corner"; - default_tag("remove") attachable() { - translate(anch[1]) { - mirror_to_corner(vec) { - rot_to_axis(axis) { - down(0.01) { - linear_extrude(height=r+0.01, center=false, convexity=convexity) { - corner_round_mask2d(r); - } - } - translate([r,r]) zrot(180) { - rotate_extrude(angle=90, convexity=convexity) { - right(r) xflip() { - children(); - } - } - } - } - } - } - union(); - } - } -} - - // Section: Making your objects attachable @@ -2994,14 +2204,14 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, axis="Z", convexity= // } // // Example(NORENDER): Extruded Polygon Shape, by Extents -// attachable(anchor, spin, orient, path=path, l=length) { +// attachable(anchor, spin, orient, path=path, l=length, scale=scale) { // linear_extrude(height=length, center=true) // polygon(path); // children(); // } // // Example(NORENDER): Extruded Polygon Shape, by Intersection -// attachable(anchor, spin, orient, path=path, l=length, extent=false) { +// attachable(anchor, spin, orient, path=path, l=length, extent=false, scale=scale) { // linear_extrude(height=length, center=true) // polygon(path); // children(); @@ -3192,7 +2402,7 @@ module attachable( anchor, spin, orient, size, size2, shift, r,r1,r2, d,d1,d2, l,h, - vnf, path, region, + vnf, path, region, scale, extent=true, cp=[0,0,0], offset=[0,0,0], @@ -3219,7 +2429,7 @@ module attachable( attach_geom( size=size, size2=size2, shift=shift, r=r, r1=r1, r2=r2, h=h, - d=d, d1=d1, d2=d2, l=l, + d=d, d1=d1, d2=d2, l=l, scale=scale, vnf=vnf, region=region, extent=extent, cp=cp, offset=offset, anchors=anchors, two_d=two_d, axis=axis, override=override diff --git a/std.scad b/std.scad index aca3480..f8a71eb 100644 --- a/std.scad +++ b/std.scad @@ -20,8 +20,7 @@ include include include include -include -include +include include include include diff --git a/tutorials/Rounding_the_Cube.md b/tutorials/Rounding_the_Cube.md index 498a388..b7947b0 100644 --- a/tutorials/Rounding_the_Cube.md +++ b/tutorials/Rounding_the_Cube.md @@ -179,7 +179,7 @@ cuboid(50){ } ``` -See [mask2d_roundover()](https://github.com/BelfrySCAD/BOSL2/wiki/masks2d.scad#functionmodule-mask2d_roundover) for additional mask parameters. Here we use the *inset* parameter to produce a bead. +See [mask2d_roundover()](https://github.com/BelfrySCAD/BOSL2/wiki/masks.scad#functionmodule-mask2d_roundover) for additional mask parameters. Here we use the *inset* parameter to produce a bead. ```openscad-3D include @@ -189,7 +189,7 @@ diff() mask2d_roundover(h=12, inset=4); ``` -You can use [edge-profile()](https://github.com/BelfrySCAD/BOSL2/wiki/attachments.scad#module-edge_profile) to round the top or bottom of a [prismoid()](https://github.com/BelfrySCAD/BOSL2/wiki/shapes3d.scad#functionmodule-prismoid). Because the side faces of a [prismoid()](https://github.com/BelfrySCAD/BOSL2/wiki/shapes3d.scad#functionmodule-prismoid) are not strictly vertical, it's is necessary to increase the length of the masks using the *excess* parameter in [edge_profile()](https://github.com/BelfrySCAD/BOSL2/wiki/attachments.scad#module-edge_profile), and to set the mask\_angle to $edge\_angle in [mask2d\_roundover()](https://github.com/BelfrySCAD/BOSL2/wiki/masks2d.scad#functionmodule-mask2d_roundover). +You can use [edge-profile()](https://github.com/BelfrySCAD/BOSL2/wiki/attachments.scad#module-edge_profile) to round the top or bottom of a [prismoid()](https://github.com/BelfrySCAD/BOSL2/wiki/shapes3d.scad#functionmodule-prismoid). Because the side faces of a [prismoid()](https://github.com/BelfrySCAD/BOSL2/wiki/shapes3d.scad#functionmodule-prismoid) are not strictly vertical, it's is necessary to increase the length of the masks using the *excess* parameter in [edge_profile()](https://github.com/BelfrySCAD/BOSL2/wiki/attachments.scad#module-edge_profile), and to set the mask\_angle to $edge\_angle in [mask2d\_roundover()](https://github.com/BelfrySCAD/BOSL2/wiki/masks.scad#functionmodule-mask2d_roundover). ```openscad-3D include @@ -219,7 +219,7 @@ diff() mask2d_roundover(h = 20, $fn = 64); ``` -The [mask2d_teardrop()](https://github.com/BelfrySCAD/BOSL2/wiki/masks2d.scad#functionmodule-mask2d_teardrop) mask can be used to round the bottom of a cube-like object. It limits the overhang angle to 45° or a value you specify in with the **angle** argument. +The [mask2d_teardrop()](https://github.com/BelfrySCAD/BOSL2/wiki/masks.scad#functionmodule-mask2d_teardrop) mask can be used to round the bottom of a cube-like object. It limits the overhang angle to 45° or a value you specify in with the **angle** argument. ``` include @@ -237,7 +237,7 @@ diff() mask2d_teardrop(h = 5, angle = 50, mask_angle = $edge_angle, $fn = 64); ``` -In addition to the simple [roundover](https://github.com/BelfrySCAD/BOSL2/wiki/masks2d.scad#functionmodule-mask2d_roundover) mask, and the [teardrop](https://github.com/BelfrySCAD/BOSL2/wiki/masks2d.scad#functionmodule-mask2d_teardrop) mask, there are masks for [cove](https://github.com/BelfrySCAD/BOSL2/wiki/masks2d.scad#functionmodule-mask2d_cove), [chamfer](https://github.com/BelfrySCAD/BOSL2/wiki/masks2d.scad#functionmodule-mask2d_chamfer), [rabbet](https://github.com/BelfrySCAD/BOSL2/wiki/masks2d.scad#functionmodule-mask2d_rabbet), [dovetail](https://github.com/BelfrySCAD/BOSL2/wiki/masks2d.scad#functionmodule-mask2d_dovetail) and [ogee](https://github.com/BelfrySCAD/BOSL2/wiki/masks2d.scad#functionmodule-mask2d_ogee) edges. +In addition to the simple [roundover](https://github.com/BelfrySCAD/BOSL2/wiki/masks.scad#functionmodule-mask2d_roundover) mask, and the [teardrop](https://github.com/BelfrySCAD/BOSL2/wiki/masks.scad#functionmodule-mask2d_teardrop) mask, there are masks for [cove](https://github.com/BelfrySCAD/BOSL2/wiki/masks.scad#functionmodule-mask2d_cove), [chamfer](https://github.com/BelfrySCAD/BOSL2/wiki/masks.scad#functionmodule-mask2d_chamfer), [rabbet](https://github.com/BelfrySCAD/BOSL2/wiki/masks.scad#functionmodule-mask2d_rabbet), [dovetail](https://github.com/BelfrySCAD/BOSL2/wiki/masks.scad#functionmodule-mask2d_dovetail) and [ogee](https://github.com/BelfrySCAD/BOSL2/wiki/masks.scad#functionmodule-mask2d_ogee) edges. The mask2d_ogee() only works on [cube()](https://github.com/BelfrySCAD/BOSL2/wiki/shapes3d.scad#functionmodule-cube) and [cuboid()](https://github.com/BelfrySCAD/BOSL2/wiki/shapes3d.scad#module-cuboid) shapes, or a [prismoid()](https://github.com/BelfrySCAD/BOSL2/wiki/shapes3d.scad#functionmodule-prismoid) where size2 >= size1 in both the X and Y dimensions.