Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rounded_edge_mask enhancements #1486

Merged
merged 2 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions attachments.scad
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,14 @@ $parent_orient = UP;
$parent_size = undef;
$parent_geom = undef;

$edge_angle = undef;
$edge_length = undef;

$tags_shown = "ALL";
$tags_hidden = [];



_ANCHOR_TYPES = ["intersect","hull"];


Expand Down Expand Up @@ -972,6 +977,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
anchor_data = _find_anchor(anchor, $parent_geom);
$edge_angle = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_angle") : undef;
$edge_length = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_length") : undef;
$edge_end1 = len(anchor_data)==5 ? struct_val(anchor_data[4],"vec") : undef;
anchor_pos = anchor_data[1];
anchor_dir = factor*anchor_data[2];
anchor_spin = two_d || !inside || anchor==TOP || anchor==BOT ? anchor_data[3]
Expand Down Expand Up @@ -1955,7 +1961,7 @@ module face_mask(faces=[LEFT,RIGHT,FRONT,BACK,BOT,TOP]) {
// Usage:
// PARENT() edge_mask([edges], [except]) CHILDREN;
// Description:
// Takes a 3D mask shape, and attaches it to the given edges, with the appropriate orientation to be
// 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 will work with the default {{diff()}} tag.
Expand Down Expand Up @@ -3179,12 +3185,13 @@ function reorient(
// orient = A vector pointing in the direction parts should project from the anchor position. Default: UP
// spin = If needed, the angle to rotate the part around the direction vector. Default: 0
// ---
// info = structure listing info to be propagated to the attached child, e.g. "edge_anchor"
// rot = A 4x4 rotations matrix, which may include a translation
// flip = If true, flip the anchor the opposite direction. Default: false
function named_anchor(name, pos, orient, spin, rot, flip) =
function named_anchor(name, pos, orient, spin, rot, flip, info) =
assert(num_defined([orient,spin])==0 || num_defined([rot,flip])==0, "Cannot mix orient or spin with rot or flip")
assert(num_defined([pos,rot])>0, "Must give pos or rot")
is_undef(rot) ? [name, pos, default(orient,UP), default(spin,0)]
is_undef(rot) ? [name, pos, default(orient,UP), default(spin,0), if (info) info]
:
let(
flip = default(flip,false),
Expand All @@ -3198,7 +3205,7 @@ function named_anchor(name, pos, orient, spin, rot, flip) =
decode=rot_decode(rot(to=UP,from=dir)*_force_rot(rot)),
spin = decode[0]*sign(decode[1].z)
)
[name, pos, dir, spin];
[name, pos, dir, spin, if (info) info];


// Function: attach_geom()
Expand Down Expand Up @@ -3789,6 +3796,26 @@ function _find_anchor(anchor, geom)=
)
unit(v3,UP),
edgeang = len(facevecs)==2 ? 180-vector_angle(facevecs[0], facevecs[1]) : undef,
edgelen = anch.z==0 ? norm(edge)
: anch.z>0 ? abs([size2.y,size2.x]*axy)
: abs([size.y,size.x]*axy),
endvecs = len(facevecs)!=2 ? undef
: anch.z==0 ? [DOWN, UP]
: let(
raxy = zrot(-90,axy),
bot1 = point3d(v_mul(point2d(size )/2, raxy), -h/2),
top1 = point3d(v_mul(point2d(size2)/2, raxy) + shift, h/2),
edge1 = top1-bot1,
vec1 = (raxy.x!=0) ? unit(rot(from=UP, to=[edge1.x,0,max(0.01,h)], p=[raxy.x,0,0]), UP)
: unit(rot(from=UP, to=[0,edge1.y,max(0.01,h)], p=[0,raxy.y,0]), UP),
raxy2 = zrot(90,axy),
bot2 = point3d(v_mul(point2d(size )/2, raxy2), -h/2),
top2 = point3d(v_mul(point2d(size2)/2, raxy2) + shift, h/2),
edge2 = top2-bot2,
vec2 = (raxy2.y!=0) ? unit(rot(from=UP, to=[edge.x,0,max(0.01,h)], p=[raxy2.x,0,0]), UP)
: unit(rot(from=UP, to=[0,edge.y,max(0.01,h)], p=[0,raxy2.y,0]), UP)
)
[vec1,vec2],
final_dir = default(override[1],anch==CENTER?UP:rot(from=UP, to=axis, p=dir)),
final_pos = default(override[0],rot(from=UP, to=axis, p=pos)),

Expand All @@ -3801,7 +3828,8 @@ function _find_anchor(anchor, geom)=
: anch.z!=0 && sum(v_abs(anch))==2 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=anch.z*[anch.y,-anch.x,0]))
: norm(anch)==3 ? _compute_spin(final_dir, final_dir==DOWN || final_dir==UP ? BACK : UP)
: oang // face anchors point UP/BACK
) [anchor, final_pos, final_dir, default(override[2],spin), if (is_def(edgeang)) [["edge_angle",edgeang],["edge_length",norm(edge)]]]
) [anchor, final_pos, final_dir, default(override[2],spin),
if (is_def(edgeang)) [["edge_angle",edgeang],["edge_length",edgelen], ["vec", endvecs]]]
) : type == "conoid"? ( //r1, r2, l, shift
let(
rr1=geom[1],
Expand Down
32 changes: 32 additions & 0 deletions constants.scad
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ _UNDEF="LRG+HX7dy89RyHvDlAKvb9Y04OTuaikpx205CTh8BSI";
// The correct hole should hold the plug when the long block is turned upside-down.
// The number in front of that hole will indicate the `$slop` value that is ideal for your printer.
// Remember to set that slop value in your scripts after you include the BOSL2 library: ie: `$slop = 0.15;`
// .
// Note that the `$slop` value may be different using different materials even on the same printer.
// Example(3D,Med): Slop Calibration Part.
// min_slop = 0.00;
// slop_step = 0.05;
Expand Down Expand Up @@ -215,6 +217,36 @@ CENTER = [ 0, 0, 0]; // Centered zero vector.
CTR = CENTER;
CENTRE = CENTER;

// Function: EDGE()
// Synopsis: Named edge anchor constants
// Topics: Constants, Attachment
// Usage:
// EDGE(i)
// EDGE(direction,i)
// Description:
// A shorthand for the named anchors "edge0", "top_edge0", "bot_edge0", etc.
// Use `EDGE(i)` to get "edge<i>". Use `EDGE(TOP,i)` to get "top_edge<i>" and
// use `EDGE(BOT,i)` to get "bot_edge(i)". You can also use
// `EDGE(CTR,i)` to get "edge<i>" and you can replace TOP or BOT with simply 1 or -1.

function EDGE(a,b) =
is_undef(b) ? str("edge",a)
: assert(in_list(a,[TOP,BOT,CTR,1,0,-1]),str("Invalid direction: ",a))
let(
choices=["bot_","","top_"],
ind=is_vector(a) ? a.z : a
)
str(choices[ind+1],"edge",b);

// Function: FACE()
// Synopsis: Named face anchor constants
// Topics: Constants, Attachment
// Usage:
// FACE(i)
// Description:
// A shorthand for the named anchors "face0", "face1", etc.

function FACE(i) = str("face",i);

// Section: Line specifiers
// Used by functions in geometry.scad for specifying whether two points
Expand Down
125 changes: 95 additions & 30 deletions masks3d.scad
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
// 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.
// 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`
// ---
Expand All @@ -49,7 +49,8 @@
// }
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 = one_defined([l, h, height, length], "l,h,height,length");
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);
Expand Down Expand Up @@ -169,35 +170,48 @@ module chamfer_cylinder_mask(r, chamfer, d, ang=45, from_end=false, anchor=CENTE
}



// Section: Rounding Masks

// Module: rounding_edge_mask()
// Synopsis: Creates a shape to round a 90° edge.
// SynTags: Geom
// Topics: Masks, Rounding, Shapes (3D)
// See Also: rounding_corner_mask(), default_tag(), diff()
// See Also: edge_profile(), rounding_corner_mask(), default_tag(), diff()
// Usage:
// rounding_edge_mask(l|h=|length=|height=, r|d=, [ang], [excess=]) [ATTACHMENTS];
// rounding_edge_mask(l|h=|length=|height=, r1=|d1=, r2=|d2=, [ang=], [excess=]) [ATTACHMENTS];
// 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 shape that can be used to round a straight edge at any angle.
// Difference it from the object to be rounded. The center of the mask
// object should align exactly with the edge to be rounded. You can use it with {{diff()}} and
// {{edge_mask()}} to attach masks automatically to objects. The default "remove" tag is set
// automatically.
// 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.
// l/h/length/height = Length of mask. Default: $edge_length if defined
// r = Radius of the rounding.
// ang = Angle between faces for rounding. Default: 90
// 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`
Expand Down Expand Up @@ -249,34 +263,85 @@ module chamfer_cylinder_mask(r, chamfer, d, ang=45, from_end=false, anchor=CENTE
// 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=90, r1, r2, excess=0.01, d1, d2,d,r,length, h, height, anchor=CENTER, spin=0, orient=UP,
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)
{
length = one_defined([l,length,h,height],"l,length,h,height");
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(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 = ceil(segs(max(r1,r2))*(180-ang)/360);
function make_path(r) =
let(
arc = r==0 ? repeat([0,0],steps+1)
: arc(n=steps+1, r=r, corner=[polar_to_xy(r,ang),[0,0],[r,0]]),
maxx = last(arc).x,
maxy = arc[0].y,
cp = [-excess/tan(ang/2),-excess]
)
[
[maxx, -excess],
cp,
arc[0] + polar_to_xy(excess, 90+ang),
each arc
];
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)]);
Expand Down Expand Up @@ -315,7 +380,7 @@ module rounding_edge_mask(l, r, ang=90, r1, r2, excess=0.01, d1, d2,d,r,length,
[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([path1,path2],caps=true,col_wrap=true);
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);
Expand Down
Loading
Loading