masks reorg

This commit is contained in:
Adrian Mariano 2025-10-15 20:01:17 -04:00
parent b657290835
commit 4b533144a9
3 changed files with 11 additions and 802 deletions

View file

@ -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

View file

@ -20,8 +20,7 @@ include <beziers.scad>
include <shapes3d.scad>
include <shapes2d.scad>
include <drawing.scad>
include <masks3d.scad>
include <masks2d.scad>
include <masks.scad>
include <math.scad>
include <paths.scad>
include <lists.scad>

View file

@ -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 <BOSL2/std.scad>
@ -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<BOSL2/std.scad>
@ -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<BOSL2/std.scad>
@ -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.