From 11b4151f74a6682095bc9daef45aa161432a28cc Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 28 Jun 2021 18:02:13 -0700 Subject: [PATCH 1/3] Added Extruded Path geometry to attachable() and reorient(). --- attachments.scad | 118 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 13 deletions(-) diff --git a/attachments.scad b/attachments.scad index 3be687d..e145815 100644 --- a/attachments.scad +++ b/attachments.scad @@ -112,11 +112,13 @@ function anchorpt(name, pos=[0,0,0], orient=UP, spin=0) = [name, pos, orient, sp // Usage: Cubical/Prismoidal Geometry // geom = attach_geom(size=, [size2=], [shift=], ...); // Usage: Cylindrical Geometry -// geom = attach_geom(r=|d=, l=, [axis=], ...); +// geom = attach_geom(r=|d=, l=|h=, [axis=], ...); // Usage: Conical Geometry // geom = attach_geom(r1|d1=, r2=|d2=, l=, [axis=], ...); // Usage: Spheroid/Ovoid Geometry // geom = attach_geom(r=|d=, ...); +// Usage: Extruded 2D Path/Polygon Geometry +// geom = attach_geom(path=, l=|h=, [extent=], ...); // Usage: VNF Geometry // geom = attach_geom(vnf=, [extent=], ...); // @@ -138,7 +140,7 @@ function anchorpt(name, pos=[0,0,0], orient=UP, spin=0) = [name, pos, orient, sp // r2 = Radius of the top of the conical volume. Can be a scalar, or a list of sizes per axis. // d1 = Diameter of the bottom of the conical volume. Can be a scalar, a list of sizes per axis. // d2 = Diameter of the top of the conical volume. Can be a scalar, a list of sizes per axis. -// l = Length of the cylindrical/conical volume along axis. +// l/h = Length of the cylindrical, conical or extruded path volume along axis. // vnf = The [VNF](vnf.scad) of the volume. // path = The path to generate a polygon from. // extent = If true, calculate anchors by extents, rather than intersection. Default: true. @@ -205,6 +207,12 @@ function anchorpt(name, pos=[0,0,0], orient=UP, spin=0) = [name, pos, orient, sp // Example(NORENDER): Arbitrary 2D Polygon Shape, Anchored by Intersection // geom = attach_geom(two_d=true, path=path, extent=false); // +// Example(NORENDER): Extruded Polygon Shape, Anchored by Extents +// geom = attach_geom(path=path, l=height); +// +// Example(NORENDER): Extruded Polygon Shape, Anchored by Intersection +// geom = attach_geom(path=path, l=length, extent=false); +// function attach_geom( size, size2, shift, r,r1,r2, d,d1,d2, l,h, @@ -249,9 +257,16 @@ function attach_geom( ["vnf_isect", vnf, cp, offset, anchors] ) : !is_undef(path)? ( assert(is_path(path),2) - assert(two_d == true) - extent? ["path_extent", path, cp, offset, anchors] : - ["path_isect", path, cp, offset, anchors] + let( l = default(l, h) ) + two_d==true + ? assert(is_undef(l)) + extent==true + ? ["path_extent", path, cp, offset, anchors] + : ["path_isect", path, cp, offset, anchors] + : assert(is_finite(l)) + extent==true + ? ["xpath_extent", path, l, cp, offset, anchors] + : ["xpath_isect", path, l, cp, offset, anchors] ) : let( r1 = get_radius(r1=r1,d1=d1,r=r,d=d,dflt=undef) @@ -325,7 +340,7 @@ function attach_geom_size(geom) = approx(axis,UP)? [2*maxxr,2*maxyr,l] : approx(axis,RIGHT)? [l,2*maxyr,2*maxxr] : approx(axis,BACK)? [2*maxxr,l,2*maxyr] : - [2*maxxr, 2*maxyr,l] + [2*maxxr, 2*maxyr, l] ) : type == "spheroid"? ( //r let( r=geom[1] ) is_num(r)? [2,2,2]*r : v_mul([2,2,2],point3d(r)) @@ -337,6 +352,11 @@ function attach_geom_size(geom) = mm = pointlist_bounds(geom[1][0]), delt = mm[1]-mm[0] ) delt + ) : type == "xpath_isect" || type == "xpath_extent"? ( //path, l + let( + mm = pointlist_bounds(geom[1]), + delt = mm[1]-mm[0] + ) [delt.x, delt.y, geom[2]] ) : type == "rect"? ( //size, size2 let( size=geom[1], size2=geom[2], shift=geom[3], @@ -633,6 +653,46 @@ function find_anchor(anchor, geom) = avgy = (miny+maxy)/2, pos = point2d(cp) + rot(from=RIGHT, to=anchor, p=[maxx,avgy]) ) [anchor, pos, anchor, 0] + ) : type == "xpath_isect"? ( //path + let( + path = move(-point2d(cp), p=geom[1]), + l = geom[2], + anchor = point3d(anchor), + xyanch = point2d(anchor), + isects = [ + for (t=triplet(path,true)) let( + seg1 = [t[0],t[1]], + seg2 = [t[1],t[2]], + isect = ray_segment_intersection([[0,0],xyanch], seg1), + n = is_undef(isect)? [0,1] : + !approx(isect, t[1])? line_normal(seg1) : + unit((line_normal(seg1)+line_normal(seg2))/2,[0,1]), + n2 = vector_angle(xyanch,n)>90? -n : n + ) + if(!is_undef(isect) && !approx(isect,t[0])) + [norm(isect), isect, n2] + ], + maxidx = max_index(subindex(isects,0)), + isect = isects[maxidx], + pos = point3d(cp) + point3d(isect[1]) + unit([0,0,anchor.z],CENTER)*l/2, + xyvec = unit(isect[2],[0,1]), + vec = unit((point3d(xyvec)+UP)/2,UP), + oang = approx(xyvec, [0,0])? 0 : atan2(xyvec.y, xyvec.x) + 90 + ) [anchor, pos, vec, oang] + ) : type == "xpath_extent"? ( //path + let( + path = geom[1], l = geom[2], + anchor = point3d(anchor), + xyanch = point2d(anchor), + rpath = rot(from=xyanch, to=RIGHT, p=move(point2d(-cp), p=path)), + maxx = max(subindex(rpath,0)), + idxs = [for (i = idx(rpath)) if (approx(rpath[i].x, maxx)) i], + ys = [for (i=idxs) rpath[i].y], + avgy = (min(ys)+max(ys))/2, + xypos = point2d(cp) + rot(from=RIGHT, to=xyanch, p=[maxx,avgy]), + pos = point3d(xypos) + unit([0,0,anchor.z],CENTER)*l/2, + vec = unit((point3d(xyanch)+UP)/2,UP) + ) [anchor, pos, vec, oang] ) : assert(false, "Unknown attachment geometry type."); @@ -677,6 +737,9 @@ function attachment_is_shown(tags) = // Usage: Spheroid/Ovoid Geometry // mat = reorient(anchor, spin, [orient], r|d=, ...); // pts = reorient(anchor, spin, [orient], r|d=, p=, ...); +// Usage: Extruded Path/Polygon Geometry +// mat = reorient(anchor, spin, [orient], path=, l=|h=, [extent=], ...); +// pts = reorient(anchor, spin, [orient], path=, l=|h=, [extent=], p=, ...); // Usage: VNF Geometry // mat = reorient(anchor, spin, [orient], vnf, [extent], ...); // pts = reorient(anchor, spin, [orient], vnf, [extent], p=, ...); @@ -722,7 +785,7 @@ function attachment_is_shown(tags) = // r2 = Radius of the top of the conical volume. Can be a scalar, or a list of sizes per axis. // d1 = Diameter of the bottom of the conical volume. Can be a scalar, a list of sizes per axis. // d2 = Diameter of the top of the conical volume. Can be a scalar, a list of sizes per axis. -// l = Length of the cylindrical/conical volume along axis. +// l/h = Length of the cylindrical, conical, or extruded path volume along axis. // vnf = The [VNF](vnf.scad) of the volume. // path = The path to generate a polygon from. // extent = If true, calculate anchors by extents, rather than intersection. Default: false. @@ -785,6 +848,8 @@ function reorient( // attachable(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], ...) {...} // Usage: Spheroid/Ovoid Geometry // attachable(anchor, spin, [orient], r=|d=, ...) {...} +// Usage: Extruded Path/Polygon Geometry +// attachable(anchor, spin, path=, l=|h=, [extent=], ...) {...} // Usage: VNF Geometry // attachable(anchor, spin, [orient], vnf=, [extent=], ...) {...} // @@ -834,10 +899,10 @@ function reorient( // r2 = Radius of the top of the conical volume. Can be a scalar, or a list of sizes per axis. // d1 = Diameter of the bottom of the conical volume. Can be a scalar, a list of sizes per axis. // d2 = Diameter of the top of the conical volume. Can be a scalar, a list of sizes per axis. -// l = Length of the cylindrical/conical volume along axis. +// l/h = Length of the cylindrical, conical, or extruded path volume along axis. // vnf = The [VNF](vnf.scad) of the volume. // path = The path to generate a polygon from. -// extent = If true, calculate anchors by extents, rather than intersection. Default: false. +// extent = If true, calculate anchors by extents, rather than intersection, for VNFs and paths. Default: true. // cp = If given, specifies the centerpoint of the volume. Default: `[0,0,0]` // offset = If given, offsets the perimeter of the volume around the centerpoint. // anchors = If given as a list of anchor points, allows named anchor points. @@ -910,14 +975,34 @@ function reorient( // children(); // } // -// Example(NORENDER): Arbitrary VNF Shape +// Example(NORENDER): Extruded Polygon Shape, by Extents +// attachable(anchor, spin, orient, path=path, l=length) { +// 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) { +// linear_extrude(height=length, center=true) +// polygon(path); +// children(); +// } +// +// Example(NORENDER): Arbitrary VNF Shape, by Extents // attachable(anchor, spin, orient, vnf=vnf) { // vnf_polyhedron(vnf); // children(); // } // +// Example(NORENDER): Arbitrary VNF Shape, by Intersection +// attachable(anchor, spin, orient, vnf=vnf, extent=false) { +// vnf_polyhedron(vnf); +// children(); +// } +// // Example(NORENDER): 2D Rectangular Shape -// attachable(anchor, spin, orient, size=size) { +// attachable(anchor, spin, orient, two_d=true, size=size) { // square(size, center=true); // children(); // } @@ -925,6 +1010,7 @@ function reorient( // Example(NORENDER): 2D Trapezoidal Shape // attachable( // anchor, spin, orient, +// two_d=true, // size=[x1,y], // size2=x2, // shift=shift @@ -939,8 +1025,14 @@ function reorient( // children(); // } // -// Example(NORENDER): Arbitrary 2D Polygon Shape -// attachable(anchor, spin, orient, path=path) { +// Example(NORENDER): Arbitrary 2D Polygon Shape, by Extents +// attachable(anchor, spin, orient, two_d=true, path=path) { +// polygon(path); +// children(); +// } +// +// Example(NORENDER): Arbitrary 2D Polygon Shape, by Intersection +// attachable(anchor, spin, orient, two_d=true, path=path, extent=false) { // polygon(path); // children(); // } From 0fea590d0ff636f4d4611e64f6a735f3f560387a Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 28 Jun 2021 18:04:27 -0700 Subject: [PATCH 2/3] Fix for #129 --- partitions.scad | 78 ++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/partitions.scad b/partitions.scad index 747279f..bdeb040 100644 --- a/partitions.scad +++ b/partitions.scad @@ -10,21 +10,20 @@ // Section: Partitioning -_partition_cutpaths = [ - ["flat", [[0,0],[1,0]]], - ["sawtooth", [[0,-0.5], [0.5,0.5], [1,-0.5]]], - ["sinewave", [for (a=[0:5:360]) [a/360,sin(a)/2]]], - ["comb", let(dx=0.5*sin(2)) [[0,0],[0+dx,0.5],[0.5-dx,0.5],[0.5+dx,-0.5],[1-dx,-0.5],[1,0]]], - ["finger", let(dx=0.5*sin(20)) [[0,0],[0+dx,0.5],[0.5-dx,0.5],[0.5+dx,-0.5],[1-dx,-0.5],[1,0]]], - ["dovetail", [[0,-0.5], [0.3,-0.5], [0.2,0.5], [0.8,0.5], [0.7,-0.5], [1,-0.5]]], - ["hammerhead", [[0,-0.5], [0.35,-0.5], [0.35,0], [0.15,0], [0.15,0.5], [0.85,0.5], [0.85,0], [0.65,0], [0.65,-0.5],[1,-0.5]]], - ["jigsaw", concat( - arc(N=6, r=5/16, cp=[0,-3/16], start=270, angle=125), - arc(N=12, r=5/16, cp=[1/2,3/16], start=215, angle=-250), - arc(N=6, r=5/16, cp=[1,-3/16], start=145, angle=125) - ) - ], -]; +function _partition_subpath(type) = + type=="flat"? [[0,0],[1,0]] : + type=="sawtooth"? [[0,-0.5], [0.5,0.5], [1,-0.5]] : + type=="sinewave"? [for (a=[0:5:360]) [a/360,sin(a)/2]] : + type=="comb"? let(dx=0.5*sin(2)) [[0,0],[0+dx,0.5],[0.5-dx,0.5],[0.5+dx,-0.5],[1-dx,-0.5],[1,0]] : + type=="finger"? let(dx=0.5*sin(20)) [[0,0],[0+dx,0.5],[0.5-dx,0.5],[0.5+dx,-0.5],[1-dx,-0.5],[1,0]] : + type=="dovetail"? [[0,-0.5], [0.3,-0.5], [0.2,0.5], [0.8,0.5], [0.7,-0.5], [1,-0.5]] : + type=="hammerhead"? [[0,-0.5], [0.35,-0.5], [0.35,0], [0.15,0], [0.15,0.5], [0.85,0.5], [0.85,0], [0.65,0], [0.65,-0.5],[1,-0.5]] : + type=="jigsaw"? concat( + arc(r=5/16, cp=[0,-3/16], start=270, angle=125), + arc(r=5/16, cp=[1/2,3/16], start=215, angle=-250), + arc(r=5/16, cp=[1,-3/16], start=145, angle=125) + ) : + assert(false, str("Unsupported cutpath type: ", type)); function _partition_cutpath(l, h, cutsize, cutpath, gap) = @@ -35,24 +34,26 @@ function _partition_cutpath(l, h, cutsize, cutpath, gap) = assert(is_finite(cutsize) || is_vector(cutsize,2)) assert(is_string(cutpath) || is_path(cutpath,2)), cutsize = is_vector(cutsize)? cutsize : [cutsize*2, cutsize], - cutpath = is_path(cutpath)? cutpath : ( - let(idx = search([cutpath], _partition_cutpaths)) - idx==[[]]? assert(in_list(cutpath,_partition_cutpaths,idx=0)) : - _partition_cutpaths[idx.x][1] - ), + cutpath = is_path(cutpath)? cutpath : + _partition_subpath(cutpath), reps = ceil(l/(cutsize.x+gap)), cplen = (cutsize.x+gap) * reps, path = deduplicate(concat( [[-l/2, cutpath[0].y*cutsize.y]], [for (i=[0:1:reps-1], pt=cutpath) v_mul(pt,cutsize)+[i*(cutsize.x+gap)+gap/2-cplen/2,0]], [[ l/2, cutpath[len(cutpath)-1].y*cutsize.y]] - )) - ) path; + )), + stidxs = [for (i = idx(path)) if (path[i].x < -l/2) i], + enidxs = [for (i = idx(path)) if (path[i].x > +l/2) i], + stidx = stidxs? last(stidxs) : 0, + enidx = enidxs? enidxs[0] : -1, + trunc = select(path, stidx, enidx) + ) trunc; // Module: partition_mask() // Usage: -// partition_mask(l, w, h, [cutsize], [cutpath], [gap], [inverse], [spin], [orient]); +// partition_mask(l, w, h, [cutsize], [cutpath], [gap], [inverse], [spin], [orient],); // Description: // Creates a mask that you can use to difference or intersect with an object to remove half of it, leaving behind a side designed to allow assembly of the sub-parts. // Arguments: @@ -65,6 +66,7 @@ function _partition_cutpath(l, h, cutsize, cutpath, gap) = // inverse = If true, create a cutpath that is meant to mate to a non-inverted cutpath. // spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#spin). Default: `0` // orient = Vector to rotate top towards. See [orient](attachments.scad#orient). Default: `UP` +// $slop = The amount to shrink the mask by, to correct for printer-specific fitting. // Examples: // partition_mask(w=50, gap=0, cutpath="jigsaw"); // partition_mask(w=50, gap=30, cutpath="jigsaw"); @@ -79,17 +81,22 @@ function _partition_cutpath(l, h, cutsize, cutpath, gap) = // partition_mask(w=20, cutpath="dovetail"); // partition_mask(w=20, cutpath="hammerhead"); // partition_mask(w=20, cutpath="jigsaw"); -module partition_mask(l=100, w=100, h=100, cutsize=10, cutpath=undef, gap=0, inverse=false, spin=0, orient=UP) +module partition_mask(l=100, w=100, h=100, cutsize=10, cutpath="jigsaw", gap=0, inverse=false, anchor=CENTER, spin=0, orient=UP) { cutsize = is_vector(cutsize)? point2d(cutsize) : [cutsize*2, cutsize]; path = _partition_cutpath(l, h, cutsize, cutpath, gap); - fullpath = concat(path, [[l/2,w*(inverse?-1:1)], [-l/2,w*(inverse?-1:1)]]); - rot(from=UP,to=orient) { - rotate(spin) { - linear_extrude(height=h, convexity=10) { + midpath = select(path,1,-2); + sizepath = concat([path[0]+[-$slop,0]], midpath, [last(path)+[$slop,0]], [[+(l/2+$slop), (w+$slop)*(inverse?-1:1)], [-(l/2+$slop), (w+$slop)*(inverse?-1:1)]]); + bnds = pointlist_bounds(sizepath); + fullpath = concat(path, [[last(path).x, w*(inverse?-1:1)], [path[0].x, w*(inverse?-1:1)]]); + attachable(anchor,spin,orient, size=point3d(bnds[1]-bnds[0],h)) { + linear_extrude(height=h, center=true, convexity=10) { + intersection() { offset(delta=-$slop) polygon(fullpath); + square([l, w*2], center=true); } } + children(); } } @@ -104,10 +111,11 @@ module partition_mask(l=100, w=100, h=100, cutsize=10, cutpath=undef, gap=0, inv // w = The width of the part to be masked, back from the cut plane. // h = The height of the part to be masked. // cutsize = The width of the cut pattern to be used. -// cutpath = The cutpath to use. Standard named paths are "flat", "sawtooth", "sinewave", "comb", "finger", "dovetail", "hammerhead", and "jigsaw". Alternatively, you can give a cutpath as a 2D path, where X is between 0 and 1, and Y is between -0.5 and 0.5. +// cutpath = The cutpath to use. Standard named paths are "flat", "sawtooth", "sinewave", "comb", "finger", "dovetail", "hammerhead", and "jigsaw". Alternatively, you can give a cutpath as a 2D path, where X is between 0 and 1, and Y is between -0.5 and 0.5. Default: "jigsaw" // gap = Empty gaps between cutpath iterations. Default: 0 // spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#spin). Default: `0` // orient = Vector to rotate top towards. See [orient](attachments.scad#orient). Default: `UP` +// $slop = The width of the cut mask, to correct for printer-specific fitting. Min: 0.1. // Examples: // partition_cut_mask(gap=0, cutpath="dovetail"); // partition_cut_mask(gap=30, cutpath="dovetail"); @@ -121,15 +129,13 @@ module partition_mask(l=100, w=100, h=100, cutsize=10, cutpath=undef, gap=0, inv // partition_cut_mask(cutpath="dovetail"); // partition_cut_mask(cutpath="hammerhead"); // partition_cut_mask(cutpath="jigsaw"); -module partition_cut_mask(l=100, h=100, cutsize=10, cutpath=undef, gap=0, spin=0, orient=UP) +module partition_cut_mask(l=100, h=100, cutsize=10, cutpath="jigsaw", gap=0, anchor=CENTER, spin=0, orient=UP) { cutsize = is_vector(cutsize)? cutsize : [cutsize*2, cutsize]; path = _partition_cutpath(l, h, cutsize, cutpath, gap); - rot(from=UP,to=orient) { - rotate(spin) { - linear_extrude(height=h, convexity=10) { - stroke(path, width=max(0.1, $slop*2)); - } + attachable(anchor,spin,orient, size=[l,cutsize.y,h]) { + linear_extrude(height=h, center=true, convexity=10) { + stroke(path, width=max(0.1, $slop*2)); } } } @@ -160,7 +166,7 @@ module partition_cut_mask(l=100, h=100, cutsize=10, cutpath=undef, gap=0, spin=0 // partition(spread=12, cutpath="dovetail") cylinder(h=50, d=80, center=false); // partition(spread=12, cutpath="hammerhead") cylinder(h=50, d=80, center=false); // partition(cutpath="jigsaw") cylinder(h=50, d=80, center=false); -module partition(size=100, spread=10, cutsize=10, cutpath=undef, gap=0, spin=0) +module partition(size=100, spread=10, cutsize=10, cutpath="jigsaw", gap=0, spin=0) { size = is_vector(size)? size : [size,size,size]; cutsize = is_vector(cutsize)? cutsize : [cutsize*2, cutsize]; From af3b427a9783f08cbd00a65f7bf79933f321260e Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 28 Jun 2021 18:07:29 -0700 Subject: [PATCH 3/3] Added missing 'children()' call. --- partitions.scad | 1 + 1 file changed, 1 insertion(+) diff --git a/partitions.scad b/partitions.scad index bdeb040..53741f8 100644 --- a/partitions.scad +++ b/partitions.scad @@ -137,6 +137,7 @@ module partition_cut_mask(l=100, h=100, cutsize=10, cutpath="jigsaw", gap=0, anc linear_extrude(height=h, center=true, convexity=10) { stroke(path, width=max(0.1, $slop*2)); } + children(); } }