Merge pull request #1828 from adrianVmariano/master

masks reorg + polygon_edge_mask()
This commit is contained in:
adrianVmariano 2025-10-15 22:02:47 -04:00 committed by GitHub
commit fc2e57cf56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 2627 additions and 2541 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

@ -2788,7 +2788,7 @@ function worm(
helical=helical,
profile_shift=0
), 1, -2),
ff=echo(tooth=tooth, rack_profile=rack_profile,nrp=nrp),
// ff=echo(tooth=tooth, rack_profile=rack_profile,nrp=nrp),
steps = max(36, segs(d/2)),
step = 360 / steps,
zsteps = ceil(l / trans_pitch / starts * steps),
@ -3142,7 +3142,7 @@ function worm_gear(
)
assert(is_finite(worm_diam) && worm_diam>0)
assert(is_integer(teeth) && teeth>7)
assert(is_finite(worm_arc) && worm_arc>0 && worm_arc <= 60)
// assert(is_finite(worm_arc) && worm_arc>0 && worm_arc <= 60)
assert(is_integer(worm_starts) && worm_starts>0)
assert(is_bool(left_handed))
assert(is_finite(backlash))
@ -3153,31 +3153,43 @@ function worm_gear(
gear_arc = 2 * PA,
helical = asin(worm_starts * circ_pitch / PI / worm_diam),
//fee=echo(helical=helical),
full_tooth = apply(
zrot(90) * scale(0.99),
_gear_tooth_profile(
full_tooth = path3d(reverse(zrot(90, _gear_tooth_profile(
circ_pitch, teeth=teeth,
pressure_angle=PA,
profile_shift=-profile_shift,
clearance=clearance,
profile_shift=profile_shift,
clearance=clearance,shorten=.3,
helical=helical, internal=false,
center=true
)
),
center=true)))),
bnd = pointlist_bounds(full_tooth),
fdeewqqq= echo(toothbounds = bnd)echo(toothlength = bnd[1].x-bnd[0].x),
tooth_bot = pointlist_bounds(full_tooth)[1].x,
ftl = len(full_tooth),
tooth_half1 = (select(full_tooth, 0, ftl/2-1)),
tooth_half2 = (select(full_tooth, ftl/2, -1)),
tooth_half1 = select(full_tooth, 0, ftl/2-1),
tooth_half2 = select(full_tooth, ftl/2, -1),
//eer= echo(full_tooth=full_tooth),
//fdewq= echo(tooth_half1=tooth_half1)echo(tooth_half2=tooth_half2),
tang = 360 / teeth,
rteeth = (quantdn(teeth * gear_arc / 360, 2) / 2 + 0.5),
pr = pitch_radius(circ_pitch, teeth, helical=helical),
//feee=echo(pr_worm = pr, circ_pitch, teeth, helical),
circum = 2*PI*pr,
tan_helical = tan(helical),
// Compute thickness based on tooth form and worm_arc for no crowning
half_thickness = sin(worm_arc/2)*(worm_diam/2+tooth_bot),
// Update worm_arc to account for crowning and produce same thickness
worm_arc = 2*asin(half_thickness / (worm_diam/2 + crowning + tooth_bot)),
feee=echo(pr_worm = pr, teeth=teeth ,helical= helical),
// When multiplied by z this gives the spin required to rotationally shear
// a straight tooth so that it follows the specified helical angle.
shear_angle_per_z = (left_handed?-1:1) * 360 * tan(helical) / (2*PI*pr),
oslices = slices * 4,
// Tooth is in xy plane, centered, pointing in Y+ direction
//
rows = [
for (data = [[tooth_half1,1], [tooth_half2,-1]])
let (
tooth_half = (data[0]),
tooth_half = data[0],
dir = data[1]
)
for (pt = tooth_half) [
@ -3185,15 +3197,16 @@ function worm_gear(
let (
u = i / oslices,
w_ang = worm_arc * (u - 0.5),
g_ang_delta = w_ang/360 * tang * worm_starts * (left_handed?1:-1) *0 ,
m = //zrot(dir*rteeth*tang+g_ang_delta, cp=[worm_diam/2+pr,0,0]) *
left(crowning) *
m = left(crowning) *
yrot(w_ang) *
right(worm_diam/2+crowning) *
//zrot(-dir*rteeth*tang+g_ang_delta, cp=[pr,0,0]) *
xrot(180),
pt = apply(m, point3d(pt)),
angled_pt = zrot((left_handed?-1:1)*360*pt.z*tan_helical/circum,pt,cp=[worm_diam/2+pr,0,0])
right(worm_diam/2+crowning),
pt = apply(m, pt),
L = 2*PI*worm_diam/2*w_ang/360 * tan(helical),
// zang = -360*L/(2*PI*pr),
// zang = -asin(L/pr),
// angled_pt = zrot(shear_angle_per_z * pt.z, pt, cp=[worm_diam/2+pr,0,0])
// angled_pt = zrot(zang, pt, cp=[worm_diam/2+pr,0,0])
angled_pt = back(L,pt)
) angled_pt
]
],
@ -3226,6 +3239,7 @@ function worm_gear(
)
get_thickness? zmax*2 :
let(
feef=echo(actual_thick=zmax*2, est=half_thickness*2),
gear_rows = [
for (i = [0:1:teeth-1])
let(
@ -3271,7 +3285,7 @@ module worm_gear(
assert(is_integer(teeth) && teeth>10)
assert(is_finite(worm_diam) && worm_diam>0)
assert(is_integer(worm_starts) && worm_starts>0)
assert(is_finite(worm_arc) && worm_arc>0 && worm_arc<90)
// assert(is_finite(worm_arc) && worm_arc>0 && worm_arc<90)
assert(is_finite(crowning) && crowning>=0)
assert(is_bool(left_handed))
assert(is_finite(PA) && PA>=0 && PA<90, "Bad pressure_angle value.")
@ -3371,6 +3385,7 @@ function _gear_tooth_profile(
// Calculate the important circle radii
arad = outer_radius(circ_pitch, teeth, helical=helical, profile_shift=profile_shift, internal=internal, shorten=shorten),
ffe=echo(arad=arad),
prad = pitch_radius(circ_pitch, teeth, helical=helical),
brad = _base_radius(circ_pitch, teeth, pressure_angle, helical=helical),
rrad = _root_radius_basic(circ_pitch, teeth, clear, helical=helical, profile_shift=profile_shift, internal=internal),
@ -3989,7 +4004,6 @@ function outer_radius(circ_pitch, teeth, clearance, internal=false, helical=0, p
// root radius so that you can, for example, place a partial tooth gear onto a matching circle. The `backlash` parameter may seem
// unnecessary, but when large pressure angle teeth are clipped, the value of backlash changes the clipping radius. For regular
// gear teeth, `backlash` has no effect on the radius.
// Arguments:
// teeth = The number of teeth on the gear.
// helical = The helical angle (from vertical) of the teeth on the gear. Default: 0

2571
masks.scad Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,707 +0,0 @@
//////////////////////////////////////////////////////////////////////
// LibFile: masks3d.scad
// This file defines 3D masks for applying chamfers, roundovers, and teardrop roundovers to straight edges and circular
// edges in three dimensions.
// Includes:
// include <BOSL2/std.scad>
// FileGroup: Basic Modeling
// FileSummary: 3D masks for rounding or chamfering edges and corners.
// FileFootnotes: STD=Included in std.scad
//////////////////////////////////////////////////////////////////////
// Section: Chamfer Masks
// Module: chamfer_edge_mask()
// Synopsis: Creates a shape to chamfer a 90° edge.
// SynTags: Geom
// Topics: Masking, Chamfers, Shapes (3D)
// See Also: chamfer_corner_mask(), chamfer_cylinder_mask(), chamfer_edge_mask(), default_tag(), diff()
// Usage:
// chamfer_edge_mask(l|h=|length=|height=, chamfer, [excess]) [ATTACHMENTS];
// Description:
// Creates a shape that can be used to chamfer a 90° edge.
// Difference it from the object to be chamfered. The center of
// the mask object should align exactly with the edge to be chamfered.
// Arguments:
// l/h/length/height = Length of mask. Default: $edge_length if defined
// chamfer = Size of chamfer.
// excess = The extra amount to add to the length of the mask so that it differences away from other shapes cleanly. Default: `0.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`
// Side Effects:
// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
// Example:
// chamfer_edge_mask(l=50, chamfer=10);
// Example:
// difference() {
// cube(50, anchor=BOTTOM+FRONT);
// #chamfer_edge_mask(l=50, chamfer=10, orient=RIGHT);
// }
// Example: Masking by Attachment
// diff()
// cube(50, center=true) {
// edge_mask(TOP+RIGHT)
// #chamfer_edge_mask(l=50, chamfer=10);
// }
function chamfer_edge_mask(l, chamfer=1, excess=0.1, h, length, height, anchor=CENTER, spin=0, orient=UP) = no_function("chamfer_edge_mask");
module chamfer_edge_mask(l, chamfer=1, excess=0.1, h, length, height, anchor=CENTER, spin=0, orient=UP) {
l = is_def($edge_length) && !any_defined([l,length,h,height]) ? $edge_length
: one_defined([l,length,h,height],"l,length,h,height");
default_tag("remove") {
attachable(anchor,spin,orient, size=[chamfer*2, chamfer*2, l]) {
cylinder(r=chamfer, h=l+excess, center=true, $fn=4);
children();
}
}
}
// Module: chamfer_corner_mask()
// Synopsis: Creates a shape to chamfer a 90° corner.
// SynTags: Geom
// Topics: Masking, Chamfers, Shapes (3D)
// See Also: chamfer_corner_mask(), chamfer_cylinder_mask(), chamfer_edge_mask(), default_tag(), diff()
// Usage:
// chamfer_corner_mask(chamfer) [ATTACHMENTS];
// Description:
// Creates a shape that can be used to chamfer a 90° corner.
// Difference it from the object to be chamfered. The center of
// the mask object should align exactly with the corner to be chamfered.
// Arguments:
// chamfer = Size of chamfer.
// ---
// 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`
// Side Effects:
// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
// Example:
// chamfer_corner_mask(chamfer=10);
// Example:
// difference() {
// cuboid(50, chamfer=10, trimcorners=false);
// move(25*[1,-1,1]) #chamfer_corner_mask(chamfer=10);
// }
// Example: Masking by Attachment
// diff()
// cuboid(100, chamfer=20, trimcorners=false) {
// corner_mask(TOP+FWD+RIGHT)
// chamfer_corner_mask(chamfer=20);
// }
// Example: Anchors
// chamfer_corner_mask(chamfer=20)
// show_anchors();
function chamfer_corner_mask(chamfer=1, anchor=CENTER, spin=0, orient=UP) = no_function("chamfer_corner_mask");
module chamfer_corner_mask(chamfer=1, anchor=CENTER, spin=0, orient=UP) {
default_tag("remove") {
octahedron(chamfer*4, anchor=anchor, spin=spin, orient=orient) children();
}
}
// Module: chamfer_cylinder_mask()
// Synopsis: Creates a shape to chamfer the end of a cylinder.
// SynTags: Geom
// Topics: Masking, Chamfers, Cylinders
// See Also: chamfer_corner_mask(), chamfer_cylinder_mask(), chamfer_edge_mask(), default_tag(), diff()
// Usage:
// chamfer_cylinder_mask(r|d=, chamfer, [ang], [from_end]) [ATTACHMENTS];
// Description:
// Create a mask that can be used to bevel/chamfer the end of a cylindrical region.
// Difference it from the end of the region to be chamfered. The center of the mask
// object should align exactly with the center of the end of the cylindrical region
// to be chamfered.
// Arguments:
// r = Radius of cylinder to chamfer.
// chamfer = Size of the edge chamfered, inset from edge.
// ---
// d = Diameter of cylinder to chamfer. Use instead of r.
// ang = Angle of chamfer in degrees from the horizontal. (Default: 45)
// from_end = If true, chamfer size is measured from end of cylinder. If false, chamfer is measured outset from the radius of the cylinder. (Default: false)
// 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`
// Side Effects:
// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
// Example:
// difference() {
// cylinder(r=50, h=100, center=true);
// up(50) #chamfer_cylinder_mask(r=50, chamfer=10);
// }
// Example:
// difference() {
// cylinder(r=50, h=100, center=true);
// up(50) chamfer_cylinder_mask(r=50, chamfer=10);
// }
// Example: Changing the chamfer angle
// difference() {
// cylinder(r=50, h=100, center=true);
// up(50) #chamfer_cylinder_mask(r=50, chamfer=10, ang=70);
// }
// Example:
// difference() {
// cylinder(r=50, h=100, center=true);
// up(50) chamfer_cylinder_mask(r=50, chamfer=10, ang=70);
// }
// Example: Masking by Attachment
// diff()
// cyl(d=100,h=40)
// attach([TOP,BOT])
// tag("remove")chamfer_cylinder_mask(d=100, chamfer=10);
function chamfer_cylinder_mask(r, chamfer, d, ang=45, from_end=false, anchor=CENTER, spin=0, orient=UP) = no_function("chamfer_cylinder_mask");
module chamfer_cylinder_mask(r, chamfer, d, ang=45, from_end=false, anchor=CENTER, spin=0, orient=UP)
{
r = get_radius(r=r, d=d, dflt=1);
dummy = assert(all_nonnegative([chamfer]), "Chamfer must be a nonnegative number");
ch = from_end? chamfer : opp_ang_to_adj(chamfer,90-ang);
default_tag("remove"){
attachable(anchor,spin,orient, r=r, l=ch*2) {
difference() {
cyl(r=r+chamfer, l=ch*2, anchor=CENTER);
cyl(r=r, l=ch*3, chamfer=chamfer, chamfang=ang, from_end=from_end, anchor=TOP);
}
children();
}
}
}
// Section: Rounding Masks
// Module: rounding_edge_mask()
// Synopsis: Creates a shape to round an arbitrary 3d edge.
// SynTags: Geom
// Topics: Masks, Rounding, Shapes (3D)
// See Also: edge_profile(), rounding_corner_mask(), default_tag(), diff()
// Usage:
// rounding_edge_mask(l|h=|length=|height=, r|d=, [ang], [excess=], [rounding=|chamfer=], ) [ATTACHMENTS];
// rounding_edge_mask(l|h=|length=|height=, r1=|d1=, r2=|d2=, [ang=], [excess=], [rounding=|chamfer=]) [ATTACHMENTS];
// Description:
// Creates a mask shape that can be used to round a straight edge at any angle, with
// different rounding radii at each end. The corner of the mask appears on the Z axis with one face on the XZ plane.
// You must align the mask corner with the edge you want to round. If your parent object is a cuboid, the easiest way to
// do this is to use {{diff()}} and {{edge_mask()}}. However, this method is somewhat inflexible regarding orientation of a tapered
// mask, and it does not support other parent shapes. You can attach the mask to a larger range of shapes using
// {{attach()}} to anchor the `LEFT+FWD` anchor of the mask to a desired corner on the parent with `inside=true`.
// Many shapes propagate `$edge_angle` and `$edge_length` which can aid in configuring the mask, and you can adjust the
// mask as needed to align the taper as desired. The default "remove" tag is set so {{diff()}} will automatically difference
// away the mask. You can of course also position the mask manually and use `difference()`.
// .
// For mating with other roundings or chamfers on cuboids or regular prisms, you can choose end roundings and end chamfers. These affect
// only the curved edge of the mask ends and will only work if the terminating face is perpendicular to the masked edge. The `excess`
// parameter will add extra length to the mask when you use these settings.
//
// Arguments:
// l/h/length/height = Length of mask. Default: $edge_length if defined
// r = Radius of the rounding.
// ang = Angle between faces for rounding. Default: $edge_angle if defined, otherwise 90
// ---
// r1 = Bottom radius of rounding.
// r2 = Top radius of rounding.
// d = Diameter of the rounding.
// d1 = Bottom diameter of rounding.
// d2 = Top diameter of rounding.
// excess = Extra size for the mask. Defaults: 0.1
// rounding = Radius of roundong along ends. Default: 0
// rounding1 = Radius of rounding along bottom end
// rounding2 = Radius of rounding along top end
// chamfer = Chamfer size of end chamfers. Default: 0
// chamfer1 = Chamfer size of chamfer at bottom end
// chamfer2 = Chamfer size of chamfer at top end
// 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`
// Side Effects:
// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
// Example(VPD=200,VPR=[55,0,120]):
// rounding_edge_mask(l=50, r=15);
// Example(VPD=200,VPR=[55,0,120]): With different radii at each end
// rounding_edge_mask(l=50, r1=10, r2=25);
// Example(VPD=200,VPR=[55,0,120]): Acute angle
// rounding_edge_mask(l=50, r=10, ang=45);
// Example(VPD=200,VPR=[55,0,120]): A large excess
// rounding_edge_mask(l=50, r=15,excess=4);
// Example: Subtracting from a cube
// difference() {
// cube(size=100, center=false);
// #rounding_edge_mask(l=100, r=25, anchor=BOTTOM);
// }
// Example: Varying Rounding Radius
// difference() {
// cube(size=50, center=false);
// down(1)rounding_edge_mask(l=52, r1=25, r2=10, anchor=BOTTOM);
// }
// Example: Angle not 90 degrees
// difference() {
// pie_slice(ang=70, h=50, d=100, center=true);
// #rounding_edge_mask(h=51, r=20.0, ang=70, $fn=32);
// }
// Example: Varying Rounding Radius
// difference() {
// pie_slice(ang=70, h=50, d=100, center=true);
// #rounding_edge_mask(h=51, r1=10, r2=25, ang=70, $fn=32);
// }
// Example: Rounding a non-right angled edge, with a zero radius at the bottom.
// difference(){
// linear_extrude(height=50)xflip(x=25)right_triangle([50,50]);
// rounding_edge_mask(l=51, ang=45, r1=0, r2=15, anchor=BOT);
// }
// Example: Masking by Attachment
// diff()
// cube(100, center=true)
// edge_mask(FRONT+RIGHT)
// #rounding_edge_mask(l=$parent_size.z+0.01, r=25);
// Example: Multiple Masking by Attachment
// diff()
// cube([80,90,100], center=true) {
// let(p = $parent_size*1.01) {
// edge_mask(TOP)
// rounding_edge_mask(l=p.z, r=25);
// }
// }
// Example(3D,VPT=[5.02872,6.37039,-0.503894],VPR=[75.3,0,107.4],VPD=74.4017): Mask shape with end rounding at the top, chamfer at the bottom, and a large excess value:
// rounding_edge_mask(r=10,h=20, chamfer1=3, rounding2=3, excess=1);
// Example(3D,VPT=[1.05892,1.10442,2.20513],VPR=[60.6,0,118.1],VPD=74.4017): Attaching masks using {{attach()}} with automatic angle and length from the parent. Note that sometimes the automatic length is too short because it is the length of the edge itself.
// diff()
// prismoid([20,30],[12,19], h=10,shift=[4,7])
// attach([TOP+RIGHT,RIGHT+FRONT],LEFT+FWD,inside=true)
// rounding_edge_mask(r1=2,r2=4);
// Example(3D): The mask does not need to be the full length of the edge
// diff()
// cuboid(20)
// attach(RIGHT+TOP,LEFT+FWD,inside=true,inset=-.1,align=FWD)
// rounding_edge_mask(r1=0,r2=10,length=10);
function rounding_edge_mask(l, r, ang=90, r1, r2, d, d1, d2, excess=0.1, anchor=CENTER, spin=0, orient=UP, h,height,length) = no_function("rounding_edge_mask");
module rounding_edge_mask(l, r, ang, r1, r2, excess=0.01, d1, d2,d,r,length, h, height, anchor=CENTER, spin=0, orient=UP,
rounding,rounding1,rounding2,chamfer,chamfer1,chamfer2,
_remove_tag=true)
{
ang = first_defined([ang,$edge_angle,90]);
length = is_def($edge_length) && !any_defined([l,length,h,height]) ? $edge_length
: one_defined([l,length,h,height],"l,length,h,height");
r1 = get_radius(r1=r1, d1=d1,d=d,r=r);
r2 = get_radius(r2=r2, d1=d2,d=d,r=r);
dummy1 = assert(num_defined([chamfer,rounding])<2, "Cannot give both rounding and chamfer")
assert(num_defined([chamfer1,rounding1])<2, "Cannot give both rounding1 and chamfer1")
assert(num_defined([chamfer2,rounding2])<2, "Cannot give both rounding2 and chamfer2");
rounding1 = first_defined([rounding1,rounding,0]);
rounding2 = first_defined([rounding2,rounding,0]);
chamfer1 = first_defined([chamfer1,chamfer,0]);
chamfer2 = first_defined([chamfer2,chamfer,0]);
dummy = assert(all_nonnegative([r1,r2]), "radius/diameter value(s) must be nonnegative")
assert(all_positive([length]), "length/l/h/height must be a positive value")
assert(is_finite(ang) && ang>0 && ang<180, "ang must be a number between 0 and 180")
assert(all_nonnegative([chamfer1,chamfer2,rounding1,rounding2]), "chamfers and roundings must be nonnegative");
steps = max(2,segs(max(r1,r2), 180-ang));
function make_path(r) =
r==0 ? repeat([0,0],steps+1)
: arc(n=steps+1, r=r, corner=[polar_to_xy(r,ang),[0,0],[r,0]]);
path1 = path3d(make_path(r1),-length/2);
path2 = path3d(make_path(r2),length/2);
function getarc(bigr,r,chamfer,p1,p2,h,print=false) =
r==0 && chamfer==0? [p2]
:
let(
steps = ceil(segs(r)/4)+1,
center = [bigr/tan(ang/2), bigr,h],
refplane = plane_from_normal([-(p2-center).y, (p2-center).x, 0], p2),
refnormal = plane_normal(refplane),
mplane = plane3pt(p2,p1,center),
A = plane_normal(mplane),
basept = lerp(p2,p1,max(r,chamfer)/2/h),
corner = [basept+refnormal*(refplane[3]-basept*refnormal)/(refnormal*refnormal),
p2,
center],
bare_arc = chamfer ? [p2+chamfer*unit(corner[0]-corner[1]),p2+chamfer*unit(corner[2]-corner[1])]
: arc(r=r, corner = corner, n=steps),
arc_with_excess = [each bare_arc, up(excess, last(bare_arc))],
arc = [for(pt=arc_with_excess) pt+refnormal*(mplane[3]-pt*A)/(refnormal*A)]
)
arc;
cp = [-excess/tan(ang/2), -excess];
extra1 = rounding1 || chamfer1 ? [0,0,excess] : CTR;
extra2 = rounding2 || chamfer2 ? [0,0,excess] : CTR;
pathlist = [for(i=[0:len(path1)-1])
let(
path = [
if (i==0) move(polar_to_xy( excess, 90+ang),path1[i]-extra1)
else if (i==len(path1)-1) fwd(excess,last(path1)-extra1)
else point3d(cp,-length/2-extra1.z),
each reverse(zflip(getarc(r1,rounding1,chamfer1,zflip(path2[i]), zflip(path1[i]),length/2))),
each getarc(r2,rounding2,chamfer2,path1[i],path2[i],length/2,print=rounding2!=0&&!is_undef(rounding2)&&i==3),
if (i==0) move(polar_to_xy( excess, 90+ang),path2[i]+extra2)
else if (i==len(path2)-1) fwd(excess,last(path2)+extra2)
else point3d(cp, length/2+extra2.z),
]
)
path];
left_normal = cylindrical_to_xyz(1,90+ang,0);
left_dir = cylindrical_to_xyz(1,ang,0);
zdir = unit([length, 0,-(r2-r1)/tan(ang/2)]);
cutfact = 1/sin(ang/2)-1;
v=unit(zrot(ang,zdir)+left_normal);
ref = UP - (v*UP)*v;
backleft_spin=-vector_angle(rot(from=UP,to=v,p=BACK),ref);
override = [
[CENTER, [CENTER,UP]],
[TOP, [[0,0,length/2]]],
[BOT, [[0,0,-length/2]]],
[FWD, [[(r1+r2)/tan(ang/2)/4,0,0]]],
[FWD+BOT, [[r1/tan(ang/2)/2,0,-length/2]]],
[FWD+TOP, [[r2/tan(ang/2)/2,0,length/2]]],
[LEFT, [(r1+r2)/tan(ang/2)/4*left_dir, left_normal,ang-180]],
[LEFT+BOT, [down(length/2,r1/tan(ang/2)/2*left_dir), rot(v=left_dir,-45,p=left_normal),ang-180]],
[LEFT+TOP, [up(length/2,r2/tan(ang/2)/2*left_dir), rot(v=left_dir, 45, p=left_normal),ang-180]],
[LEFT+FWD, [CENTER, left_normal+FWD,ang/2-90]],
[LEFT+FWD+TOP, [[0,0,length/2], left_normal+FWD+UP,ang/2-90]],
[LEFT+FWD+BOT, [[0,0,-length/2], left_normal+FWD+DOWN,ang/2-90]],
[RIGHT, [[(r1+r2)/2/tan(ang/2),0,0],zdir]],
[RIGHT+TOP, [[r2/tan(ang/2),0,length/2],zdir+UP]],
[RIGHT+BOT, [[r1/tan(ang/2),0,-length/2],zdir+DOWN]],
[RIGHT+FWD, [[(r1+r2)/2/tan(ang/2),0,0],zdir+FWD]],
[RIGHT+TOP+FWD, [[r2/tan(ang/2),0,length/2],zdir+UP+FWD]],
[RIGHT+BOT+FWD, [[r1/tan(ang/2),0,-length/2],zdir+DOWN+FWD]],
[BACK, [ (r1+r2)/2/tan(ang/2)*left_dir,zrot(ang,zdir),ang+90]],
[BACK+BOT, [ down(length/2,r1/tan(ang/2)*left_dir),zrot(ang,zdir)+DOWN,ang+90]],
[BACK+UP, [ up(length/2,r2/tan(ang/2)*left_dir),zrot(ang,zdir)+UP,ang+90]],
[BACK+LEFT, [ (r1+r2)/2/tan(ang/2)*left_dir,zrot(ang,zdir)+left_normal, backleft_spin]],
[BACK+BOT+LEFT, [ down(length/2,r1/tan(ang/2)*left_dir),zrot(ang,zdir)+left_normal+DOWN,backleft_spin]],
[BACK+UP+LEFT, [ up(length/2,r2/tan(ang/2)*left_dir),zrot(ang,zdir)+left_normal+UP,backleft_spin]],
[BACK+RIGHT, [cylindrical_to_xyz(cutfact*(r1+r2)/2,ang/2,0), zrot(ang/2,zdir),ang/2+90]],
[BACK+RIGHT+TOP, [cylindrical_to_xyz(cutfact*r2,ang/2,length/2), zrot(ang/2,zdir)+UP,ang/2+90]],
[BACK+RIGHT+BOT, [cylindrical_to_xyz(cutfact*r1,ang/2,-length/2), zrot(ang/2,zdir)+DOWN,ang/2+90]],
];
vnf = vnf_vertex_array(reverse(pathlist), col_wrap=true,caps=true);
default_tag("remove", _remove_tag)
attachable(anchor,spin,orient,size=[1,1,length],override=override){
vnf_polyhedron(vnf);
children();
}
}
// Module: rounding_corner_mask()
// Synopsis: Creates a shape to round 90° corners.
// SynTags: Geom
// Topics: Masking, Rounding, Shapes (3D)
// See Also: rounding_edge_mask(), default_tag(), diff()
// Usage:
// rounding_corner_mask(r|d, [ang], [excess=], [style=]) [ATTACHMENTS];
// Description:
// Creates a shape that you can use to round corners where the top and bottom faces are parallel and the two side
// faces are perpendicular to the top and bottom, e.g. cubes or pie_slice corners.
// Difference it from the object to be rounded. The center of the mask
// object should align exactly with the corner to be rounded.
// Arguments:
// r = Radius of corner rounding.
// ang = Angle of corner (measured around the z axis). Default: 90
// ---
// d = Diameter of corner rounding.
// excess = Extra size for the mask. Defaults: 0.1
// style = The style of the sphere cutout's construction. One of "orig", "aligned", "stagger", "octa", or "icosa". Default: "octa"
// 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`
// Side Effects:
// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
// Example:
// rounding_corner_mask(r=20);
// Example: Adding a huge excess
// rounding_corner_mask(r=20, excess=5);
// Example: Position masks manually
// difference() {
// cube(size=[50, 60, 70], center=true);
// translate([-25, -30, 35])
// #rounding_corner_mask(r=20, spin=90, orient=DOWN);
// translate([25, -30, 35])
// #rounding_corner_mask(r=20, orient=DOWN);
// translate([25, -30, -35])
// #rounding_corner_mask(r=20, spin=90);
// }
// Example: Masking by Attachment
// diff()
// cube(size=[50, 60, 70]) {
// corner_mask(TOP)
// #rounding_corner_mask(r=20);
// }
// Example(VPR=[71.8,0,345.8],VPT=[57.0174,43.8496,24.5863],VPD=263.435,NoScales): Acute angle
// ang=60;
// difference() {
// pie_slice(ang=ang, h=50, r=100);
// zflip_copy(z=25)
// #rounding_corner_mask(r=20, ang=ang);
// }
// Example(VPR=[62.7,0,5.4],VPT=[6.9671,22.7592,20.7513],VPD=192.044): Obtuse angle
// ang=120;
// difference() {
// pie_slice(ang=ang, h=50, r=30);
// zflip_copy(z=25)
// #rounding_corner_mask(r=20, ang=ang);
// }
function rounding_corner_mask(r, ang, d, style="octa", excess=0.1, anchor=CENTER, spin=0, orient=UP) = no_function("rounding_corner_mask");
module rounding_corner_mask(r, ang=90, d, style="octa", excess=0.1, anchor=CENTER, spin=0, orient=UP)
{
r = get_radius(r=r, d=d, dflt=1);
joint = r/tan(ang/2);
path = [
[joint,r],
[joint,-excess],
[-excess/tan(ang/2),-excess],
polar_to_xy(joint,ang)+polar_to_xy(excess,90+ang)
];
default_tag("remove") {
attachable(anchor,spin,orient, size=[2,2,2]*r) {
difference() {
down(excess)
linear_extrude(height=r+excess) polygon(path);
translate([joint,r,r])
spheroid(r=r, style=style);
}
children();
}
}
}
function rounding_angled_edge_mask(h, r, r1, r2, d, d1, d2, ang=90, anchor=CENTER, spin=0, orient=UP,l,height,length) = no_function("rounding_angled_edge_mask");
module rounding_angled_edge_mask(h, r, r1, r2, d, d1, d2, ang=90, anchor=CENTER, spin=0, orient=UP,l,height,length)
{
deprecate("angled_edge_mask");
rounding_edge_mask(h=h,r=r,r1=r1,r2=r2,d=d,d1=d1,d2=d1,ang=ang,anchor=anchor,spin=spin,orient=orient,l=l,height=height,length=length)
children();
}
function rounding_angled_corner_mask(r, ang=90, d, anchor=CENTER, spin=0, orient=UP) = no_function("rounding_angled_corner_mask");
module rounding_angled_corner_mask(r, ang=90, d, anchor=CENTER, spin=0, orient=UP)
{
deprecate("rounding_corner_mask");
zflip()rounding_corner_mask(r=r,ang=ang,d=d,anchor=anchor,spin=spin,orient=orient)
children();
}
// Module: rounding_cylinder_mask()
// Synopsis: Creates a shape to round the end of a cylinder.
// SynTags: Geom
// Topics: Masking, Rounding, Cylinders
// See Also: rounding_hole_mask(), rounding_corner_mask(), default_tag(), diff()
// Usage:
// rounding_cylinder_mask(r|d=, rounding);
// Description:
// Create a mask that can be used to round the end of a cylinder.
// Difference it from the cylinder to be rounded. The center of the
// mask object should align exactly with the center of the end of the
// cylinder to be rounded.
// Arguments:
// r = Radius of cylinder.
// rounding = Radius of the edge rounding.
// ---
// d = Diameter of cylinder.
// 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`
// Side Effects:
// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
// Example:
// difference() {
// cylinder(r=50, h=50, center=false);
// up(50) #rounding_cylinder_mask(r=50, rounding=10);
// }
// Example:
// difference() {
// cylinder(r=50, h=50, center=false);
// up(50) rounding_cylinder_mask(r=50, rounding=10);
// }
// Example: Masking by Attachment
// diff()
// cyl(h=30, d=30) {
// attach(TOP)
// #tag("remove")
// rounding_cylinder_mask(d=30, rounding=5);
// }
function rounding_cylinder_mask(r, rounding, d, anchor, spin, orient) = no_function("rounding_cylinder_mask");
module rounding_cylinder_mask(r, rounding, d, anchor=CENTER, spin=0, orient=UP)
{
r = get_radius(r=r, d=d, dflt=1);
default_tag("remove") {
attachable(anchor,spin,orient, r=r+rounding, l=rounding*2) {
difference() {
cyl(r=r+rounding, l=rounding*2, anchor=CENTER);
cyl(r=r, l=rounding*3, rounding=rounding, anchor=TOP);
}
children();
}
}
}
// Module: rounding_hole_mask()
// Synopsis: Creates a shape to round the edge of a round hole.
// SynTags: Geom
// Topics: Masking, Rounding
// See Also: rounding_cylinder_mask(), rounding_hole_mask(), rounding_corner_mask(), default_tag(), diff()
// Usage:
// rounding_hole_mask(r|d, rounding, [excess]) [ATTACHMENTS];
// Description:
// Create a mask that can be used to round the edge of a circular hole.
// Difference it from the hole to be rounded. The center of the
// mask object should align exactly with the center of the end of the
// hole to be rounded.
// Arguments:
// r = Radius of hole.
// rounding = Radius of the rounding.
// excess = The extra thickness of the mask. Default: `0.1`.
// ---
// d = Diameter of hole to rounding.
// 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`
// Side Effects:
// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
// Example:
// rounding_hole_mask(r=40, rounding=20, $fa=2, $fs=2);
// Example(Med):
// difference() {
// cube([150,150,100], center=true);
// cylinder(r=50, h=100.1, center=true);
// up(50) #rounding_hole_mask(r=50, rounding=10);
// }
// Example(Med):
// difference() {
// cube([150,150,100], center=true);
// cylinder(r=50, h=100.1, center=true);
// up(50) rounding_hole_mask(r=50, rounding=10);
// }
function rounding_hole_mask(r, rounding, excess=0.1, d, anchor=CENTER, spin=0, orient=UP) = no_function("rounding_hole_mask");
module rounding_hole_mask(r, rounding, excess=0.1, d, anchor=CENTER, spin=0, orient=UP)
{
r = get_radius(r=r, d=d, dflt=1);
default_tag("remove") {
attachable(anchor,spin,orient, r=r+rounding, l=2*rounding) {
rotate_extrude(convexity=4) {
difference() {
right(r-excess) fwd(rounding) square(rounding+excess, center=false);
right(r+rounding) fwd(rounding) circle(r=rounding);
}
}
children();
}
}
}
// Section: Teardrop Masking
// Module: teardrop_edge_mask()
// Synopsis: Creates a shape to round a 90° edge but limit the angle of overhang.
// SynTags: Geom
// Topics: Masking, Rounding, Shapes (3D), FDM Optimized
// See Also: teardrop_corner_mask(), teardrop_edge_mask(), default_tag(), diff()
// Usage:
// teardrop_edge_mask(l|h=|length=|height=, r|d=, [angle], [excess], [anchor], [spin], [orient]) [ATTACHMENTS];
// Description:
// Makes an apropriate 3D edge rounding mask that keeps within `angle` degrees of vertical.
// Arguments:
// l/h/length/height = length of mask
// r = Radius of the mask rounding.
// angle = Maximum angle from vertical. Default: 45
// excess = Excess mask size. Default: 0.1
// ---
// d = Diameter of the mask rounding.
// 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`
// Side Effects:
// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
// Example(VPD=50,VPR=[55,0,120]):
// teardrop_edge_mask(l=20, r=10, angle=40);
// Example(VPD=300,VPR=[75,0,25]):
// diff()
// cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) {
// edge_mask(BOT)
// teardrop_edge_mask(l=max($parent_size)+1, r=10, angle=40);
// corner_mask(BOT)
// teardrop_corner_mask(r=10, angle=40);
// }
function teardrop_edge_mask(l, r, angle=45, excess=0.1, d, anchor, spin, orient,h,height,length) = no_function("teardrop_edge_mask");
module teardrop_edge_mask(l, r, angle=45, excess=0.1, d, anchor=CTR, spin=0, orient=UP,h,height,length)
{
l = one_defined([l, h, height, length], "l,h,height,length");
check =
assert(is_num(l) && l>0, "Length of mask must be positive")
assert(is_num(angle) && angle>0 && angle<90, "Angle must be a number between 0 and 90")
assert(is_num(excess));
r = get_radius(r=r, d=d, dflt=1);
path = mask2d_teardrop(r=r, angle=angle, excess=excess);
default_tag("remove") {
linear_sweep(path, height=l, center=true, atype="bbox", anchor=anchor, spin=spin, orient=orient) children();
}
}
// Module: teardrop_corner_mask()
// Synopsis: Creates a shape to round a 90° corner but limit the angle of overhang.
// SynTags: Geom
// Topics: Masking, Rounding, Shapes (3D), FDM Optimized
// See Also: teardrop_corner_mask(), teardrop_edge_mask(), default_tag(), diff()
// Usage:
// teardrop_corner_mask(r|d=, [angle], [excess], [anchor], [spin], [orient]) [ATTACHMENTS];
// Description:
// Makes an apropriate 3D corner rounding mask that keeps within `angle` degrees of vertical.
// Arguments:
// r = Radius of the mask rounding.
// angle = Maximum angle from vertical. Default: 45
// excess = Excess mask size. Default: 0.1
// ---
// d = Diameter of the mask rounding.
// 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`
// Side Effects:
// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
// Example:
// teardrop_corner_mask(r=20, angle=40);
// Example:
// diff()
// cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) {
// edge_profile(BOT)
// mask2d_teardrop(r=10, angle=40);
// corner_mask(BOT)
// teardrop_corner_mask(r=10, angle=40);
// }
function teardrop_corner_mask(r, angle=45, excess=0.1, d, anchor, spin, orient) = no_function("teardrop_corner_mask");
module teardrop_corner_mask(r, angle=45, excess=0.1, d, anchor=CTR, spin=0, orient=UP)
{
assert(is_num(angle));
assert(is_num(excess));
assert(angle>0 && angle<90);
r = get_radius(r=r, d=d, dflt=1);
size = (r+excess) * [1,1,1];
midpt = (r-excess)/2 * [1,1,1];
default_tag("remove") {
attachable(anchor,spin,orient, size=size, offset=midpt) {
difference() {
translate(-[1,1,1]*excess) cube(r+excess, center=false);
translate([1,1,1]*r) onion(r=r, ang=angle, orient=DOWN);
}
children();
}
}
}
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

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.