diff --git a/drawing.scad b/drawing.scad index f852e52..4da970f 100644 --- a/drawing.scad +++ b/drawing.scad @@ -28,6 +28,7 @@ // various marker shapes, and can be assigned different colors. If passed a region instead of // a path, draws each path in the region as a closed polygon by default. If `closed=false` is // given with a region or list of paths, then each path is drawn without the closing line segment. +// When drawing a closed path or region, there are no endcaps, so you cannot give the endcap parameters. // To facilitate debugging, stroke() accepts "paths" that have a single point. These are drawn with // the style of endcap1, but have their own scale parameter, `singleton_scale`, which defaults to 2 // so that singleton dots with endcap "round" are clearly visible. @@ -115,6 +116,12 @@ // Example(2D): Plotting Points. Setting endcap_angle to zero results in the weird arrow orientation. // path = [for (a=[0:30:360]) [a-180, 60*sin(a)]]; // stroke(path, width=3, joints="diamond", endcaps="arrow2", endcap_angle=0, endcap_width=5, joint_angle=0, joint_width=5); +// Example(2D): Default joint gives curves along outside corners of the path: +// stroke([square(40)], width=18); +// Example(2D): Setting `joints="square"` gives flat outside corners +// stroke([square(40)], width=18, joints="square"); +// Example(2D): Setting `joints="butt"` does not draw any transitions, just rectangular strokes for each segment, meeting at their centers: +// stroke([square(40)], width=18, joints="butt"); // Example(2D): Joints and Endcaps // path = [for (a=[0:30:360]) [a-180, 60*sin(a)]]; // stroke(path, width=8, joints="dot", endcaps="arrow2"); @@ -238,14 +245,15 @@ module stroke( ) * linewidth; closed = default(closed, is_region(path)); - check1 = assert(is_bool(closed)); + check1 = assert(is_bool(closed)) + assert(!closed || num_defined([endcaps,endcap1,endcap2])==0, "Cannot give endcap parameter(s) with closed path or region"); dots = dots==true? "dot" : dots; endcap1 = first_defined([endcap1, endcaps, dots, "round"]); endcap2 = first_defined([endcap2, endcaps, if (!closed) dots, "round"]); joints = first_defined([joints, dots, "round"]); - check2 = + check2 = assert(is_bool(endcap1) || is_string(endcap1) || is_path(endcap1)) assert(is_bool(endcap2) || is_string(endcap2) || is_path(endcap2)) assert(is_bool(joints) || is_string(joints) || is_path(joints)); @@ -290,258 +298,290 @@ module stroke( // We want to allow "paths" with length 1, so we can't use the normal path/region checks paths = is_matrix(path) ? [path] : path; assert(is_list(paths),"The path argument must be a list of 2D or 3D points, or a region."); - for (path = paths) { - pathvalid = is_path(path,[2,3]) || same_shape(path,[[0,0]]) || same_shape(path,[[0,0,0]]); - assert(pathvalid,"The path argument must be a list of 2D or 3D points, or a region."); - path = deduplicate( closed? list_wrap(path) : path ); + attachable(){ + for (path = paths) { + pathvalid = is_path(path,[2,3]) || same_shape(path,[[0,0]]) || same_shape(path,[[0,0,0]]); + assert(pathvalid,"The path argument must be a list of 2D or 3D points, or a region."); - check4 = assert(is_num(width) || len(width)==len(path), - "width must be a number or a vector the same length as the path (or all components of a region)"); - width = is_num(width)? [for (x=path) width] : width; + check4 = assert(is_num(width) || len(width)==len(path), + "width must be a number or a vector the same length as the path (or all components of a region)"); + path = deduplicate( closed? list_wrap(path) : path ); + width = is_num(width)? [for (x=path) width] + : closed? list_wrap(width) + : width; + check4a=assert(len(width)==len(path), "path had duplicated points and width was given as a list: this is not allowd"); - endcap_shape1 = _shape_path(endcap1, width[0], endcap_width1, endcap_length1, endcap_extent1); - endcap_shape2 = _shape_path(endcap2, last(width), endcap_width2, endcap_length2, endcap_extent2); + endcap_shape1 = _shape_path(endcap1, width[0], endcap_width1, endcap_length1, endcap_extent1); + endcap_shape2 = _shape_path(endcap2, last(width), endcap_width2, endcap_length2, endcap_extent2); - trim1 = width[0] * first_defined([ - trim1, trim, - (endcap1=="arrow")? endcap_length1-0.01 : - (endcap1=="arrow2")? endcap_length1*3/4 : - 0 - ]); + trim1 = width[0] * first_defined([ + trim1, trim, + (endcap1=="arrow")? endcap_length1-0.01 : + (endcap1=="arrow2")? endcap_length1*3/4 : + 0 + ]); - trim2 = last(width) * first_defined([ - trim2, trim, - (endcap2=="arrow")? endcap_length2-0.01 : - (endcap2=="arrow2")? endcap_length2*3/4 : - 0 - ]); - check10 = assert(is_finite(trim1)) - assert(is_finite(trim2)); + trim2 = last(width) * first_defined([ + trim2, trim, + (endcap2=="arrow")? endcap_length2-0.01 : + (endcap2=="arrow2")? endcap_length2*3/4 : + 0 + ]); + check10 = assert(is_finite(trim1)) + assert(is_finite(trim2)); - if (len(path) == 1) { - if (len(path[0]) == 2) { - // Endcap1 - setcolor(endcap_color1) { - translate(path[0]) { - mat = is_undef(endcap_angle1)? ident(3) : zrot(endcap_angle1); - multmatrix(mat) polygon(scale(singleton_scale,endcap_shape1)); - } - } - } else { - // Endcap1 - setcolor(endcap_color1) { - translate(path[0]) { - $fn = segs(width[0]/2); - if (is_undef(endcap_angle1)) { - rotate_extrude(convexity=convexity) { - right_half(planar=true) { - polygon(endcap_shape1); - } - } - } else { - rotate([90,0,endcap_angle1]) { - linear_extrude(height=max(widths[0],0.001), center=true, convexity=convexity) { - polygon(endcap_shape1); - } - } + if (len(path) == 1) { + if (len(path[0]) == 2) { + // Endcap1 + setcolor(endcap_color1) { + translate(path[0]) { + mat = is_undef(endcap_angle1)? ident(3) : zrot(endcap_angle1); + multmatrix(mat) polygon(scale(singleton_scale,endcap_shape1)); + } + } + } else { + // Endcap1 + setcolor(endcap_color1) { + translate(path[0]) { + $fn = segs(width[0]/2); + if (is_undef(endcap_angle1)) { + rotate_extrude(convexity=convexity) { + right_half(planar=true) { + polygon(endcap_shape1); + } + } + } else { + rotate([90,0,endcap_angle1]) { + linear_extrude(height=max(widths[0],0.001), center=true, convexity=convexity) { + polygon(endcap_shape1); + } + } + } + } + } + } + } else { + dummy=assert(trim10) + theta = v_theta(v1) - sign(ang)*90; + + if (!approx(ang,0)){ + // This section creates a rounded wedge to fill in gaps. The wedge needs to be oversized for overlap + // in all directions, including its apex, but not big enough to create artifacts. + // The core of the wedge is the proper arc we need to create. We then add side points based + // on firstang and secondang, where we try 1 degree, but if that appears too big we based it + // on the segment length. We pick the radius based on the smaller of the width at this point + // and the adjacent width, which could be much smaller---meaning that we need a much smaller radius. + // The apex offset we pick to be simply based on the width at this point. + firstang = sign(ang)*min(1,0.5*norm(v1)/PI/widths[i]*360); + secondang = sign(ang)*min(1,0.5*norm(v2)/PI/widths[i]*360); + firstR = 0.5*min(widths[i], lerp(widths[i],widths[i-1], abs(firstang)*PI*widths[i]/360/norm(v1))); + secondR = 0.5*min(widths[i], lerp(widths[i],widths[i+1], abs(secondang)*PI*widths[i]/360/norm(v2))); + apex_offset = widths[i]/10; + arcpath = [ + firstR*[cos(theta-firstang), sin(theta-firstang)], + each arc(d=widths[i], angle=[theta, theta+ang],n=joints=="square"?2:undef), + secondR*[cos(theta+ang+secondang), sin(theta+ang+secondang)], + -apex_offset*[cos(theta+ang/2), sin(theta+ang/2)] + ]; + polygon(arcpath); + } + } + } + } + } + if (!closed){ + // Endcap1 + setcolor(endcap_color1) { + translate(path[0]) { + mat = is_undef(endcap_angle1)? rot(from=BACK,to=start_vec) : + zrot(endcap_angle1); + multmatrix(mat) polygon(endcap_shape1); } } - } - } - } else { - dummy=assert(trim10) - theta = v_theta(v1) - sign(ang)*90; - ang_eps = sign(ang)/10; - - if (!approx(ang,0)) - arc(d=widths[i], angle=[theta-ang_eps, theta+ang+ang_eps], wedge=true); - } - } - } - } + sides = [ + for (i = idx(path2,e=-2)) + quantup(segs(max(widths[i],widths[i+1])/2),4) + ]; - // Endcap1 - setcolor(endcap_color1) { - translate(path[0]) { - mat = is_undef(endcap_angle1)? rot(from=BACK,to=start_vec) : - zrot(endcap_angle1); - multmatrix(mat) polygon(endcap_shape1); - } - } + // Straight segments + setcolor(color) { + for (i = idx(path2,e=-2)) { + dist = norm(path2[i+1] - path2[i]); + w1 = widths[i]/2; + w2 = widths[i+1]/2; + $fn = sides[i]; + translate(path2[i]) { + multmatrix(rotmats[i]) { + cylinder(r1=w1, r2=w2, h=dist, center=false); + } + } + } + } - // Endcap2 - setcolor(endcap_color2) { - translate(last(path)) { - mat = is_undef(endcap_angle2)? rot(from=BACK,to=end_vec) : - zrot(endcap_angle2); - multmatrix(mat) polygon(endcap_shape2); - } - } - } else { - rotmats = cumprod([ - for (i = idx(path2,e=-2)) let( - vec1 = i==0? UP : unit(path2[i]-path2[i-1], UP), - vec2 = unit(path2[i+1]-path2[i], UP) - ) rot(from=vec1,to=vec2) - ]); - - sides = [ - for (i = idx(path2,e=-2)) - quantup(segs(max(widths[i],widths[i+1])/2),4) - ]; - - // Straight segments - setcolor(color) { - for (i = idx(path2,e=-2)) { - dist = norm(path2[i+1] - path2[i]); - w1 = widths[i]/2; - w2 = widths[i+1]/2; - $fn = sides[i]; - translate(path2[i]) { - multmatrix(rotmats[i]) { - cylinder(r1=w1, r2=w2, h=dist, center=false); - } - } - } - } - - // Joints - setcolor(joint_color) { - for (i = [1:1:len(path2)-2]) { - $fn = sides[i]; - translate(path2[i]) { - if (joints != undef && joints != "round") { - joint_shape = _shape_path( - joints, width[i], - joint_width, - joint_length, - joint_extent - ); - multmatrix(rotmats[i] * xrot(180)) { - $fn = sides[i]; - if (is_undef(joint_angle)) { - rotate_extrude(convexity=convexity) { - right_half(planar=true) { - polygon(joint_shape); - } - } - } else { - rotate([90,0,joint_angle]) { - linear_extrude(height=max(widths[i],0.001), center=true, convexity=convexity) { - polygon(joint_shape); - } + // Joints + setcolor(joint_color) { + for (i = [1:1:len(path2)-2]) { + $fn = sides[i]; + translate(path2[i]) { + if (joints != undef && joints != "round") { + joint_shape = _shape_path( + joints, width[i], + joint_width, + joint_length, + joint_extent + ); + multmatrix(rotmats[i] * xrot(180)) { + $fn = sides[i]; + if (is_undef(joint_angle)) { + rotate_extrude(convexity=convexity) { + right_half(planar=true) { + polygon(joint_shape); + } + } + } else { + rotate([90,0,joint_angle]) { + linear_extrude(height=max(widths[i],0.001), center=true, convexity=convexity) { + polygon(joint_shape); + } + } + } + } + } else { + corner = select(path2,i-1,i+1); + axis = vector_axis(corner); + ang = vector_angle(corner); + if (!approx(ang,0)) { + frame_map(x=path2[i-1]-path2[i], z=-axis) { + zrot(90-0.5) { + rotate_extrude(angle=180-ang+1) { + arc(d=widths[i], start=-90, angle=180); + } + } + } + } + } + } + } + } + if (!closed){ + // Endcap1 + setcolor(endcap_color1) { + translate(path[0]) { + multmatrix(rotmats[0] * xrot(180)) { + $fn = sides[0]; + if (is_undef(endcap_angle1)) { + rotate_extrude(convexity=convexity) { + right_half(planar=true) { + polygon(endcap_shape1); } } - } - } else { - corner = select(path2,i-1,i+1); - axis = vector_axis(corner); - ang = vector_angle(corner); - if (!approx(ang,0)) { - frame_map(x=path2[i-1]-path2[i], z=-axis) { - zrot(90-0.5) { - rotate_extrude(angle=180-ang+1) { - arc(d=widths[i], start=-90, angle=180); - } + } else { + rotate([90,0,endcap_angle1]) { + linear_extrude(height=max(widths[0],0.001), center=true, convexity=convexity) { + polygon(endcap_shape1); } } } } } } - } - // Endcap1 - setcolor(endcap_color1) { - translate(path[0]) { - multmatrix(rotmats[0] * xrot(180)) { - $fn = sides[0]; - if (is_undef(endcap_angle1)) { - rotate_extrude(convexity=convexity) { - right_half(planar=true) { - polygon(endcap_shape1); + // Endcap2 + setcolor(endcap_color2) { + translate(last(path)) { + multmatrix(last(rotmats)) { + $fn = last(sides); + if (is_undef(endcap_angle2)) { + rotate_extrude(convexity=convexity) { + right_half(planar=true) { + polygon(endcap_shape2); + } } - } - } else { - rotate([90,0,endcap_angle1]) { - linear_extrude(height=max(widths[0],0.001), center=true, convexity=convexity) { - polygon(endcap_shape1); + } else { + rotate([90,0,endcap_angle2]) { + linear_extrude(height=max(last(widths),0.001), center=true, convexity=convexity) { + polygon(endcap_shape2); + } } } } } } - } - - // Endcap2 - setcolor(endcap_color2) { - translate(last(path)) { - multmatrix(last(rotmats)) { - $fn = last(sides); - if (is_undef(endcap_angle2)) { - rotate_extrude(convexity=convexity) { - right_half(planar=true) { - polygon(endcap_shape2); - } - } - } else { - rotate([90,0,endcap_angle2]) { - linear_extrude(height=max(last(widths),0.001), center=true, convexity=convexity) { - polygon(endcap_shape2); - } - } - } - } - } - } - } - } + } + } + } + } + union(); } } diff --git a/mutators.scad b/mutators.scad index 908a0c2..e658f1d 100644 --- a/mutators.scad +++ b/mutators.scad @@ -359,14 +359,18 @@ module extrude_from_to(pt1, pt2, convexity, twist, scale, slices) { pt1 = point3d(pt1); pt2 = point3d(pt2); rtp = xyz_to_spherical(pt2-pt1); - translate(pt1) { - rotate([0, rtp[2], rtp[1]]) { - if (rtp[0] > 0) { - linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) { - children(); - } - } - } + attachable() + { + translate(pt1) { + rotate([0, rtp[2], rtp[1]]) { + if (rtp[0] > 0) { + linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) { + children(); + } + } + } + } + union(); } } diff --git a/rounding.scad b/rounding.scad index 27cc1ec..6495889 100644 --- a/rounding.scad +++ b/rounding.scad @@ -1892,26 +1892,29 @@ module convex_offset_extrude( delta[i] == 1 ? above : /* delta[i] == -1 ? */ below]; dochamfer = offset=="chamfer"; - for(i=[0:len(r)-2]) - for(j=[0:$children-1]) - hull(){ - up(r[i][1]+layers[i][0]) - linear_extrude(convexity=convexity,height=layers[i][1]-layers[i][0]) - if (offset=="round") - offset(r=r[i][0]) - children(j); - else - offset(delta=r[i][0],chamfer = dochamfer) - children(j); - up(r[i+1][1]+layers[i+1][0]) - linear_extrude(convexity=convexity,height=layers[i+1][1]-layers[i+1][0]) - if (offset=="round") - offset(r=r[i+1][0]) - children(j); - else - offset(delta=r[i+1][0],chamfer=dochamfer) - children(j); - } + attachable(){ + for(i=[0:len(r)-2]) + for(j=[0:$children-1]) + hull(){ + up(r[i][1]+layers[i][0]) + linear_extrude(convexity=convexity,height=layers[i][1]-layers[i][0]) + if (offset=="round") + offset(r=r[i][0]) + children(j); + else + offset(delta=r[i][0],chamfer = dochamfer) + children(j); + up(r[i+1][1]+layers[i+1][0]) + linear_extrude(convexity=convexity,height=layers[i+1][1]-layers[i+1][0]) + if (offset=="round") + offset(r=r[i+1][0]) + children(j); + else + offset(delta=r[i+1][0],chamfer=dochamfer) + children(j); + } + union(); + } } diff --git a/threading.scad b/threading.scad index 293e748..68d5f19 100644 --- a/threading.scad +++ b/threading.scad @@ -2012,9 +2012,14 @@ module _nutshape(nutwidth, h, shape, bevel1, bevel2) // internal = if true make internal threads. The only effect this has is to change how the threads taper if tapering is selected. When true, threads taper towards the outside; when false, they taper towards the inside. Default: false // d1 = Bottom inside base diameter of threads. // d2 = Top inside base diameter of threads. -// taper = Length of tapers for thread ends. Positive to add taper to threads, negative to taper within specified length. Default: 0 -// taper1 = Length of taper for bottom thread end -// taper2 = Length of taper for top thread end +// lead_in = Specify linear length of the lead in section of the threading with blunt start threads +// lead_in1 = Specify linear length of the lead in section of the threading at the bottom with blunt start threads +// lead_in2 = Specify linear length of the lead in section of the threading at the top with blunt start threads +// lead_in_ang = Specify angular length in degrees of the lead in section of the threading with blunt start threads +// lead_in_ang1 = Specify angular length in degrees of the lead in section of the threading at the bottom with blunt start threads +// lead_in_ang2 = Specify angular length in degrees of the lead in section of the threading at the top with blunt start threads +// lead_in_shape = Specify the shape of the thread lead in by giving a text string or function. Default: "default" +// lead_in_sample = Factor to increase sample rate in the lead-in section. Default: 10 // 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` @@ -2060,7 +2065,11 @@ module _nutshape(nutwidth, h, shape, bevel1, bevel2) function thread_helix( d, pitch, thread_depth, flank_angle, turns, profile, starts=1, left_handed=false, internal=false, - d1, d2, taper, taper1, taper2, + d1, d2, + lead_in_shape, + lead_in, lead_in1, lead_in2, + lead_in_ang, lead_in_ang1, lead_in_ang2, + lead_in_sample=10, anchor, spin, orient ) = no_function("thread_helix"); module thread_helix( diff --git a/vnf.scad b/vnf.scad index 72231fe..db13f35 100644 --- a/vnf.scad +++ b/vnf.scad @@ -999,14 +999,21 @@ module vnf_polyhedron(vnf, convexity=2, extent=true, cp="centroid", anchor="orig // vnf_wireframe(octahedron,width=5); module vnf_wireframe(vnf, width=1) { + no_children($children); vertex = vnf[0]; edges = unique([for (face=vnf[1], i=idx(face)) sort([face[i], select(face,i+1)]) ]); - for (e=edges) extrude_from_to(vertex[e[0]],vertex[e[1]]) circle(d=width); - // Identify vertices actually used and draw them - vertused = search(count(len(vertex)), flatten(edges), 1); - for(i=idx(vertex)) if(vertused[i]!=[]) move(vertex[i]) sphere(d=width); + attachable() + { + union(){ + for (e=edges) extrude_from_to(vertex[e[0]],vertex[e[1]]) circle(d=width); + // Identify vertices actually used and draw them + vertused = search(count(len(vertex)), flatten(edges), 1); + for(i=idx(vertex)) if(vertused[i]!=[]) move(vertex[i]) sphere(d=width); + } + union(); + } } diff --git a/wiring.scad b/wiring.scad index f57d8a8..1c621d3 100644 --- a/wiring.scad +++ b/wiring.scad @@ -85,11 +85,14 @@ module wire_bundle(path, wires, wirediam=2, rounding=10, wirenum=0, corner_steps sides = max(segs(wirediam/2), 8); offsets = _hex_offsets(wires, wirediam); rounded_path = round_corners(path, radius=rounding, $fn=(corner_steps+1)*4, closed=false); - for (i = [0:1:wires-1]) { - extpath = move(offsets[i], p=circle(d=wirediam, $fn=sides)); - color(colors[(i+wirenum)%len(colors)]) { - path_sweep(extpath, rounded_path); - } + attachable(){ + for (i = [0:1:wires-1]) { + extpath = move(offsets[i], p=circle(d=wirediam, $fn=sides)); + color(colors[(i+wirenum)%len(colors)]) { + path_sweep(extpath, rounded_path); + } + } + union(); } }