From 785bfb69de3dc368ec13024c8d53d9a4a89f60bc Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 6 Nov 2024 16:36:50 -0500 Subject: [PATCH 1/5] fix some issues with hirth and add skew option --- joiners.scad | 99 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 42 deletions(-) diff --git a/joiners.scad b/joiners.scad index 45ff5bf..a815e25 100644 --- a/joiners.scad +++ b/joiners.scad @@ -1247,54 +1247,72 @@ module rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1 // to remove. The teeth valleys are chamfered by half the specified value to ensure that there is room for the parts // to mate. The base is added based on the unchamfered dimensions of the joint, and the "teeth_bot" anchor is located // based on the unchamfered dimensions. +// . +// By default the teeth are symmetric, which is ideal for registration and for situations where loading may occur in either +// direction. The skew parameter will skew the teeth by the specified amount, where a skew of ±1 gives a tooth with a vertical +// side either on the left or the right. Intermediate values will produce partially skewed teeth. Note that the skew +// applies after the tooth profile is computed with the specified tooth_angle, which means that the skewed tooth will +// have an altered tooth angle from the one specified. +// . +// The joint is constructed with a tooth peak aligned with the X+ axis. +// For two hirth joints to mate they must have the same tooth count, opposite cone angles, and the chamfer/rounding values +// must be equal. (One can be chamfered and one rounded, but with the same value.) The rotation required to mate the parts +// depends on the skew and whether the tooth count is odd or even. To apply this rotation automatically, set `rot=true`. + // Named Anchors: -// "teeth_bot" = center of the joint, aligned with the bottom of the (unchamfered) teeth, pointing DOWN. -// "mate" = center of the joint, pointing UP, but with the correct spin so that the part will mate with a compatible parent joint. +// "teeth_bot" = center of the joint, aligned with the bottom of the (unchamfered/unrounded) teeth, pointing DOWN. // Arguments: // n = number of teeth // ir/id = inner radius or diameter // or/od = outer radius or diameter // tooth_angle = nominal tooth angle. Default: 60 // cone_angle = raise or lower the angle of the teeth in the radial direction. Default: 0 +// skew = skew the tooth shape. Default: 0 // chamfer = chamfer teeth by this fraction at tips and half this fraction at valleys. Default: 0 -// roudning = round the teeth by this fraction at the tips, and half this fraction at valleys. Default: 0 +// rounding = round the teeth by this fraction at the tips, and half this fraction at valleys. Default: 0 +// rot = if true rotate so the part will mate (via attachment) with another identical part. Default: false // base = add base of this height to the bottom. Default: 1 // crop = crop to a cylindrical shape. 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` -// Example: Basic uncropped hirth spline +// Example(3D,NoScale): Basic uncropped hirth spline // hirth(32,20,50); -// Example: Raise cone angle +// Example(3D,NoScale): Raise cone angle // hirth(32,20,50,cone_angle=30); -// Example: Lower cone angle +// Example(3D,NoScale): Lower cone angle // hirth(32,20,50,cone_angle=-30); -// Example: Adding a large base +// Example(3D,NoScale): Adding a large base // hirth(20,20,50,base=20); -// Example: Only 8 teeth, with chamfering +// Example(3D,NoScale): Only 8 teeth, with chamfering // hirth(8,20,50,tooth_angle=60,base=10,chamfer=.1); -// Example: Only 8 teeth, cropped +// Example(3D,NoScale): Only 8 teeth, cropped // hirth(8,20,50,tooth_angle=60,base=10,chamfer=.1, crop=true); -// Example: Only 8 teeth, with rounding +// Example(3D,NoScale): Only 8 teeth, with rounding // hirth(8,20,50,tooth_angle=60,base=10,rounding=.1); -// Example: Only 8 teeth, different tooth angle, cropping with $fn to crop cylinder aligned with teeth +// Example(3D,NoScale): Only 8 teeth, different tooth angle, cropping with $fn to crop cylinder aligned with teeth // hirth(8,20,50,tooth_angle=90,base=10,rounding=.05,crop=true,$fn=48); -// Example: Two identical parts joined together (with 1 unit offset to reveal the joint line). With odd tooth count you can use the CENTER anchor for the child and the teeth line up correctly. +// Example(3D,NoScale): Two identical parts joined together (with 1 unit offset to reveal the joint line). With odd tooth count and no skew the teeth line up correctly: // hirth(27,20,50, tooth_angle=60,base=2,chamfer=.05) // up(1) attach(CENTER,CENTER) // hirth(27,20,50, tooth_angle=60,base=2,chamfer=.05); -// Example: Two conical parts joined together, with opposite cone angles for a correct joint. With an even tooth count you must use the "mate" anchor for correct alignment of the teeth. +// Example(3D,NoScale): Two conical parts joined together, with opposite cone angles for a correct joint. With an even tooth count one part needs to be rotated for the parts to align: // hirth(26,20,50, tooth_angle=60,base=2,cone_angle=30,chamfer=.05) -// up(1) attach(CENTER,"mate") -// hirth(26,20,50, tooth_angle=60,base=2,cone_angle=-30, chamfer=.05); +// up(1) attach(CENTER,CENTER) +// hirth(26,20,50, tooth_angle=60,base=2,cone_angle=-30, chamfer=.05, rot=true); +// Example(3D,NoScale): Using skew to create teeth with vertical faces +// hirth(17,20,50,skew=-1, base=5, chamfer=0.05); +// Example(3D,NoScale): If you want to change how tall the teeth are you do that by chaging the tooth angle. Increasing the tooth angle makes the teeth shorter: +// hirth(17,20,50,tooth_angle=120,skew=0, base=5, rounding=0.05, crop=true); -module hirth(n, ir, or, id, od, tooth_angle=60, cone_angle=0, chamfer, rounding, base=1, crop=false, orient,anchor,spin) +module hirth(n, ir, or, id, od, tooth_angle=60, cone_angle=0, chamfer, rounding, base=1, crop=false,skew=0, rot=false, orient,anchor,spin) { ir = get_radius(r=ir,d=id); or = get_radius(r=or,d=od); dummy = assert(all_positive([ir]), "ir/id must be a positive value") assert(all_positive([or]), "or/od must be a positive value") assert(is_int(n) && n>1, "n must be an integer larger than 1") + assert(is_finite(skew) && abs(skew)<=1, "skew must be a number between -1 and 1") assert(ir Date: Wed, 6 Nov 2024 19:02:45 -0500 Subject: [PATCH 2/5] doc fixes --- beziers.scad | 12 ++++++------ joiners.scad | 8 +++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/beziers.scad b/beziers.scad index 281a09b..9159a09 100644 --- a/beziers.scad +++ b/beziers.scad @@ -979,20 +979,20 @@ function bezier_patch_reverse(patch) = // v = The bezier v parameter (outer list of patch). Generally between 0 and 1. Can be a list, range or value. // Example(3D): // patch = [ -// [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], -// [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], +// [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], // [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], -// [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]] +// [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], +// [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]] // ]; // debug_bezier_patches(patches=[patch], size=1, showcps=true); // pt = bezier_patch_points(patch, 0.6, 0.75); // translate(pt) color("magenta") sphere(d=3, $fn=12); // Example(3D): Getting Multiple Points at Once // patch = [ -// [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], -// [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], +// [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], // [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], -// [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]] +// [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], +// [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]] // ]; // debug_bezier_patches(patches=[patch], size=1, showcps=true); // pts = bezier_patch_points(patch, [0:0.2:1], [0:0.2:1]); diff --git a/joiners.scad b/joiners.scad index a815e25..7749afc 100644 --- a/joiners.scad +++ b/joiners.scad @@ -1223,6 +1223,8 @@ module rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1 // Section: Splines // Module: hirth() +// Synopsis: Creates a Hirth face spline that locks together two cylinders. +// Syntags: Geom // Usage: // hirth(n, ir|id=, or|od=, tooth_angle, [cone_angle=], [chamfer=], [rounding=], [base=], [crop=], [anchor=], [spin=], [orient=] // Description: @@ -1232,7 +1234,7 @@ module rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1 // Each tooth is a triangle that grows larger with radius. You specify a nominal tooth angle; the actual tooth // angle will be slightly different. // . -// You can also specify a cone_angle which raises or lowers the angle of the teeth. When you do this you ened to +// You can also specify a cone_angle which raises or lowers the angle of the teeth. When you do this you need to // mate splines with opposite angles such as -20 and +20. The splines appear centered at the origin so that two // splines will mate if their centers coincide. Therefore `attach(CENTER,CENTER)` will produce two mating splines // assuming that they are rotated correctly. The bottom anchors will be at the bottom of the spline base. The top @@ -1249,7 +1251,7 @@ module rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1 // based on the unchamfered dimensions. // . // By default the teeth are symmetric, which is ideal for registration and for situations where loading may occur in either -// direction. The skew parameter will skew the teeth by the specified amount, where a skew of ±1 gives a tooth with a vertical +// direction. The skew parameter will skew the teeth by the specified amount, where a skew of ±1 gives a tooth with a vertical // side either on the left or the right. Intermediate values will produce partially skewed teeth. Note that the skew // applies after the tooth profile is computed with the specified tooth_angle, which means that the skewed tooth will // have an altered tooth angle from the one specified. @@ -1302,7 +1304,7 @@ module rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1 // hirth(26,20,50, tooth_angle=60,base=2,cone_angle=-30, chamfer=.05, rot=true); // Example(3D,NoScale): Using skew to create teeth with vertical faces // hirth(17,20,50,skew=-1, base=5, chamfer=0.05); -// Example(3D,NoScale): If you want to change how tall the teeth are you do that by chaging the tooth angle. Increasing the tooth angle makes the teeth shorter: +// Example(3D,NoScale): If you want to change how tall the teeth are you do that by changing the tooth angle. Increasing the tooth angle makes the teeth shorter: // hirth(17,20,50,tooth_angle=120,skew=0, base=5, rounding=0.05, crop=true); module hirth(n, ir, or, id, od, tooth_angle=60, cone_angle=0, chamfer, rounding, base=1, crop=false,skew=0, rot=false, orient,anchor,spin) From b39e28433e3f85f74ccdfb4de1997a528aadd655 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 6 Nov 2024 19:30:09 -0500 Subject: [PATCH 3/5] doc fix --- joiners.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joiners.scad b/joiners.scad index 7749afc..37da7f5 100644 --- a/joiners.scad +++ b/joiners.scad @@ -1224,7 +1224,7 @@ module rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1 // Module: hirth() // Synopsis: Creates a Hirth face spline that locks together two cylinders. -// Syntags: Geom +// SynTags: Geom // Usage: // hirth(n, ir|id=, or|od=, tooth_angle, [cone_angle=], [chamfer=], [rounding=], [base=], [crop=], [anchor=], [spin=], [orient=] // Description: From 9d48dbf8d5128633c0a507a60b05b2a225a8fb83 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 6 Nov 2024 22:36:24 -0500 Subject: [PATCH 4/5] add example of internal attachment --- attachments.scad | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/attachments.scad b/attachments.scad index e9f0a07..f7da94e 100644 --- a/attachments.scad +++ b/attachments.scad @@ -2759,7 +2759,10 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) { // in that color, but if you want to retain control of color for sub-parts of an attachable object, you can use // the `keep_color=true` option, which delays the assignment of colors to the child level. For this to work // correctly, all of the sub-parts of your attachable object must be attachables. Also note that this option could -// be confusing to users who don't understand why color commands are not working on the object. +// be confusing to users who don't understand why color commands are not working on the object. +// . +// Note that anchors created by attachable() are generally intended for use by the user-supplied children of the attachable object, but they +// are available internally and can be used in the object's definition. // . // For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments). // @@ -3021,6 +3024,38 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) { // recolor("pink") thing() // attach(RIGHT,BOT) // recolor("blue") cyl(d=5,h=5); +// Example(3D,NoScale): This example defines named anchors and then uses them internally in the object definition to make a cutout in the socket() object and to attach the plug on the plug() object. These objects can be connected using the "socket" and "plug" named anchors, which will fit the plug into the socket. +// module socket(anchor, spin, orient) { +// sz = 50; +// prong_size = 10; +// anchors = [ +// named_anchor("socket", [sz/2,.15*sz,.2*sz], RIGHT, 0) +// ]; +// attachable(anchor, spin, orient, size=[sz,sz,sz], anchors=anchors) { +// diff() { +// cuboid(sz); +// tag("remove") attach("socket") zcyl(d=prong_size, h=prong_size*2); +// } +// children(); +// } +// } +// module plug(anchor, spin, orient) { +// sz = 30; +// prong_size = 9.5; +// anchors=[ +// named_anchor("plug", [0,sz/3,sz/2], UP, 0) +// ]; +// attachable(anchor, spin, orient, size=[sz,sz,sz], anchors=anchors) { +// union(){ +// cuboid(sz); +// attach("plug") cyl(d=prong_size, h=prong_size*2,$fn=6); +// } +// children(); +// } +// } +// socket(); +// right(75) plug(); + module attachable( anchor, spin, orient, From fde77e0cbd68a7da769e3a6c0dd56b1e588da20b Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Thu, 7 Nov 2024 18:32:30 -0500 Subject: [PATCH 5/5] fix chamfering of hirth joint --- joiners.scad | 55 +++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/joiners.scad b/joiners.scad index 37da7f5..31f6548 100644 --- a/joiners.scad +++ b/joiners.scad @@ -1329,39 +1329,36 @@ module hirth(n, ir, or, id, od, tooth_angle=60, cone_angle=0, chamfer, rounding, factor = crop ? 3 : 1; // Make it oversized when crop is true - profile = is_undef(rounding) || rounding==0 ? - let( - chamfer=default(chamfer,0), - vchamf = chamfer*(ridge_angle-valley_angle), - pts = [ - [-angle*(1-chamfer/2), valley_angle+vchamf/2], - [-angle*chamfer, ridge_angle-vchamf] - ], - full = deduplicate(concat(pts, reverse(xflip(pts)))) - ) - back(valley_angle, skew(sxy=skew*angle/(ridge_angle-valley_angle),fwd(valley_angle,full))) - : let( - vround=rounding*(ridge_angle-valley_angle), - profpts = [ - [ -angle, valley_angle+vround/2], - [ -angle*(1-rounding/2), valley_angle+vround/2], - [ -angle*rounding, ridge_angle-vround], - ], - segs = max(16,segs(or*rounding)), - full = concat(profpts, reverse(xflip(profpts))), - skewed = back(valley_angle, skew(sxy=skew*angle/(ridge_angle-valley_angle),fwd(valley_angle,full))), - // Using computed values for the joints lead to round-off error issues - joints = [(skewed[1]-skewed[0]).x, (skewed[3]-skewed[2]).x/2, (skewed[3]-skewed[2]).x/2,(skewed[5]-skewed[4]).x ], - roundpts = round_corners(skewed, joint=joints, closed=false,$fn=segs) - ) - roundpts; - - // project spherical coordinate point onto cylinder of radius r - cyl_proj = function (r,theta_phi) +// project spherical coordinate point onto cylinder of radius r + cyl_proj = function (r,theta_phi) [for(pt=theta_phi) let(xyz = spherical_to_xyz(1,pt[0], 90-pt[1])) r * xyz / norm(point2d(xyz))]; + edge = cyl_proj(or,[[-angle, valley_angle], [0, ridge_angle]]); + cutfrac = first_defined([chamfer,rounding,0]); + rounding = rounding==0? undef:rounding; + ridgecut=xyz_to_spherical(lerp(edge[0],edge[1], 1-cutfrac)); + valleycut=xyz_to_spherical(lerp(edge[0],edge[1], cutfrac/2)); + ridge_chamf = [ridgecut.y,90-ridgecut.z]; + valley_chamf = [valleycut.y,90-valleycut.z]; + basicprof = [ + if (is_def(rounding)) [-angle, valley_chamf.y], + valley_chamf, + ridge_chamf + ]; + full = deduplicate(concat(basicprof, reverse(xflip(basicprof)))); + skewed = back(valley_angle, skew(sxy=skew*angle/(ridge_angle-valley_angle),fwd(valley_angle,full))); + profile = is_undef(rounding) ? skewed + : + let( + segs = max(16,segs(or*rounding)), + // Using computed values for the joints lead to round-off error issues + joints = [(skewed[1]-skewed[0]).x, (skewed[3]-skewed[2]).x/2, (skewed[3]-skewed[2]).x/2,(skewed[5]-skewed[4]).x ], + roundpts = round_corners(skewed, joint=joints, closed=false,$fn=segs) + ) + roundpts; + bottom = min([tan(valley_angle)*ir,tan(valley_angle)*or])-base-cone_height*ir; safebottom = min([tan(valley_angle)*ir/factor,tan(valley_angle)*or*factor])-base-(crop?1:0)-cone_height*ir; ang_ofs = !rot ? -skew*angle