From 69e8491659dd5fe67ee265ac9489b23b34e893bd Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Thu, 11 Nov 2021 08:45:30 -0500 Subject: [PATCH 1/5] doc tweaks vnf_merge split --- beziers.scad | 4 ++-- edges.scad | 26 ++++++++++++++------- gears.scad | 4 ++-- regions.scad | 2 +- rounding.scad | 2 +- shapes3d.scad | 2 +- skin.scad | 4 ++-- threading.scad | 2 +- vnf.scad | 62 +++++++++++++++++++++++++++++--------------------- 9 files changed, 64 insertions(+), 44 deletions(-) diff --git a/beziers.scad b/beziers.scad index ffa61d4..90a2a37 100644 --- a/beziers.scad +++ b/beziers.scad @@ -1170,7 +1170,7 @@ function is_patch(x) = // // u=0,v=1 u=1,v=1 // ]; // tpatch = translate([-50,-50,50], patch); -// vnf = vnf_merge([ +// vnf = vnf_join([ // bezier_patch(tpatch), // bezier_patch(xrot(90, tpatch)), // bezier_patch(xrot(-90, tpatch)), @@ -1541,7 +1541,7 @@ function patch_reverse(patch) = // vnf = bezier_surface(patches=[patch1, patch2], splinesteps=16); // polyhedron(points=vnf[0], faces=vnf[1]); function bezier_surface(patches=[], splinesteps=16, style="default") = - vnf_merge([for(patch=patches) bezier_patch(patch, splinesteps=splinesteps, style=style)]); + vnf_join([for(patch=patches) bezier_patch(patch, splinesteps=splinesteps, style=style)]); // Module: trace_bezier_patches() diff --git a/edges.scad b/edges.scad index d0706b1..9462404 100644 --- a/edges.scad +++ b/edges.scad @@ -40,7 +40,7 @@ // } // Section: Specifying Faces // Modules operating on faces accept a list of faces to describe the faces to operate on. Each -// face is given by a vector that points to that face. Attachments of cuboid objects also +// face is given by a vector that points to that face. Attachments of cuboid objects onto their faces also // work by choosing an attachment face with a single vector in the same manner. // Figure(3D,Big,NoScales,VPD=275): The six faces of the cube. Some have faces have more than one name. // ydistribute(50) { @@ -57,8 +57,11 @@ // } // Section: Specifying Edges // Modules operating on edges use two arguments to describe the edge set they will use: The `edges` argument -// is a list of edge set descriptors to include in the edge set and the `except` argument is a list of -// edge set descriptors to remove from the edge set. If either argument is just a single edge set +// is a list of edge set descriptors to include in the edge set, and the `except` argument is a list of +// edge set descriptors to remove from the edge set. +// The default value for `edges` is `"ALL"`, the set of all edges. +// The default value for `except` is the empty set, meaning no edges are removed. +// If either argument is just a single edge set // descriptor it can be passed directly rather than in a singleton list. // Each edge set descriptor must be one of: // - A vector pointing towards an edge, indicating that single edge. @@ -79,7 +82,8 @@ // ``` // You can specify edge descriptors directly by giving a vector, or you can use sums of the // named direction vectors described above. Below we show all of the edge sets you can -// describe with sums of the direction vectors. +// describe with sums of the direction vectors, and then we show some examples of combining +// edge set descriptors. // Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward an edge select that single edge // ydistribute(50) { // xdistribute(30) { @@ -141,7 +145,7 @@ // _show_edges(edges="NONE"); // } // } -// Figure(3D,Big,VPD=310,NoScales): Next are some examples showing how you can combine edge descriptors to obtain different edge sets. The default value for `edges` is `"ALL"`, the set of all edges. The default value for `except` is the empty set, meaning no edges are removed. You can specify the top front edge with a numerical vector or by combining the named direction vectors. If you combine them as a list you get all the edges around the front or top faces. Adding `except` removes an edge. +// Figure(3D,Big,VPD=310,NoScales): Next are some examples showing how you can combine edge descriptors to obtain different edge sets. You can specify the top front edge with a numerical vector or by combining the named direction vectors. If you combine them as a list you get all the edges around the front or top faces. Adding `except` removes an edge. // xdistribute(43){ // _show_edges(_edges([0,-1,1]),toplabel=["edges=[0,-1,1]"]); // _show_edges(_edges(TOP+FRONT),toplabel=["edges=TOP+FRONT"]); @@ -165,7 +169,10 @@ // Section: Specifying Corners // Modules operating on corners use two arguments to describe the corner set they will use: The `corners` argument // is a list of corner set descriptors to include in the corner set, and the `except` argument is a list of -// corner set descriptors to remove from the corner set. If either argument is just a single corner set +// corner set descriptors to remove from the corner set. +// The default value for `corners` is `"ALL"`, the set of all corners. +// The default value for `except` is the empty set, meaning no corners are removed. +// If either argument is just a single corner set // descriptor it can be passed directly rather than in a singleton list. // Each corner set descriptor must be one of: // - A vector pointing towards a corner, indicating that corner. @@ -179,7 +186,8 @@ // ``` // You can specify corner descriptors directly by giving a vector, or you can use sums of the // named direction vectors described above. Below we show all of the corner sets you can -// describe with sums of the direction vectors. +// describe with sums of the direction vectors and then we show some examples of combining +// corner set descriptors. // Figure(3D,Big,NoScales,VPD=300): Vectors pointing toward a corner select that corner. // ydistribute(55) { // xdistribute(35) { @@ -234,7 +242,7 @@ // _show_corners(corners="ALL"); // _show_corners(corners="NONE"); // } -// Figure(3D,Big,NoScales,VPD=300): Next are some examples showing how you can combine corner descriptors to obtain different corner sets. The default value for `corners` is `"ALL"`, the set of all corners. The default value for `except` is the empty set, meaning no corners are removed. You can specify corner sets numerically or by adding together named directions. The third example shows a list of two corner specifications, giving all the corners on the front face or the right face. +// Figure(3D,Big,NoScales,VPD=300): Next are some examples showing how you can combine corner descriptors to obtain different corner sets. You can specify corner sets numerically or by adding together named directions. The third example shows a list of two corner specifications, giving all the corners on the front face or the right face. // xdistribute(52){ // _show_corners(_corners([1,-1,-1]),toplabel=["corners=[1,-1,-1]"]); // _show_corners(_corners(BOT+RIGHT+FRONT),toplabel=["corners=BOT+RIGHT+FRONT"]); @@ -425,6 +433,7 @@ function _normalize_edges(v) = [for (ax=v) [for (edge=ax) edge>0? 1 : 0]]; /// See Also: EDGES_NONE, EDGES_ALL /// function _edges(v, except=[]) = + v==[] ? EDGES_NONE : (is_string(v) || is_vector(v) || _is_edge_array(v))? _edges([v], except=except) : (is_string(except) || is_vector(except) || _is_edge_array(except))? _edges(v, except=[except]) : except==[]? _normalize_edges(sum([for (x=v) _edge_set(x)])) : @@ -559,6 +568,7 @@ function _corner_set(v) = /// from the returned corners array. If either argument only has a single corner /// set descriptor, you do not have to pass it in a list. function _corners(v, except=[]) = + v==[] ? CORNERS_NONE : (is_string(v) || is_vector(v) || _is_corner_array(v))? _corners([v], except=except) : (is_string(except) || is_vector(except) || _is_corner_array(except))? _corners(v, except=[except]) : except==[]? _normalize_corners(sum([for (x=v) _corner_set(x)])) : diff --git a/gears.scad b/gears.scad index 58a9122..4c2b6a1 100644 --- a/gears.scad +++ b/gears.scad @@ -980,7 +980,7 @@ function bevel_gear( [gear_pts, ((i+1)%teeth)*face_pts, (i+1)*face_pts-1] ] ], - vnf1 = vnf_merge([ + vnf1 = vnf_join([ [ [each top_verts, [0,0,top_verts[0].z]], top_faces @@ -1451,7 +1451,7 @@ function worm_gear( ] ], sides_vnf = vnf_vertex_array(profiles, caps=false, col_wrap=true, style="min_edge"), - vnf1 = vnf_merge([ + vnf1 = vnf_join([ [ [each top_verts, [0,0,top_verts[0].z]], [for (x=top_faces) reverse(x)] diff --git a/regions.scad b/regions.scad index e05d7a3..3f7a0de 100644 --- a/regions.scad +++ b/regions.scad @@ -674,7 +674,7 @@ function linear_sweep(region, height=1, center, twist=0, scale=1, slices, rot(twist, p=scale([scale,scale],p=path)) ] ], - vnf = vnf_merge([ + vnf = vnf_join([ for (rgn = regions) for (pathnum = idx(rgn)) let( p = cleanup_path(rgn[pathnum]), diff --git a/rounding.scad b/rounding.scad index ae2edb3..bb16be0 100644 --- a/rounding.scad +++ b/rounding.scad @@ -1962,7 +1962,7 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b "Roundovers interfere with each other on bottom face: either input is self intersecting or top joint length is too large") assert(debug || (verify_vert==[] && verify_horiz==[]), "Curvature continuity failed") let( - vnf = vnf_merge([ each column(top_samples,0), + vnf = vnf_join([ each column(top_samples,0), each column(bot_samples,0), for(pts=edge_points) vnf_vertex_array(pts), debug ? vnf_from_polygons(faces) diff --git a/shapes3d.scad b/shapes3d.scad index 2008346..12101e8 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -2486,7 +2486,7 @@ function heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04 ] ] ], - vnf = vnf_merge([ + vnf = vnf_join([ vnf_vertex_array(verts, style=style, reverse=true), vnf_vertex_array([ verts[0], diff --git a/skin.scad b/skin.scad index 888b009..04daf88 100644 --- a/skin.scad +++ b/skin.scad @@ -492,7 +492,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close ) subdivide_and_slice(pair,slices[i], nsamples, method=sampling)] ) - vnf_merge(cleanup=false, + vnf_join( [for(i=idx(full_list)) vnf_vertex_array(full_list[i], cap1=i==0 && fullcaps[0], cap2=i==len(full_list)-1 && fullcaps[1], col_wrap=true, style=style)]); @@ -1120,7 +1120,7 @@ function sweep(shape, transforms, closed=false, caps, style="min_edge") = if (fullcaps[1]) vnf_from_region(rgn, transform=last(transforms)), ], ], - vnf = vnf_merge(vnfs) + vnf = vnf_join(vnfs) ) vnf : assert(len(shape)>=3, "shape must be a path of at least 3 non-colinear points") vnf_vertex_array([for(i=[0:len(transforms)-(closed?0:1)]) apply(transforms[i%len(transforms)],path3d(shape))], diff --git a/threading.scad b/threading.scad index 94f586b..ff40aba 100644 --- a/threading.scad +++ b/threading.scad @@ -971,7 +971,7 @@ module generic_threaded_rod( style = higang1>0 || higang2>0 ? "quincunx" : "min_edge"; - thread_vnfs = vnf_merge([ + thread_vnfs = vnf_join([ // Main thread faces for (i=[0:1:starts-1]) zrot(i*360/starts, p=vnf_vertex_array(thread_verts, reverse=left_handed, style=style)), diff --git a/vnf.scad b/vnf.scad index 0e7684f..874f2f3 100644 --- a/vnf.scad +++ b/vnf.scad @@ -217,14 +217,14 @@ function vnf_vertex_array( // Example(3D): Merging two VNFs to construct a cone with one point length change between rows. // pts1 = [for(z=[0:10]) path3d(arc(3+z,r=z/2+1, angle=[0,180]),10-z)]; // pts2 = [for(z=[0:10]) path3d(arc(3+z,r=z/2+1, angle=[180,360]),10-z)]; -// vnf = vnf_merge([vnf_tri_array(pts1), +// vnf = vnf_join([vnf_tri_array(pts1), // vnf_tri_array(pts2)]); // color("green")vnf_wireframe(vnf,width=0.1); // vnf_polyhedron(vnf); // Example(3D): Cone with length change two between rows // pts1 = [for(z=[0:1:10]) path3d(arc(3+2*z,r=z/2+1, angle=[0,180]),10-z)]; // pts2 = [for(z=[0:1:10]) path3d(arc(3+2*z,r=z/2+1, angle=[180,360]),10-z)]; -// vnf = vnf_merge([vnf_tri_array(pts1), +// vnf = vnf_join([vnf_tri_array(pts1), // vnf_tri_array(pts2)]); // color("green")vnf_wireframe(vnf,width=0.1); // vnf_polyhedron(vnf); @@ -284,22 +284,20 @@ function vnf_tri_array(points, row_wrap=false, reverse=false) = -// Function: vnf_merge() +// Function: vnf_join() // Usage: -// vnf = vnf_merge([VNF, VNF, VNF, ...], [cleanup],[eps]); +// vnf = vnf_join([VNF, VNF, VNF, ...]); // Description: // Given a list of VNF structures, merges them all into a single VNF structure. -// When cleanup=true, it consolidates all duplicate vertices with a tolerance `eps`, -// and eliminates any faces with fewer than 3 vertices. -// (Unreferenced vertices of the input VNFs are not dropped.) +// Combines all the points of the input VNFs and labels the faces appropriately. +// All the points in the input VNFs will appear in the output, even if they are +// duplicates of each other. It is valid to repeat points in a VNF, but if you +// with to remove the duplicates that will occur along joined edges, use {{vnf_merge_points()}}. // Arguments: -// vnfs = a list of the VNFs to merge in one VNF. -// cleanup = when true, consolidates the duplicate vertices of the merge. Default: false -// eps = the tolerance in finding duplicates when cleanup=true. Default: EPSILON -function vnf_merge(vnfs, cleanup=false, eps=EPSILON) = - is_vnf(vnfs) ? vnf_merge([vnfs], cleanup, eps) : - assert( is_vnf_list(vnfs) , "Improper vnf or vnf list") - len(vnfs)==1 ? (cleanup ? _vnf_cleanup(vnfs[0][0],vnfs[0][1],eps) : vnfs[0]) +// vnfs = a list of the VNFs to joint into one VNF. +function vnf_join(vnfs) = + assert(is_vnf_list(vnfs) , "Input must be a list of VNFs") + len(vnfs)==1 ? vnfs[0] : let ( offs = cumsum([ 0, for (vnf = vnfs) len(vnf[0]) ]), @@ -315,18 +313,30 @@ function vnf_merge(vnfs, cleanup=false, eps=EPSILON) = offs[i] + j ] ] ) - cleanup? _vnf_cleanup(verts,faces,eps) : [verts,faces]; + [verts,faces]; -function _vnf_cleanup(verts,faces,eps) = + +// Function: vnf_merge_points() +// Usage: +// new_vnf = vnf_merge_points(vnf, [eps]); +// Description: +// Given a VNF, consolidates all duplicate vertices with a tolerance `eps`, relabeling the faces as necessary, +// and eliminating any face with fewer than 3 vertices. Unreferenced vertices of the input VNF are not dropped. +// To remove such vertices uses {{vnf_drop_unused_points()}}. +// Arguments: +// vnf = a VNF to consolidate +// eps = the tolerance in finding duplicates. Default: EPSILON +function vnf_merge_points(vnf,eps=EPSILON) = let( + verts = vnf[0], dedup = vector_search(verts,eps,verts), // collect vertex duplicates map = [for(i=idx(verts)) min(dedup[i]) ], // remap duplic vertices offset = cumsum([for(i=idx(verts)) map[i]==i ? 0 : 1 ]), // remaping face vertex offsets map2 = list(idx(verts))-offset, // map old vertex indices to new indices nverts = [for(i=idx(verts)) if(map[i]==i) verts[i] ], // this doesn't eliminate unreferenced vertices nfaces = - [ for(face=faces) + [ for(face=vnf[1]) let( nface = [ for(vi=face) map2[map[vi]] ], dface = [for (i=idx(nface)) @@ -346,7 +356,7 @@ function _vnf_cleanup(verts,faces,eps) = // Given a list of 3d polygons, produces a VNF containing those polygons. // It is up to the caller to make sure that the points are in the correct order to make the face // normals point outwards. No checking for duplicate vertices is done. If you want to -// remove duplicate vertices use vnf_merge with the cleanup option. +// remove duplicate vertices use {{vnf_merge_points()}}. // Arguments: // polygons = The list of 3d polygons to turn into a VNF function vnf_from_polygons(polygons) = @@ -533,7 +543,7 @@ function vnf_from_region(region, transform, reverse=false) = faceidxs = reverse? [for (i=[len(face)-1:-1:0]) i] : [for (i=[0:1:len(face)-1]) i] ) [face, [faceidxs]] ], - outvnf = vnf_merge(vnfs) + outvnf = vnf_join(vnfs) ) vnf_triangulate(outvnf); @@ -673,7 +683,7 @@ function vnf_slice(vnf,dir,cuts) = faces = [for(face=vnf[1]) select(vert,face)], poly_list = _slice_3dpolygons(faces, dir, cuts) ) - vnf_merge([vnf_from_polygons(poly_list)], cleanup=true); + vnf_merge_points(vnf_from_polygons(poly_list)); function _split_polygon_at_x(poly, x) = @@ -777,7 +787,7 @@ function _slice_3dpolygons(polys, dir, cuts) = // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP` module vnf_polyhedron(vnf, convexity=2, extent=true, cp=[0,0,0], anchor="origin", spin=0, orient=UP) { - vnf = is_vnf_list(vnf)? vnf_merge(vnf) : vnf; + vnf = is_vnf_list(vnf)? vnf_join(vnf) : vnf; cp = is_def(cp) ? cp : centroid(vnf); attachable(anchor,spin,orient, vnf=vnf, extent=extent, cp=cp) { polyhedron(vnf[0], vnf[1], convexity=convexity); @@ -943,7 +953,7 @@ function vnf_halfspace(plane, vnf, closed=true) = faceregion = [for(path=newpaths) path2d(apply(M,select(newvert,path)))], facevnf = vnf_from_region(faceregion,transform=rot_inverse(M),reverse=true) ) - vnf_merge([[newvert, faces_edges_vertices[0]], facevnf]); + vnf_join([[newvert, faces_edges_vertices[0]], facevnf]); function _assemble_paths(vertices, edges, paths=[],i=0) = @@ -1315,7 +1325,7 @@ module vnf_debug(vnf, faces=true, vertices=true, opacity=0.5, size=1, convexity= // path3d(square(100,center=true),0), // path3d(square(100,center=true),100), // ], slices=0, caps=false); -// vnf = vnf_merge([vnf1, vnf_from_polygons([ +// vnf = vnf_join([vnf1, vnf_from_polygons([ // [[-50,-50, 0], [ 50, 50, 0], [-50, 50, 0]], // [[-50,-50, 0], [ 50,-50, 0], [ 50, 50, 0]], // [[-50,-50,100], [-50, 50,100], [ 50, 50,100]], @@ -1327,7 +1337,7 @@ module vnf_debug(vnf, faces=true, vertices=true, opacity=0.5, size=1, convexity= // path3d(square(100,center=true),0), // path3d(square(100,center=true),100), // ], slices=0, caps=false); -// vnf = vnf_merge([vnf1, vnf_from_polygons([ +// vnf = vnf_join([vnf1, vnf_from_polygons([ // [[-50,-50,0], [50,50,0], [-50,50,0]], // [[-50,-50,0], [50,-50,0], [50,50,0]], // [[-50,-50,100], [-50,50,100], [0,50,100]], @@ -1337,7 +1347,7 @@ module vnf_debug(vnf, faces=true, vertices=true, opacity=0.5, size=1, convexity= // ])]); // vnf_validate(vnf); // Example: FACE_ISECT Errors; Faces Intersect -// vnf = vnf_merge([ +// vnf = vnf_join([ // vnf_triangulate(linear_sweep(square(100,center=true), height=100)), // move([75,35,30],p=vnf_triangulate(linear_sweep(square(100,center=true), height=100))) // ]); @@ -1351,7 +1361,7 @@ module vnf_debug(vnf, faces=true, vertices=true, opacity=0.5, size=1, convexity= function vnf_validate(vnf, show_warns=true, check_isects=false) = assert(is_vnf(vnf), "Invalid VNF") let( - vnf = vnf_merge(vnf, cleanup=true), + vnf = vnf_merge_points(vnf), varr = vnf[0], faces = vnf[1], lvarr = len(varr), From ec87be11ecc48869fe3b20af679a3d4a79230e1e Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Thu, 11 Nov 2021 09:09:54 -0500 Subject: [PATCH 2/5] fix tests --- tests/test_vnf.scad | 10 +++---- vnf.scad | 65 +++++++++++++++++++++++---------------------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/tests/test_vnf.scad b/tests/test_vnf.scad index 14675fb..a344e6a 100644 --- a/tests/test_vnf.scad +++ b/tests/test_vnf.scad @@ -37,8 +37,8 @@ test_vnf_faces(); module test_vnf_from_polygons() { verts = [[-1,-1,-1],[1,-1,-1],[0,1,-1],[0,0,1]]; faces = [[0,1,2],[0,1,3,2],[2,3,0]]; - assert(vnf_merge(cleanup=true, - [vnf_from_polygons([for (face=faces) select(verts,face)])]) == [verts,faces]); + assert(vnf_merge_points( + vnf_from_polygons([for (face=faces) select(verts,face)])) == [verts,faces]); } test_vnf_from_polygons(); @@ -58,12 +58,12 @@ module test_vnf_area(){ test_vnf_area(); -module test_vnf_merge() { +module test_vnf_join() { vnf1 = vnf_from_polygons([[[-1,-1,-1],[1,-1,-1],[0,1,-1]]]); vnf2 = vnf_from_polygons([[[1,1,1],[-1,1,1],[0,1,-1]]]); - assert(vnf_merge([vnf1,vnf2]) == [[[-1,-1,-1],[1,-1,-1],[0,1,-1],[1,1,1],[-1,1,1],[0,1,-1]],[[0,1,2],[3,4,5]]]); + assert(vnf_join([vnf1,vnf2]) == [[[-1,-1,-1],[1,-1,-1],[0,1,-1],[1,1,1],[-1,1,1],[0,1,-1]],[[0,1,2],[3,4,5]]]); } -test_vnf_merge(); +test_vnf_join(); module test_vnf_triangulate() { diff --git a/vnf.scad b/vnf.scad index 874f2f3..e135a6c 100644 --- a/vnf.scad +++ b/vnf.scad @@ -317,38 +317,6 @@ function vnf_join(vnfs) = -// Function: vnf_merge_points() -// Usage: -// new_vnf = vnf_merge_points(vnf, [eps]); -// Description: -// Given a VNF, consolidates all duplicate vertices with a tolerance `eps`, relabeling the faces as necessary, -// and eliminating any face with fewer than 3 vertices. Unreferenced vertices of the input VNF are not dropped. -// To remove such vertices uses {{vnf_drop_unused_points()}}. -// Arguments: -// vnf = a VNF to consolidate -// eps = the tolerance in finding duplicates. Default: EPSILON -function vnf_merge_points(vnf,eps=EPSILON) = - let( - verts = vnf[0], - dedup = vector_search(verts,eps,verts), // collect vertex duplicates - map = [for(i=idx(verts)) min(dedup[i]) ], // remap duplic vertices - offset = cumsum([for(i=idx(verts)) map[i]==i ? 0 : 1 ]), // remaping face vertex offsets - map2 = list(idx(verts))-offset, // map old vertex indices to new indices - nverts = [for(i=idx(verts)) if(map[i]==i) verts[i] ], // this doesn't eliminate unreferenced vertices - nfaces = - [ for(face=vnf[1]) - let( - nface = [ for(vi=face) map2[map[vi]] ], - dface = [for (i=idx(nface)) - if( nface[i]!=nface[(i+1)%len(nface)]) - nface[i] ] - ) - if(len(dface) >= 3) dface - ] - ) - [nverts, nfaces]; - - // Function: vnf_from_polygons() // Usage: // vnf = vnf_from_polygons(polygons); @@ -606,6 +574,39 @@ function vnf_quantize(vnf,q=pow(2,-12)) = [[for (pt = vnf[0]) quant(pt,q)], vnf[1]]; + +// Function: vnf_merge_points() +// Usage: +// new_vnf = vnf_merge_points(vnf, [eps]); +// Description: +// Given a VNF, consolidates all duplicate vertices with a tolerance `eps`, relabeling the faces as necessary, +// and eliminating any face with fewer than 3 vertices. Unreferenced vertices of the input VNF are not dropped. +// To remove such vertices uses {{vnf_drop_unused_points()}}. +// Arguments: +// vnf = a VNF to consolidate +// eps = the tolerance in finding duplicates. Default: EPSILON +function vnf_merge_points(vnf,eps=EPSILON) = + let( + verts = vnf[0], + dedup = vector_search(verts,eps,verts), // collect vertex duplicates + map = [for(i=idx(verts)) min(dedup[i]) ], // remap duplic vertices + offset = cumsum([for(i=idx(verts)) map[i]==i ? 0 : 1 ]), // remaping face vertex offsets + map2 = list(idx(verts))-offset, // map old vertex indices to new indices + nverts = [for(i=idx(verts)) if(map[i]==i) verts[i] ], // this doesn't eliminate unreferenced vertices + nfaces = + [ for(face=vnf[1]) + let( + nface = [ for(vi=face) map2[map[vi]] ], + dface = [for (i=idx(nface)) + if( nface[i]!=nface[(i+1)%len(nface)]) + nface[i] ] + ) + if(len(dface) >= 3) dface + ] + ) + [nverts, nfaces]; + + // Function: vnf_drop_unused_points() // Usage: // clean_vnf=vnf_drop_unused_points(vnf); From 477dd557810ddecae238b7b4c4454866c1430147 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Thu, 11 Nov 2021 18:50:26 -0500 Subject: [PATCH 3/5] remove polygon_shift, hide noncollinear_triple modify glued circle to not produce duplicate points --- attachments.scad | 2 -- geometry.scad | 65 +++++++++++++--------------------------- hull.scad | 2 +- lists.scad | 2 +- rounding.scad | 2 +- shapes2d.scad | 25 +++++++++------- skin.scad | 14 ++++----- tests/test_geometry.scad | 22 ++++---------- tests/test_lists.scad | 3 ++ tests/test_shapes2d.scad | 9 +++--- vnf.scad | 4 +-- 11 files changed, 61 insertions(+), 89 deletions(-) diff --git a/attachments.scad b/attachments.scad index ee3b1ff..51ea9ec 100644 --- a/attachments.scad +++ b/attachments.scad @@ -1952,8 +1952,6 @@ module anchor_arrow2d(s=15, color=[0.333,0.333,1], $tags="anchor-arrow") { - - // Module: expose_anchors() // Usage: // expose_anchors(opacity) {child1() show_anchors(); child2() show_anchors(); ...} diff --git a/geometry.scad b/geometry.scad index 107cf03..97ac59d 100644 --- a/geometry.scad +++ b/geometry.scad @@ -153,7 +153,7 @@ function is_collinear(a, b, c, eps=EPSILON) = assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." ) let( points = is_def(c) ? [a,b,c]: a ) len(points)<3 ? true : - noncollinear_triple(points,error=false,eps=eps) == []; + _noncollinear_triple(points,error=false,eps=eps) == []; // Function: point_line_distance() @@ -429,7 +429,7 @@ function is_coplanar(points, eps=EPSILON) = assert( is_path(points,dim=3) , "Input should be a list of 3D points." ) assert( is_finite(eps) && eps>=0, "The tolerance should be a non-negative value." ) len(points)<=2 ? false - : let( ip = noncollinear_triple(points,error=false,eps=eps) ) + : let( ip = _noncollinear_triple(points,error=false,eps=eps) ) ip == [] ? false : let( plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]) ) _pointlist_greatest_distance(points,plane) < eps; @@ -850,7 +850,7 @@ function polygon_line_intersection(poly, line, bounded=false, nonzero=false, eps ) (len(inside)==0 ? undef : _merge_segments(inside, [inside[0]], eps)) : // 3d case - let(indices = noncollinear_triple(poly)) + let(indices = _noncollinear_triple(poly)) indices==[] ? undef : // Polygon is collinear let( plane = plane3pt(poly[indices[0]], poly[indices[1]], poly[indices[2]]), @@ -1384,25 +1384,22 @@ function circle_circle_tangents(c1,r1,c2,r2,d1,d2) = -// Section: Pointlists - - -// Function: noncollinear_triple() -// Usage: -// test = noncollinear_triple(points); -// Topics: Geometry, Noncollinearity -// Description: -// Finds the indices of three non-collinear points from the pointlist `points`. -// It selects two well separated points to define a line and chooses the third point -// to be the point farthest off the line. The points do not necessarily having the -// same winding direction as the polygon so they cannot be used to determine the -// winding direction or the direction of the normal. -// If all points are collinear returns [] when `error=true` or an error otherwise . -// Arguments: -// points = List of input points. -// error = Defines the behaviour for collinear input points. When `true`, produces an error, otherwise returns []. Default: `true`. -// eps = Tolerance for collinearity test. Default: EPSILON. -function noncollinear_triple(points,error=true,eps=EPSILON) = +/// Internal Function: _noncollinear_triple() +/// Usage: +/// test = _noncollinear_triple(points); +/// Topics: Geometry, Noncollinearity +/// Description: +/// Finds the indices of three non-collinear points from the pointlist `points`. +/// It selects two well separated points to define a line and chooses the third point +/// to be the point farthest off the line. The points do not necessarily having the +/// same winding direction as the polygon so they cannot be used to determine the +/// winding direction or the direction of the normal. +/// If all points are collinear returns [] when `error=true` or an error otherwise . +/// Arguments: +/// points = List of input points. +/// error = Defines the behaviour for collinear input points. When `true`, produces an error, otherwise returns []. Default: `true`. +/// eps = Tolerance for collinearity test. Default: EPSILON. +function _noncollinear_triple(points,error=true,eps=EPSILON) = assert( is_path(points), "Invalid input points." ) assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." ) len(points)<3 ? [] : @@ -1953,26 +1950,6 @@ function reverse_polygon(poly) = [ poly[0], for(i=[len(poly)-1:-1:1]) poly[i] ]; - -// Function: polygon_shift() -// Usage: -// newpoly = polygon_shift(poly, i); -// Topics: Geometry, Polygons -// Description: -// Given a polygon `poly`, rotates the point ordering so that the first point in the polygon path is the one at index `i`. -// This is identical to `list_rotate` except that it checks for doubled endpoints and removed them if present. -// Arguments: -// poly = The list of points in the polygon path. -// i = The index of the point to shift to the front of the path. -// Example: -// polygon_shift([[3,4], [8,2], [0,2], [-4,0]], 2); // Returns [[0,2], [-4,0], [3,4], [8,2]] -function polygon_shift(poly, i) = - let(poly=force_path(poly,"poly")) - assert(is_path(poly), "Invalid polygon." ) - list_rotate(cleanup_path(poly), i); - - - // Function: reindex_polygon() // Usage: // newpoly = reindex_polygon(reference, poly); @@ -2021,7 +1998,7 @@ function reindex_polygon(reference, poly, return_error=false) = [for(i=[0:N-1]) norm(reference[i]-fixpoly[(i+k)%N]) ] ]*I, min_ind = min_index(val), - optimal_poly = polygon_shift(fixpoly, min_ind) + optimal_poly = list_rotate(fixpoly, min_ind) ) return_error? [optimal_poly, val[min_ind]] : optimal_poly; @@ -2175,7 +2152,7 @@ function is_polygon_convex(poly,eps=EPSILON) = ? let( size = max([for(p=poly) norm(p-p0)]), tol=pow(size,2)*eps ) assert( size>eps, "The polygon is self-crossing or its points are collinear" ) min(crosses) >=-tol || max(crosses)<=tol - : let( ip = noncollinear_triple(poly,error=false,eps=eps) ) + : let( ip = _noncollinear_triple(poly,error=false,eps=eps) ) assert( ip!=[], "The points are collinear") let( crx = cross(poly[ip[1]]-poly[ip[0]],poly[ip[2]]-poly[ip[1]]), diff --git a/hull.scad b/hull.scad index a2efb7d..f880d44 100644 --- a/hull.scad +++ b/hull.scad @@ -166,7 +166,7 @@ function hull3d_faces(points) = assert(is_path(points,3),"Invalid input to hull3d_faces") len(points) < 3 ? count(len(points)) : let ( // start with a single non-collinear triangle - tri = noncollinear_triple(points, error=false) + tri = _noncollinear_triple(points, error=false) ) tri==[] ? _hull_collinear(points) : let( diff --git a/lists.scad b/lists.scad index ab557e6..cc44c06 100644 --- a/lists.scad +++ b/lists.scad @@ -497,7 +497,7 @@ function reverse(x) = // Topics: List Handling // See Also: select(), reverse() // Description: -// Rotates the contents of a list by `n` positions left. +// Rotates the contents of a list by `n` positions left, so that list[n] becomes the first entry of the list. // If `n` is negative, then the rotation is `abs(n)` positions to the right. // If `list` is a string, then a string is returned with the characters rotates within the string. // Arguments: diff --git a/rounding.scad b/rounding.scad index bb16be0..1e9eb86 100644 --- a/rounding.scad +++ b/rounding.scad @@ -1132,7 +1132,7 @@ function os_mask(mask, out=false, extra,check_valid, quality, offset) = ) assert(len(origin_index)==1,"Cannot find origin in the mask") let( - points = ([for(pt=polygon_shift(mask,origin_index[0])) [xfactor*max(pt.x,0),-max(pt.y,0)]]) + points = ([for(pt=list_rotate(mask,origin_index[0])) [xfactor*max(pt.x,0),-max(pt.y,0)]]) ) os_profile(deduplicate(move(-points[1],p=list_tail(points))), extra,check_valid,quality,offset); diff --git a/shapes2d.scad b/shapes2d.scad index d59c465..6369ac3 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -367,7 +367,7 @@ function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false each arc(N=steps, cp=p, r=rounding, start=a+180/n, angle=-360/n) ], maxx_idx = max_index(column(path2,0)), - path3 = polygon_shift(path2,maxx_idx) + path3 = list_rotate(path2,maxx_idx) ) path3 ), path = apply(mat, path4), @@ -1009,7 +1009,7 @@ function teardrop2d(r, ang=45, cap_h, d, anchor=CENTER, spin=0) = ], closed=true ), maxx_idx = max_index(column(path,0)), - path2 = polygon_shift(path,maxx_idx) + path2 = list_rotate(path,maxx_idx) ) reorient(anchor,spin, two_d=true, path=path2, p=path2); @@ -1051,20 +1051,23 @@ function glued_circles(r, spread=10, tangent=30, d, anchor=CENTER, spin=0) = ea1 = 270+tangent, lobearc = ea1-sa1, lobesegs = ceil(segs(r)*lobearc/360), - lobestep = lobearc / lobesegs, sa2 = 270-tangent, ea2 = 270+tangent, subarc = ea2-sa2, arcsegs = ceil(segs(r2)*abs(subarc)/360), - arcstep = subarc / arcsegs, - path = concat( - [for (i=[0:1:lobesegs]) let(a=sa1+i*lobestep) r * [cos(a),sin(a)] - cp1], - tangent==0? [] : [for (i=[0:1:arcsegs]) let(a=ea2-i*arcstep+180) r2 * [cos(a),sin(a)] - cp2], - [for (i=[0:1:lobesegs]) let(a=sa1+i*lobestep+180) r * [cos(a),sin(a)] + cp1], - tangent==0? [] : [for (i=[0:1:arcsegs]) let(a=ea2-i*arcstep) r2 * [cos(a),sin(a)] + cp2] - ), + // In the tangent zero case the inner curves are missing so we need to complete the two + // outer curves. In the other case the inner curves are present and endpoint=false + // prevents point duplication. + path = tangent==0 ? + concat(arc(N=lobesegs+1, r=r, cp=-cp1, angle=[sa1,ea1]), + arc(N=lobesegs+1, r=r, cp=cp1, angle=[sa1+180,ea1+180])) + : + concat(arc(N=lobesegs, r=r, cp=-cp1, angle=[sa1,ea1], endpoint=false), + [for(theta=lerpn(ea2+180,ea2-subarc+180,arcsegs,endpoint=false)) r2*[cos(theta),sin(theta)] - cp2], + arc(N=lobesegs, r=r, cp=cp1, angle=[sa1+180,ea1+180], endpoint=false), + [for(theta=lerpn(ea2,ea2-subarc,arcsegs,endpoint=false)) r2*[cos(theta),sin(theta)] + cp2]), maxx_idx = max_index(column(path,0)), - path2 = reverse_polygon(polygon_shift(path,maxx_idx)) + path2 = reverse_polygon(list_rotate(path,maxx_idx)) ) reorient(anchor,spin, two_d=true, path=path2, extent=true, p=path2); diff --git a/skin.scad b/skin.scad index 04daf88..61e74a2 100644 --- a/skin.scad +++ b/skin.scad @@ -344,7 +344,7 @@ // hex = path3d(hexagon(side=flare*sidelen, align_side=RIGHT, anchor="side0"),height); // pentmate = path3d(pentagon(side=flare*sidelen,align_side=LEFT,anchor="side0"),height); // // Native index would require mapping first and last vertices together, which is not allowed, so shift -// hexmate = polygon_shift( +// hexmate = list_rotate( // path3d(apply(move(pushvec)*rot(angle),hexagon(side=sidelen,align_side=LEFT,anchor="side0"))), // -1); // join_vertex = lerp( @@ -1540,7 +1540,7 @@ function _skin_distance_match(poly1,poly2) = ; i<=len(big) ; - shifted = polygon_shift(big,i), + shifted = list_rotate(big,i), result =_dp_distance_array(small, shifted, abort_thresh = bestcost), bestmap = result[0] Date: Thu, 11 Nov 2021 19:34:16 -0500 Subject: [PATCH 4/5] organize masks into massk3d.scad and masks2d.scad --- comparisons.scad | 2 +- masks2d.scad | 503 +++++++++++++++++++++++++++++++++++ masks.scad => masks3d.scad | 5 +- shapes2d.scad | 504 +----------------------------------- std.scad | 3 +- tests/test_comparisons.scad | 10 + tests/test_shapes2d.scad | 90 ------- 7 files changed, 523 insertions(+), 594 deletions(-) create mode 100644 masks2d.scad rename masks.scad => masks3d.scad (99%) diff --git a/comparisons.scad b/comparisons.scad index 3ea5d07..4bc846f 100644 --- a/comparisons.scad +++ b/comparisons.scad @@ -187,7 +187,7 @@ function all_nonnegative(x,eps=0) = // eps = Set to tolerance for approximate equality. Default: 0 function all_equal(vec,eps=0) = eps==0 ? [for(v=vec) if (v!=vec[0]) v] == [] - : [for(v=vec) if (!approx(v,vec[0])) v] == []; + : [for(v=vec) if (!approx(v,vec[0],eps)) v] == []; diff --git a/masks2d.scad b/masks2d.scad new file mode 100644 index 0000000..65121c5 --- /dev/null +++ b/masks2d.scad @@ -0,0 +1,503 @@ +////////////////////////////////////////////////////////////////////// +// LibFile: masks2d.scad +// This file provides 2D masking shapes that you can use with {{edge_profile()}} to mask edges. +// The shapes include the simple roundover and chamfer as well as more elaborate shapes +// like the cove and ogee found in furniture and architecture. You can make the masks +// as geometry or as 2D paths. +// Includes: +// include +////////////////////////////////////////////////////////////////////// + + +// Section: 2D Masking Shapes + +// Function&Module: mask2d_roundover() +// Usage: As Module +// mask2d_roundover(r|d, [inset], [excess]); +// Usage: With Attachments +// mask2d_roundover(r|d, [inset], [excess]) { attachments } +// Usage: As Module +// path = mask2d_roundover(r|d, [inset], [excess]); +// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) +// See Also: corner_profile(), edge_profile(), face_profile() +// Description: +// Creates a 2D roundover/bead mask shape that is useful for extruding into a 3D mask for a 90° edge. +// This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. +// If called as a function, this just returns a 2D path of the outline of the mask shape. +// Arguments: +// r = Radius of the roundover. +// inset = Optional bead inset size. Default: 0 +// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 +// --- +// d = Diameter of the roundover. +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` +// Example(2D): 2D Roundover Mask +// mask2d_roundover(r=10); +// Example(2D): 2D Bead Mask +// mask2d_roundover(r=10,inset=2); +// Example: Masking by Edge Attachment +// diff("mask") +// cube([50,60,70],center=true) +// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) +// mask2d_roundover(r=10, inset=2); +module mask2d_roundover(r, inset=0, excess=0.01, d, anchor=CENTER,spin=0) { + path = mask2d_roundover(r=r,d=d,excess=excess,inset=inset); + attachable(anchor,spin, two_d=true, path=path) { + polygon(path); + children(); + } +} + +function mask2d_roundover(r, inset=0, excess=0.01, d, anchor=CENTER,spin=0) = + assert(is_num(r)||is_num(d)) + assert(is_undef(excess)||is_num(excess)) + assert(is_num(inset)||(is_vector(inset)&&len(inset)==2)) + let( + inset = is_list(inset)? inset : [inset,inset], + excess = default(excess,$overlap), + r = get_radius(r=r,d=d,dflt=1), + steps = quantup(segs(r),4)/4, + step = 90/steps, + path = [ + [r+inset.x,-excess], + [-excess,-excess], + [-excess, r+inset.y], + for (i=[0:1:steps]) [r,r] + inset + polar_to_xy(r,180+i*step) + ] + ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path); + + +// Function&Module: mask2d_cove() +// Usage: As Module +// mask2d_cove(r|d, [inset], [excess]); +// Usage: With Attachments +// mask2d_cove(r|d, [inset], [excess]) { attachments } +// Usage: As Function +// path = mask2d_cove(r|d, [inset], [excess]); +// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) +// See Also: corner_profile(), edge_profile(), face_profile() +// Description: +// Creates a 2D cove mask shape that is useful for extruding into a 3D mask for a 90° edge. +// This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. +// If called as a function, this just returns a 2D path of the outline of the mask shape. +// Arguments: +// r = Radius of the cove. +// inset = Optional amount to inset code from corner. Default: 0 +// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 +// --- +// d = Diameter of the cove. +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` +// Example(2D): 2D Cove Mask +// mask2d_cove(r=10); +// Example(2D): 2D Inset Cove Mask +// mask2d_cove(r=10,inset=3); +// Example: Masking by Edge Attachment +// diff("mask") +// cube([50,60,70],center=true) +// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) +// mask2d_cove(r=10, inset=2); +module mask2d_cove(r, inset=0, excess=0.01, d, anchor=CENTER,spin=0) { + path = mask2d_cove(r=r,d=d,excess=excess,inset=inset); + attachable(anchor,spin, two_d=true, path=path) { + polygon(path); + children(); + } +} + +function mask2d_cove(r, inset=0, excess=0.01, d, anchor=CENTER,spin=0) = + assert(is_num(r)||is_num(d)) + assert(is_undef(excess)||is_num(excess)) + assert(is_num(inset)||(is_vector(inset)&&len(inset)==2)) + let( + inset = is_list(inset)? inset : [inset,inset], + excess = default(excess,$overlap), + r = get_radius(r=r,d=d,dflt=1), + steps = quantup(segs(r),4)/4, + step = 90/steps, + path = [ + [r+inset.x,-excess], + [-excess,-excess], + [-excess, r+inset.y], + for (i=[0:1:steps]) inset + polar_to_xy(r,90-i*step) + ] + ) reorient(anchor,spin, two_d=true, path=path, p=path); + + +// Function&Module: mask2d_chamfer() +// Usage: As Module +// mask2d_chamfer(edge, [angle], [inset], [excess]); +// mask2d_chamfer(y, [angle], [inset], [excess]); +// mask2d_chamfer(x, [angle], [inset], [excess]); +// Usage: With Attachments +// mask2d_chamfer(edge, [angle], [inset], [excess]) { attachments } +// Usage: As Function +// path = mask2d_chamfer(edge, [angle], [inset], [excess]); +// path = mask2d_chamfer(y, [angle], [inset], [excess]); +// path = mask2d_chamfer(x, [angle], [inset], [excess]); +// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) +// See Also: corner_profile(), edge_profile(), face_profile() +// Description: +// Creates a 2D chamfer mask shape that is useful for extruding into a 3D mask for a 90° edge. +// This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. +// If called as a function, this just returns a 2D path of the outline of the mask shape. +// Arguments: +// edge = The length of the edge of the chamfer. +// angle = The angle of the chamfer edge, away from vertical. Default: 45. +// inset = Optional amount to inset code from corner. Default: 0 +// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 +// --- +// x = The width of the chamfer. +// y = The height of the chamfer. +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` +// Example(2D): 2D Chamfer Mask +// mask2d_chamfer(x=10); +// Example(2D): 2D Chamfer Mask by Width. +// mask2d_chamfer(x=10, angle=30); +// Example(2D): 2D Chamfer Mask by Height. +// mask2d_chamfer(y=10, angle=30); +// Example(2D): 2D Inset Chamfer Mask +// mask2d_chamfer(x=10, inset=2); +// Example: Masking by Edge Attachment +// diff("mask") +// cube([50,60,70],center=true) +// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) +// mask2d_chamfer(x=10, inset=2); +module mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTER,spin=0) { + path = mask2d_chamfer(x=x, y=y, edge=edge, angle=angle, excess=excess, inset=inset); + attachable(anchor,spin, two_d=true, path=path, extent=true) { + polygon(path); + children(); + } +} + +function mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTER,spin=0) = + assert(num_defined([x,y,edge])==1) + assert(is_num(first_defined([x,y,edge]))) + assert(is_num(angle)) + assert(is_undef(excess)||is_num(excess)) + assert(is_num(inset)||(is_vector(inset)&&len(inset)==2)) + let( + inset = is_list(inset)? inset : [inset,inset], + excess = default(excess,$overlap), + x = !is_undef(x)? x : + !is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) : + hyp_ang_to_opp(hyp=edge,ang=angle), + y = opp_ang_to_adj(opp=x,ang=angle), + path = [ + [x+inset.x, -excess], + [-excess, -excess], + [-excess, y+inset.y], + [inset.x, y+inset.y], + [x+inset.x, inset.y] + ] + ) reorient(anchor,spin, two_d=true, path=path, extent=true, p=path); + + +// Function&Module: mask2d_rabbet() +// Usage: As Module +// mask2d_rabbet(size, [excess]); +// Usage: With Attachments +// mask2d_rabbet(size, [excess]) { attachments } +// Usage: As Function +// path = mask2d_rabbet(size, [excess]); +// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) +// See Also: corner_profile(), edge_profile(), face_profile() +// Description: +// Creates a 2D rabbet mask shape that is useful for extruding into a 3D mask for a 90° edge. +// This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. +// If called as a function, this just returns a 2D path of the outline of the mask shape. +// Arguments: +// size = The size of the rabbet, either as a scalar or an [X,Y] list. +// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 +// --- +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` +// Example(2D): 2D Rabbet Mask +// mask2d_rabbet(size=10); +// Example(2D): 2D Asymmetrical Rabbet Mask +// mask2d_rabbet(size=[5,10]); +// Example: Masking by Edge Attachment +// diff("mask") +// cube([50,60,70],center=true) +// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) +// mask2d_rabbet(size=10); +module mask2d_rabbet(size, excess=0.01, anchor=CENTER,spin=0) { + path = mask2d_rabbet(size=size, excess=excess); + attachable(anchor,spin, two_d=true, path=path, extent=false) { + polygon(path); + children(); + } +} + +function mask2d_rabbet(size, excess=0.01, anchor=CENTER,spin=0) = + assert(is_num(size)||(is_vector(size)&&len(size)==2)) + assert(is_undef(excess)||is_num(excess)) + let( + excess = default(excess,$overlap), + size = is_list(size)? size : [size,size], + path = [ + [size.x, -excess], + [-excess, -excess], + [-excess, size.y], + size + ] + ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path); + + +// Function&Module: mask2d_dovetail() +// Usage: As Module +// mask2d_dovetail(edge, [angle], [inset], [shelf], [excess], ...); +// mask2d_dovetail(x=, [angle=], [inset=], [shelf=], [excess=], ...); +// mask2d_dovetail(y=, [angle=], [inset=], [shelf=], [excess=], ...); +// Usage: With Attachments +// mask2d_dovetail(edge, [angle], [inset], [shelf], ...) { attachments } +// Usage: As Function +// path = mask2d_dovetail(edge, [angle], [inset], [shelf], [excess]); +// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) +// See Also: corner_profile(), edge_profile(), face_profile() +// Description: +// Creates a 2D dovetail mask shape that is useful for extruding into a 3D mask for a 90° edge. +// This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. +// If called as a function, this just returns a 2D path of the outline of the mask shape. +// Arguments: +// edge = The length of the edge of the dovetail. +// angle = The angle of the chamfer edge, away from vertical. Default: 30. +// inset = Optional amount to inset code from corner. Default: 0 +// shelf = The extra height to add to the inside corner of the dovetail. Default: 0 +// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 +// --- +// x = The width of the dovetail. +// y = The height of the dovetail. +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` +// Example(2D): 2D Dovetail Mask +// mask2d_dovetail(x=10); +// Example(2D): 2D Dovetail Mask by Width. +// mask2d_dovetail(x=10, angle=30); +// Example(2D): 2D Dovetail Mask by Height. +// mask2d_dovetail(y=10, angle=30); +// Example(2D): 2D Inset Dovetail Mask +// mask2d_dovetail(x=10, inset=2); +// Example: Masking by Edge Attachment +// diff("mask") +// cube([50,60,70],center=true) +// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) +// mask2d_dovetail(x=10, inset=2); +module mask2d_dovetail(edge, angle=30, inset=0, shelf=0, excess=0.01, x, y, anchor=CENTER, spin=0) { + path = mask2d_dovetail(x=x, y=y, edge=edge, angle=angle, inset=inset, shelf=shelf, excess=excess); + attachable(anchor,spin, two_d=true, path=path) { + polygon(path); + children(); + } +} + +function mask2d_dovetail(edge, angle=30, inset=0, shelf=0, excess=0.01, x, y, anchor=CENTER, spin=0) = + assert(num_defined([x,y,edge])==1) + assert(is_num(first_defined([x,y,edge]))) + assert(is_num(angle)) + assert(is_undef(excess)||is_num(excess)) + assert(is_num(inset)||(is_vector(inset)&&len(inset)==2)) + let( + inset = is_list(inset)? inset : [inset,inset], + excess = default(excess,$overlap), + x = !is_undef(x)? x : + !is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) : + hyp_ang_to_opp(hyp=edge,ang=angle), + y = opp_ang_to_adj(opp=x,ang=angle), + path = [ + [inset.x,0], + [-excess, 0], + [-excess, y+inset.y+shelf], + inset+[x,y+shelf], + inset+[x,y], + inset + ] + ) reorient(anchor,spin, two_d=true, path=path, p=path); + + +// Function&Module: mask2d_teardrop() +// Usage: As Module +// mask2d_teardrop(r|d, [angle], [excess]); +// Usage: With Attachments +// mask2d_teardrop(r|d, [angle], [excess]) { attachments } +// Usage: As Function +// path = mask2d_teardrop(r|d, [angle], [excess]); +// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) +// See Also: corner_profile(), edge_profile(), face_profile() +// Description: +// Creates a 2D teardrop mask shape that is useful for extruding into a 3D mask for a 90° edge. +// This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. +// If called as a function, this just returns a 2D path of the outline of the mask shape. +// This is particularly useful to make partially rounded bottoms, that don't need support to print. +// Arguments: +// r = Radius of the rounding. +// angle = The maximum angle from vertical. +// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 +// --- +// d = Diameter of the rounding. +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` +// Example(2D): 2D Teardrop Mask +// mask2d_teardrop(r=10); +// Example(2D): Using a Custom Angle +// mask2d_teardrop(r=10,angle=30); +// Example: Masking by Edge Attachment +// diff("mask") +// cube([50,60,70],center=true) +// edge_profile(BOT) +// mask2d_teardrop(r=10, angle=40); +function mask2d_teardrop(r, angle=45, excess=0.01, d, anchor=CENTER, spin=0) = + assert(is_num(angle)) + assert(angle>0 && angle<90) + assert(is_num(excess)) + let( + r = get_radius(r=r, d=d, dflt=1), + n = ceil(segs(r) * angle/360), + cp = [r,r], + tp = cp + polar_to_xy(r,180+angle), + bp = [tp.x+adj_ang_to_opp(tp.y,angle), 0], + step = angle/n, + path = [ + bp, bp-[0,excess], [-excess,-excess], [-excess,r], + for (i=[0:1:n]) cp+polar_to_xy(r,180+i*step) + ] + ) reorient(anchor,spin, two_d=true, path=path, p=path); + +module mask2d_teardrop(r, angle=45, excess=0.01, d, anchor=CENTER, spin=0) { + path = mask2d_teardrop(r=r, d=d, angle=angle, excess=excess); + attachable(anchor,spin, two_d=true, path=path) { + polygon(path); + children(); + } +} + +// Function&Module: mask2d_ogee() +// Usage: As Module +// mask2d_ogee(pattern, [excess], ...); +// Usage: With Attachments +// mask2d_ogee(pattern, [excess], ...) { attachments } +// Usage: As Function +// path = mask2d_ogee(pattern, [excess], ...); +// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) +// See Also: corner_profile(), edge_profile(), face_profile() +// +// Description: +// Creates a 2D Ogee mask shape that is useful for extruding into a 3D mask for a 90° edge. +// This 2D mask is designed to be `difference()`d away from the edge of a shape that is in the first (X+Y+) quadrant. +// Since there are a number of shapes that fall under the name ogee, the shape of this mask is given as a pattern. +// Patterns are given as TYPE, VALUE pairs. ie: `["fillet",10, "xstep",2, "step",[5,5], ...]`. See Patterns below. +// If called as a function, this just returns a 2D path of the outline of the mask shape. +// . +// ### Patterns +// . +// Type | Argument | Description +// -------- | --------- | ---------------- +// "step" | [x,y] | Makes a line to a point `x` right and `y` down. +// "xstep" | dist | Makes a `dist` length line towards X+. +// "ystep" | dist | Makes a `dist` length line towards Y-. +// "round" | radius | Makes an arc that will mask a roundover. +// "fillet" | radius | Makes an arc that will mask a fillet. +// +// Arguments: +// pattern = A list of pattern pieces to describe the Ogee. +// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 +// --- +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` +// +// Example(2D): 2D Ogee Mask +// mask2d_ogee([ +// "xstep",1, "ystep",1, // Starting shoulder. +// "fillet",5, "round",5, // S-curve. +// "ystep",1, "xstep",1 // Ending shoulder. +// ]); +// Example: Masking by Edge Attachment +// diff("mask") +// cube([50,60,70],center=true) +// edge_profile(TOP) +// mask2d_ogee([ +// "xstep",1, "ystep",1, // Starting shoulder. +// "fillet",5, "round",5, // S-curve. +// "ystep",1, "xstep",1 // Ending shoulder. +// ]); +module mask2d_ogee(pattern, excess=0.01, anchor=CENTER,spin=0) { + path = mask2d_ogee(pattern, excess=excess); + attachable(anchor,spin, two_d=true, path=path) { + polygon(path); + children(); + } +} + +function mask2d_ogee(pattern, excess=0.01, anchor=CENTER, spin=0) = + assert(is_list(pattern)) + assert(len(pattern)>0) + assert(len(pattern)%2==0,"pattern must be a list of TYPE, VAL pairs.") + assert(all([for (i = idx(pattern,step=2)) in_list(pattern[i],["step","xstep","ystep","round","fillet"])])) + let( + excess = default(excess,$overlap), + x = concat([0], cumsum([ + for (i=idx(pattern,step=2)) let( + type = pattern[i], + val = pattern[i+1] + ) ( + type=="step"? val.x : + type=="xstep"? val : + type=="round"? val : + type=="fillet"? val : + 0 + ) + ])), + y = concat([0], cumsum([ + for (i=idx(pattern,step=2)) let( + type = pattern[i], + val = pattern[i+1] + ) ( + type=="step"? val.y : + type=="ystep"? val : + type=="round"? val : + type=="fillet"? val : + 0 + ) + ])), + tot_x = last(x), + tot_y = last(y), + data = [ + for (i=idx(pattern,step=2)) let( + type = pattern[i], + val = pattern[i+1], + pt = [x[i/2], tot_y-y[i/2]] + ( + type=="step"? [val.x,-val.y] : + type=="xstep"? [val,0] : + type=="ystep"? [0,-val] : + type=="round"? [val,0] : + type=="fillet"? [0,-val] : + [0,0] + ) + ) [type, val, pt] + ], + path = [ + [tot_x,-excess], + [-excess,-excess], + [-excess,tot_y], + for (pat = data) each + pat[0]=="step"? [pat[2]] : + pat[0]=="xstep"? [pat[2]] : + pat[0]=="ystep"? [pat[2]] : + let( + r = pat[1], + steps = segs(abs(r)), + step = 90/steps + ) [ + for (i=[0:1:steps]) let( + a = pat[0]=="round"? (180+i*step) : (90-i*step) + ) pat[2] + abs(r)*[cos(a),sin(a)] + ] + ], + path2 = deduplicate(path) + ) reorient(anchor,spin, two_d=true, path=path2, p=path2); + + + diff --git a/masks.scad b/masks3d.scad similarity index 99% rename from masks.scad rename to masks3d.scad index f5d783f..df89eb3 100644 --- a/masks.scad +++ b/masks3d.scad @@ -1,6 +1,7 @@ ////////////////////////////////////////////////////////////////////// -// LibFile: masks.scad -// Masking shapes. +// 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 ////////////////////////////////////////////////////////////////////// diff --git a/shapes2d.scad b/shapes2d.scad index 6369ac3..8a75c9b 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -1,15 +1,13 @@ ////////////////////////////////////////////////////////////////////// // LibFile: shapes2d.scad // This file includes redefinitions of the core modules to -// work with attachment. You can also create regular polygons +// work with attachment, and functional forms of those modules +// that produce paths. You can create regular polygons // with optional rounded corners and alignment features not // available with circle(). The file also provides teardrop2d, -// which is useful for 3d printable holes. Lastly you can use the -// masks to produce edge treatments common in furniture from the -// simple roundover or cove molding to the more elaborate ogee. +// which is useful for 3D printable holes. // Many of the commands have module forms that produce geometry and -// function forms that produce a path. This file defines function -// forms of the core OpenSCAD modules that produce paths. +// function forms that produce a path. // Includes: // include ////////////////////////////////////////////////////////////////////// @@ -1224,498 +1222,4 @@ function reuleaux_polygon(N=3, r, d, anchor=CENTER, spin=0) = ) reorient(anchor,spin, two_d=true, path=path, anchors=anchors, p=path); -// Section: 2D Masking Shapes - -// Function&Module: mask2d_roundover() -// Usage: As Module -// mask2d_roundover(r|d, [inset], [excess]); -// Usage: With Attachments -// mask2d_roundover(r|d, [inset], [excess]) { attachments } -// Usage: As Module -// path = mask2d_roundover(r|d, [inset], [excess]); -// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) -// See Also: corner_profile(), edge_profile(), face_profile() -// Description: -// Creates a 2D roundover/bead mask shape that is useful for extruding into a 3D mask for a 90º edge. -// This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. -// If called as a function, this just returns a 2D path of the outline of the mask shape. -// Arguments: -// r = Radius of the roundover. -// inset = Optional bead inset size. Default: 0 -// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 -// --- -// d = Diameter of the roundover. -// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` -// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` -// Example(2D): 2D Roundover Mask -// mask2d_roundover(r=10); -// Example(2D): 2D Bead Mask -// mask2d_roundover(r=10,inset=2); -// Example: Masking by Edge Attachment -// diff("mask") -// cube([50,60,70],center=true) -// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) -// mask2d_roundover(r=10, inset=2); -module mask2d_roundover(r, inset=0, excess=0.01, d, anchor=CENTER,spin=0) { - path = mask2d_roundover(r=r,d=d,excess=excess,inset=inset); - attachable(anchor,spin, two_d=true, path=path) { - polygon(path); - children(); - } -} - -function mask2d_roundover(r, inset=0, excess=0.01, d, anchor=CENTER,spin=0) = - assert(is_num(r)||is_num(d)) - assert(is_undef(excess)||is_num(excess)) - assert(is_num(inset)||(is_vector(inset)&&len(inset)==2)) - let( - inset = is_list(inset)? inset : [inset,inset], - excess = default(excess,$overlap), - r = get_radius(r=r,d=d,dflt=1), - steps = quantup(segs(r),4)/4, - step = 90/steps, - path = [ - [r+inset.x,-excess], - [-excess,-excess], - [-excess, r+inset.y], - for (i=[0:1:steps]) [r,r] + inset + polar_to_xy(r,180+i*step) - ] - ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path); - - -// Function&Module: mask2d_cove() -// Usage: As Module -// mask2d_cove(r|d, [inset], [excess]); -// Usage: With Attachments -// mask2d_cove(r|d, [inset], [excess]) { attachments } -// Usage: As Function -// path = mask2d_cove(r|d, [inset], [excess]); -// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) -// See Also: corner_profile(), edge_profile(), face_profile() -// Description: -// Creates a 2D cove mask shape that is useful for extruding into a 3D mask for a 90º edge. -// This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. -// If called as a function, this just returns a 2D path of the outline of the mask shape. -// Arguments: -// r = Radius of the cove. -// inset = Optional amount to inset code from corner. Default: 0 -// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 -// --- -// d = Diameter of the cove. -// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` -// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` -// Example(2D): 2D Cove Mask -// mask2d_cove(r=10); -// Example(2D): 2D Inset Cove Mask -// mask2d_cove(r=10,inset=3); -// Example: Masking by Edge Attachment -// diff("mask") -// cube([50,60,70],center=true) -// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) -// mask2d_cove(r=10, inset=2); -module mask2d_cove(r, inset=0, excess=0.01, d, anchor=CENTER,spin=0) { - path = mask2d_cove(r=r,d=d,excess=excess,inset=inset); - attachable(anchor,spin, two_d=true, path=path) { - polygon(path); - children(); - } -} - -function mask2d_cove(r, inset=0, excess=0.01, d, anchor=CENTER,spin=0) = - assert(is_num(r)||is_num(d)) - assert(is_undef(excess)||is_num(excess)) - assert(is_num(inset)||(is_vector(inset)&&len(inset)==2)) - let( - inset = is_list(inset)? inset : [inset,inset], - excess = default(excess,$overlap), - r = get_radius(r=r,d=d,dflt=1), - steps = quantup(segs(r),4)/4, - step = 90/steps, - path = [ - [r+inset.x,-excess], - [-excess,-excess], - [-excess, r+inset.y], - for (i=[0:1:steps]) inset + polar_to_xy(r,90-i*step) - ] - ) reorient(anchor,spin, two_d=true, path=path, p=path); - - -// Function&Module: mask2d_chamfer() -// Usage: As Module -// mask2d_chamfer(edge, [angle], [inset], [excess]); -// mask2d_chamfer(y, [angle], [inset], [excess]); -// mask2d_chamfer(x, [angle], [inset], [excess]); -// Usage: With Attachments -// mask2d_chamfer(edge, [angle], [inset], [excess]) { attachments } -// Usage: As Function -// path = mask2d_chamfer(edge, [angle], [inset], [excess]); -// path = mask2d_chamfer(y, [angle], [inset], [excess]); -// path = mask2d_chamfer(x, [angle], [inset], [excess]); -// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) -// See Also: corner_profile(), edge_profile(), face_profile() -// Description: -// Creates a 2D chamfer mask shape that is useful for extruding into a 3D mask for a 90º edge. -// This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. -// If called as a function, this just returns a 2D path of the outline of the mask shape. -// Arguments: -// edge = The length of the edge of the chamfer. -// angle = The angle of the chamfer edge, away from vertical. Default: 45. -// inset = Optional amount to inset code from corner. Default: 0 -// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 -// --- -// x = The width of the chamfer. -// y = The height of the chamfer. -// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` -// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` -// Example(2D): 2D Chamfer Mask -// mask2d_chamfer(x=10); -// Example(2D): 2D Chamfer Mask by Width. -// mask2d_chamfer(x=10, angle=30); -// Example(2D): 2D Chamfer Mask by Height. -// mask2d_chamfer(y=10, angle=30); -// Example(2D): 2D Inset Chamfer Mask -// mask2d_chamfer(x=10, inset=2); -// Example: Masking by Edge Attachment -// diff("mask") -// cube([50,60,70],center=true) -// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) -// mask2d_chamfer(x=10, inset=2); -module mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTER,spin=0) { - path = mask2d_chamfer(x=x, y=y, edge=edge, angle=angle, excess=excess, inset=inset); - attachable(anchor,spin, two_d=true, path=path, extent=true) { - polygon(path); - children(); - } -} - -function mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTER,spin=0) = - assert(num_defined([x,y,edge])==1) - assert(is_num(first_defined([x,y,edge]))) - assert(is_num(angle)) - assert(is_undef(excess)||is_num(excess)) - assert(is_num(inset)||(is_vector(inset)&&len(inset)==2)) - let( - inset = is_list(inset)? inset : [inset,inset], - excess = default(excess,$overlap), - x = !is_undef(x)? x : - !is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) : - hyp_ang_to_opp(hyp=edge,ang=angle), - y = opp_ang_to_adj(opp=x,ang=angle), - path = [ - [x+inset.x, -excess], - [-excess, -excess], - [-excess, y+inset.y], - [inset.x, y+inset.y], - [x+inset.x, inset.y] - ] - ) reorient(anchor,spin, two_d=true, path=path, extent=true, p=path); - - -// Function&Module: mask2d_rabbet() -// Usage: As Module -// mask2d_rabbet(size, [excess]); -// Usage: With Attachments -// mask2d_rabbet(size, [excess]) { attachments } -// Usage: As Function -// path = mask2d_rabbet(size, [excess]); -// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) -// See Also: corner_profile(), edge_profile(), face_profile() -// Description: -// Creates a 2D rabbet mask shape that is useful for extruding into a 3D mask for a 90º edge. -// This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. -// If called as a function, this just returns a 2D path of the outline of the mask shape. -// Arguments: -// size = The size of the rabbet, either as a scalar or an [X,Y] list. -// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 -// --- -// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` -// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` -// Example(2D): 2D Rabbet Mask -// mask2d_rabbet(size=10); -// Example(2D): 2D Asymmetrical Rabbet Mask -// mask2d_rabbet(size=[5,10]); -// Example: Masking by Edge Attachment -// diff("mask") -// cube([50,60,70],center=true) -// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) -// mask2d_rabbet(size=10); -module mask2d_rabbet(size, excess=0.01, anchor=CENTER,spin=0) { - path = mask2d_rabbet(size=size, excess=excess); - attachable(anchor,spin, two_d=true, path=path, extent=false) { - polygon(path); - children(); - } -} - -function mask2d_rabbet(size, excess=0.01, anchor=CENTER,spin=0) = - assert(is_num(size)||(is_vector(size)&&len(size)==2)) - assert(is_undef(excess)||is_num(excess)) - let( - excess = default(excess,$overlap), - size = is_list(size)? size : [size,size], - path = [ - [size.x, -excess], - [-excess, -excess], - [-excess, size.y], - size - ] - ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path); - - -// Function&Module: mask2d_dovetail() -// Usage: As Module -// mask2d_dovetail(edge, [angle], [inset], [shelf], [excess], ...); -// mask2d_dovetail(x=, [angle=], [inset=], [shelf=], [excess=], ...); -// mask2d_dovetail(y=, [angle=], [inset=], [shelf=], [excess=], ...); -// Usage: With Attachments -// mask2d_dovetail(edge, [angle], [inset], [shelf], ...) { attachments } -// Usage: As Function -// path = mask2d_dovetail(edge, [angle], [inset], [shelf], [excess]); -// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) -// See Also: corner_profile(), edge_profile(), face_profile() -// Description: -// Creates a 2D dovetail mask shape that is useful for extruding into a 3D mask for a 90º edge. -// This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. -// If called as a function, this just returns a 2D path of the outline of the mask shape. -// Arguments: -// edge = The length of the edge of the dovetail. -// angle = The angle of the chamfer edge, away from vertical. Default: 30. -// inset = Optional amount to inset code from corner. Default: 0 -// shelf = The extra height to add to the inside corner of the dovetail. Default: 0 -// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 -// --- -// x = The width of the dovetail. -// y = The height of the dovetail. -// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` -// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` -// Example(2D): 2D Dovetail Mask -// mask2d_dovetail(x=10); -// Example(2D): 2D Dovetail Mask by Width. -// mask2d_dovetail(x=10, angle=30); -// Example(2D): 2D Dovetail Mask by Height. -// mask2d_dovetail(y=10, angle=30); -// Example(2D): 2D Inset Dovetail Mask -// mask2d_dovetail(x=10, inset=2); -// Example: Masking by Edge Attachment -// diff("mask") -// cube([50,60,70],center=true) -// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) -// mask2d_dovetail(x=10, inset=2); -module mask2d_dovetail(edge, angle=30, inset=0, shelf=0, excess=0.01, x, y, anchor=CENTER, spin=0) { - path = mask2d_dovetail(x=x, y=y, edge=edge, angle=angle, inset=inset, shelf=shelf, excess=excess); - attachable(anchor,spin, two_d=true, path=path) { - polygon(path); - children(); - } -} - -function mask2d_dovetail(edge, angle=30, inset=0, shelf=0, excess=0.01, x, y, anchor=CENTER, spin=0) = - assert(num_defined([x,y,edge])==1) - assert(is_num(first_defined([x,y,edge]))) - assert(is_num(angle)) - assert(is_undef(excess)||is_num(excess)) - assert(is_num(inset)||(is_vector(inset)&&len(inset)==2)) - let( - inset = is_list(inset)? inset : [inset,inset], - excess = default(excess,$overlap), - x = !is_undef(x)? x : - !is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) : - hyp_ang_to_opp(hyp=edge,ang=angle), - y = opp_ang_to_adj(opp=x,ang=angle), - path = [ - [inset.x,0], - [-excess, 0], - [-excess, y+inset.y+shelf], - inset+[x,y+shelf], - inset+[x,y], - inset - ] - ) reorient(anchor,spin, two_d=true, path=path, p=path); - - -// Function&Module: mask2d_teardrop() -// Usage: As Module -// mask2d_teardrop(r|d, [angle], [excess]); -// Usage: With Attachments -// mask2d_teardrop(r|d, [angle], [excess]) { attachments } -// Usage: As Function -// path = mask2d_teardrop(r|d, [angle], [excess]); -// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) -// See Also: corner_profile(), edge_profile(), face_profile() -// Description: -// Creates a 2D teardrop mask shape that is useful for extruding into a 3D mask for a 90º edge. -// This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. -// If called as a function, this just returns a 2D path of the outline of the mask shape. -// This is particularly useful to make partially rounded bottoms, that don't need support to print. -// Arguments: -// r = Radius of the rounding. -// angle = The maximum angle from vertical. -// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 -// --- -// d = Diameter of the rounding. -// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` -// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` -// Example(2D): 2D Teardrop Mask -// mask2d_teardrop(r=10); -// Example(2D): Using a Custom Angle -// mask2d_teardrop(r=10,angle=30); -// Example: Masking by Edge Attachment -// diff("mask") -// cube([50,60,70],center=true) -// edge_profile(BOT) -// mask2d_teardrop(r=10, angle=40); -function mask2d_teardrop(r, angle=45, excess=0.01, d, anchor=CENTER, spin=0) = - assert(is_num(angle)) - assert(angle>0 && angle<90) - assert(is_num(excess)) - let( - r = get_radius(r=r, d=d, dflt=1), - n = ceil(segs(r) * angle/360), - cp = [r,r], - tp = cp + polar_to_xy(r,180+angle), - bp = [tp.x+adj_ang_to_opp(tp.y,angle), 0], - step = angle/n, - path = [ - bp, bp-[0,excess], [-excess,-excess], [-excess,r], - for (i=[0:1:n]) cp+polar_to_xy(r,180+i*step) - ] - ) reorient(anchor,spin, two_d=true, path=path, p=path); - -module mask2d_teardrop(r, angle=45, excess=0.01, d, anchor=CENTER, spin=0) { - path = mask2d_teardrop(r=r, d=d, angle=angle, excess=excess); - attachable(anchor,spin, two_d=true, path=path) { - polygon(path); - children(); - } -} - -// Function&Module: mask2d_ogee() -// Usage: As Module -// mask2d_ogee(pattern, [excess], ...); -// Usage: With Attachments -// mask2d_ogee(pattern, [excess], ...) { attachments } -// Usage: As Function -// path = mask2d_ogee(pattern, [excess], ...); -// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) -// See Also: corner_profile(), edge_profile(), face_profile() -// -// Description: -// Creates a 2D Ogee mask shape that is useful for extruding into a 3D mask for a 90º edge. -// This 2D mask is designed to be `difference()`d away from the edge of a shape that is in the first (X+Y+) quadrant. -// Since there are a number of shapes that fall under the name ogee, the shape of this mask is given as a pattern. -// Patterns are given as TYPE, VALUE pairs. ie: `["fillet",10, "xstep",2, "step",[5,5], ...]`. See Patterns below. -// If called as a function, this just returns a 2D path of the outline of the mask shape. -// . -// ### Patterns -// . -// Type | Argument | Description -// -------- | --------- | ---------------- -// "step" | [x,y] | Makes a line to a point `x` right and `y` down. -// "xstep" | dist | Makes a `dist` length line towards X+. -// "ystep" | dist | Makes a `dist` length line towards Y-. -// "round" | radius | Makes an arc that will mask a roundover. -// "fillet" | radius | Makes an arc that will mask a fillet. -// -// Arguments: -// pattern = A list of pattern pieces to describe the Ogee. -// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 -// --- -// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` -// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` -// -// Example(2D): 2D Ogee Mask -// mask2d_ogee([ -// "xstep",1, "ystep",1, // Starting shoulder. -// "fillet",5, "round",5, // S-curve. -// "ystep",1, "xstep",1 // Ending shoulder. -// ]); -// Example: Masking by Edge Attachment -// diff("mask") -// cube([50,60,70],center=true) -// edge_profile(TOP) -// mask2d_ogee([ -// "xstep",1, "ystep",1, // Starting shoulder. -// "fillet",5, "round",5, // S-curve. -// "ystep",1, "xstep",1 // Ending shoulder. -// ]); -module mask2d_ogee(pattern, excess=0.01, anchor=CENTER,spin=0) { - path = mask2d_ogee(pattern, excess=excess); - attachable(anchor,spin, two_d=true, path=path) { - polygon(path); - children(); - } -} - -function mask2d_ogee(pattern, excess=0.01, anchor=CENTER, spin=0) = - assert(is_list(pattern)) - assert(len(pattern)>0) - assert(len(pattern)%2==0,"pattern must be a list of TYPE, VAL pairs.") - assert(all([for (i = idx(pattern,step=2)) in_list(pattern[i],["step","xstep","ystep","round","fillet"])])) - let( - excess = default(excess,$overlap), - x = concat([0], cumsum([ - for (i=idx(pattern,step=2)) let( - type = pattern[i], - val = pattern[i+1] - ) ( - type=="step"? val.x : - type=="xstep"? val : - type=="round"? val : - type=="fillet"? val : - 0 - ) - ])), - y = concat([0], cumsum([ - for (i=idx(pattern,step=2)) let( - type = pattern[i], - val = pattern[i+1] - ) ( - type=="step"? val.y : - type=="ystep"? val : - type=="round"? val : - type=="fillet"? val : - 0 - ) - ])), - tot_x = last(x), - tot_y = last(y), - data = [ - for (i=idx(pattern,step=2)) let( - type = pattern[i], - val = pattern[i+1], - pt = [x[i/2], tot_y-y[i/2]] + ( - type=="step"? [val.x,-val.y] : - type=="xstep"? [val,0] : - type=="ystep"? [0,-val] : - type=="round"? [val,0] : - type=="fillet"? [0,-val] : - [0,0] - ) - ) [type, val, pt] - ], - path = [ - [tot_x,-excess], - [-excess,-excess], - [-excess,tot_y], - for (pat = data) each - pat[0]=="step"? [pat[2]] : - pat[0]=="xstep"? [pat[2]] : - pat[0]=="ystep"? [pat[2]] : - let( - r = pat[1], - steps = segs(abs(r)), - step = 90/steps - ) [ - for (i=[0:1:steps]) let( - a = pat[0]=="round"? (180+i*step) : (90-i*step) - ) pat[2] + abs(r)*[cos(a),sin(a)] - ] - ], - path2 = deduplicate(path) - ) reorient(anchor,spin, two_d=true, path=path2, p=path2); - - - - - // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/std.scad b/std.scad index 490ed21..10d4848 100644 --- a/std.scad +++ b/std.scad @@ -17,7 +17,8 @@ include include include include -include +include +include include include include diff --git a/tests/test_comparisons.scad b/tests/test_comparisons.scad index b732521..0168037 100644 --- a/tests/test_comparisons.scad +++ b/tests/test_comparisons.scad @@ -164,6 +164,16 @@ module test_all_zero() { test_all_zero(); +module test_all_equal() { + assert(all_equal([1,1,1,1])); + assert(all_equal([[3,4],[3,4],[3,4]])); + assert(!all_equal([1,2,1,1])); + assert(!all_equal([1,1.001,1,1.001,.999])); + assert(all_equal([1,1.001,1,1.001,.999],eps=.01)); +} +test_all_equal(); + + module test_all_nonzero() { assert(!all_nonzero(0)); assert(!all_nonzero([0,0,0])); diff --git a/tests/test_shapes2d.scad b/tests/test_shapes2d.scad index 6ed09eb..5260810 100644 --- a/tests/test_shapes2d.scad +++ b/tests/test_shapes2d.scad @@ -126,95 +126,5 @@ module test_reuleaux_polygon() { test_reuleaux_polygon(); -module test_mask2d_chamfer() { - assert_approx(mask2d_chamfer(x=10),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[10,0]]); - assert_approx(mask2d_chamfer(y=10),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[10,0]]); - assert_approx(mask2d_chamfer(edge=10),[[7.07106781187,-0.01],[-0.01,-0.01],[-0.01,7.07106781187],[0,7.07106781187],[7.07106781187,0]]); - assert_approx(mask2d_chamfer(x=10,angle=30),[[10,-0.01],[-0.01,-0.01],[-0.01,17.3205080757],[0,17.3205080757],[10,0]]); - assert_approx(mask2d_chamfer(y=10,angle=30),[[5.7735026919,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[5.7735026919,0]]); - assert_approx(mask2d_chamfer(edge=10,angle=30),[[5,-0.01],[-0.01,-0.01],[-0.01,8.66025403784],[0,8.66025403784],[5,0]]); - assert_approx(mask2d_chamfer(x=10,angle=30,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,18.3205080757],[1,18.3205080757],[11,1]]); - assert_approx(mask2d_chamfer(y=10,angle=30,inset=1),[[6.7735026919,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[6.7735026919,1]]); - assert_approx(mask2d_chamfer(edge=10,angle=30,inset=1),[[6,-0.01],[-0.01,-0.01],[-0.01,9.66025403784],[1,9.66025403784],[6,1]]); - assert_approx(mask2d_chamfer(x=10,angle=30,inset=1,excess=1),[[11,-1],[-1,-1],[-1,18.3205080757],[1,18.3205080757],[11,1]]); - assert_approx(mask2d_chamfer(y=10,angle=30,inset=1,excess=1),[[6.7735026919,-1],[-1,-1],[-1,11],[1,11],[6.7735026919,1]]); - assert_approx(mask2d_chamfer(edge=10,angle=30,inset=1,excess=1),[[6,-1],[-1,-1],[-1,9.66025403784],[1,9.66025403784],[6,1]]); -} -test_mask2d_chamfer(); - - -module test_mask2d_cove() { - $fn = 24; - assert_approx(mask2d_cove(r=10),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[2.58819045103,9.65925826289],[5,8.66025403784],[7.07106781187,7.07106781187],[8.66025403784,5],[9.65925826289,2.58819045103],[10,0]]); - assert_approx(mask2d_cove(d=20),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[2.58819045103,9.65925826289],[5,8.66025403784],[7.07106781187,7.07106781187],[8.66025403784,5],[9.65925826289,2.58819045103],[10,0]]); - assert_approx(mask2d_cove(r=10,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[3.58819045103,10.6592582629],[6,9.66025403784],[8.07106781187,8.07106781187],[9.66025403784,6],[10.6592582629,3.58819045103],[11,1]]); - assert_approx(mask2d_cove(d=20,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[3.58819045103,10.6592582629],[6,9.66025403784],[8.07106781187,8.07106781187],[9.66025403784,6],[10.6592582629,3.58819045103],[11,1]]); - assert_approx(mask2d_cove(r=10,inset=1,excess=1),[[11,-1],[-1,-1],[-1,11],[1,11],[3.58819045103,10.6592582629],[6,9.66025403784],[8.07106781187,8.07106781187],[9.66025403784,6],[10.6592582629,3.58819045103],[11,1]]); - assert_approx(mask2d_cove(d=20,inset=1,excess=1),[[11,-1],[-1,-1],[-1,11],[1,11],[3.58819045103,10.6592582629],[6,9.66025403784],[8.07106781187,8.07106781187],[9.66025403784,6],[10.6592582629,3.58819045103],[11,1]]); -} -test_mask2d_cove(); - - -module test_mask2d_roundover() { - $fn = 24; - assert_approx(mask2d_roundover(r=10),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[0.340741737109,7.41180954897],[1.33974596216,5],[2.92893218813,2.92893218813],[5,1.33974596216],[7.41180954897,0.340741737109],[10,0]]); - assert_approx(mask2d_roundover(d=20),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[0.340741737109,7.41180954897],[1.33974596216,5],[2.92893218813,2.92893218813],[5,1.33974596216],[7.41180954897,0.340741737109],[10,0]]); - assert_approx(mask2d_roundover(r=10,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[1.34074173711,8.41180954897],[2.33974596216,6],[3.92893218813,3.92893218813],[6,2.33974596216],[8.41180954897,1.34074173711],[11,1]]); - assert_approx(mask2d_roundover(d=20,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[1.34074173711,8.41180954897],[2.33974596216,6],[3.92893218813,3.92893218813],[6,2.33974596216],[8.41180954897,1.34074173711],[11,1]]); - assert_approx(mask2d_roundover(r=10,inset=1,excess=1),[[11,-1],[-1,-1],[-1,11],[1,11],[1.34074173711,8.41180954897],[2.33974596216,6],[3.92893218813,3.92893218813],[6,2.33974596216],[8.41180954897,1.34074173711],[11,1]]); - assert_approx(mask2d_roundover(d=20,inset=1,excess=1),[[11,-1],[-1,-1],[-1,11],[1,11],[1.34074173711,8.41180954897],[2.33974596216,6],[3.92893218813,3.92893218813],[6,2.33974596216],[8.41180954897,1.34074173711],[11,1]]); -} -test_mask2d_roundover(); - - -module test_mask2d_dovetail() { - assert_approx(mask2d_dovetail(x=10),[[0,0],[-0.01,0],[-0.01,17.3205080757],[10,17.3205080757],[10,17.3205080757],[0,0]]); - assert_approx(mask2d_dovetail(y=10),[[0,0],[-0.01,0],[-0.01,10],[5.7735026919,10],[5.7735026919,10],[0,0]]); - assert_approx(mask2d_dovetail(edge=10),[[0,0],[-0.01,0],[-0.01,8.66025403784],[5,8.66025403784],[5,8.66025403784],[0,0]]); - assert_approx(mask2d_dovetail(x=10,angle=30),[[0,0],[-0.01,0],[-0.01,17.3205080757],[10,17.3205080757],[10,17.3205080757],[0,0]]); - assert_approx(mask2d_dovetail(y=10,angle=30),[[0,0],[-0.01,0],[-0.01,10],[5.7735026919,10],[5.7735026919,10],[0,0]]); - assert_approx(mask2d_dovetail(edge=10,angle=30),[[0,0],[-0.01,0],[-0.01,8.66025403784],[5,8.66025403784],[5,8.66025403784],[0,0]]); - assert_approx(mask2d_dovetail(x=10,angle=30,inset=1),[[1,0],[-0.01,0],[-0.01,18.3205080757],[11,18.3205080757],[11,18.3205080757],[1,1]]); - assert_approx(mask2d_dovetail(y=10,angle=30,inset=1),[[1,0],[-0.01,0],[-0.01,11],[6.7735026919,11],[6.7735026919,11],[1,1]]); - assert_approx(mask2d_dovetail(edge=10,angle=30,inset=1),[[1,0],[-0.01,0],[-0.01,9.66025403784],[6,9.66025403784],[6,9.66025403784],[1,1]]); - assert_approx(mask2d_dovetail(x=10,angle=30,inset=1,excess=1),[[1,0],[-1,0],[-1,18.3205080757],[11,18.3205080757],[11,18.3205080757],[1,1]]); - assert_approx(mask2d_dovetail(y=10,angle=30,inset=1,excess=1),[[1,0],[-1,0],[-1,11],[6.7735026919,11],[6.7735026919,11],[1,1]]); - assert_approx(mask2d_dovetail(edge=10,angle=30,inset=1,excess=1),[[1,0],[-1,0],[-1,9.66025403784],[6,9.66025403784],[6,9.66025403784],[1,1]]); -} -test_mask2d_dovetail(); - - -module test_mask2d_rabbet() { - assert_approx(mask2d_rabbet(10), [[10,-0.01],[-0.01,-0.01],[-0.01,10],[10,10]]); - assert_approx(mask2d_rabbet(size=10), [[10,-0.01],[-0.01,-0.01],[-0.01,10],[10,10]]); - assert_approx(mask2d_rabbet(size=[10,15]), [[10,-0.01],[-0.01,-0.01],[-0.01,15],[10,15]]); - assert_approx(mask2d_rabbet(size=[10,15],excess=1), [[10,-1],[-1,-1],[-1,15],[10,15]]); -} -test_mask2d_rabbet(); - - -module test_mask2d_teardrop() { - $fn=24; - assert_approx(mask2d_teardrop(r=10), [[5.85786437627,0],[5.85786437627,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[0.340741737109,7.41180954897],[1.33974596216,5],[2.92893218813,2.92893218813]]); - assert_approx(mask2d_teardrop(d=20), [[5.85786437627,0],[5.85786437627,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[0.340741737109,7.41180954897],[1.33974596216,5],[2.92893218813,2.92893218813]]); - assert_approx(mask2d_teardrop(r=10,angle=30), [[4.2264973081,0],[4.2264973081,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[0.340741737109,7.41180954897],[1.33974596216,5]]); - assert_approx(mask2d_teardrop(r=10,angle=30,excess=1), [[4.2264973081,0],[4.2264973081,-1],[-1,-1],[-1,10],[0,10],[0.340741737109,7.41180954897],[1.33974596216,5]]); -} -test_mask2d_teardrop(); - - -module test_mask2d_ogee() { - $fn=24; - assert_approx( - mask2d_ogee([ - "xstep",1, "ystep",1, // Starting shoulder. - "fillet",5, "round",5, // S-curve. - "ystep",1, "xstep",1 // Ending shoulder. - ]), - [[12,-0.01],[-0.01,-0.01],[-0.01,12],[1,12],[1,11],[1.32701564615,10.9892946162],[1.6526309611,10.9572243069],[1.97545161008,10.903926402],[2.29409522551,10.8296291314],[2.60719732652,10.7346506475],[2.91341716183,10.6193976626],[3.2114434511,10.4843637077],[3.5,10.3301270189],[3.7778511651,10.1573480615],[4.04380714504,9.96676670146],[4.2967290755,9.75919903739],[4.53553390593,9.53553390593],[4.75919903739,9.2967290755],[4.96676670146,9.04380714504],[5.15734806151,8.7778511651],[5.33012701892,8.5],[5.48436370766,8.2114434511],[5.61939766256,7.91341716183],[5.73465064748,7.60719732652],[5.82962913145,7.29409522551],[5.90392640202,6.97545161008],[5.95722430687,6.6526309611],[5.98929461619,6.32701564615],[6,6],[6.01070538381,5.67298435385],[6.04277569313,5.3473690389],[6.09607359798,5.02454838992],[6.17037086855,4.70590477449],[6.26534935252,4.39280267348],[6.38060233744,4.08658283817],[6.51563629234,3.7885565489],[6.66987298108,3.5],[6.84265193849,3.2221488349],[7.03323329854,2.95619285496],[7.24080096261,2.7032709245],[7.46446609407,2.46446609407],[7.7032709245,2.24080096261],[7.95619285496,2.03323329854],[8.2221488349,1.84265193849],[8.5,1.66987298108],[8.7885565489,1.51563629234],[9.08658283817,1.38060233744],[9.39280267348,1.26534935252],[9.70590477449,1.17037086855],[10.0245483899,1.09607359798],[10.3473690389,1.04277569313],[10.6729843538,1.01070538381],[11,1],[11,0],[12,0]] - ); -} -test_mask2d_ogee(); - // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap From 4ddc340fd538c98624b5dbc83f1217b02fdf7ab2 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Thu, 11 Nov 2021 20:05:53 -0500 Subject: [PATCH 5/5] add tests for new file --- tests/test_masks2d.scad | 92 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 tests/test_masks2d.scad diff --git a/tests/test_masks2d.scad b/tests/test_masks2d.scad new file mode 100644 index 0000000..13052db --- /dev/null +++ b/tests/test_masks2d.scad @@ -0,0 +1,92 @@ +include<../std.scad> + +module test_mask2d_chamfer() { + assert_approx(mask2d_chamfer(x=10),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[10,0]]); + assert_approx(mask2d_chamfer(y=10),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[10,0]]); + assert_approx(mask2d_chamfer(edge=10),[[7.07106781187,-0.01],[-0.01,-0.01],[-0.01,7.07106781187],[0,7.07106781187],[7.07106781187,0]]); + assert_approx(mask2d_chamfer(x=10,angle=30),[[10,-0.01],[-0.01,-0.01],[-0.01,17.3205080757],[0,17.3205080757],[10,0]]); + assert_approx(mask2d_chamfer(y=10,angle=30),[[5.7735026919,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[5.7735026919,0]]); + assert_approx(mask2d_chamfer(edge=10,angle=30),[[5,-0.01],[-0.01,-0.01],[-0.01,8.66025403784],[0,8.66025403784],[5,0]]); + assert_approx(mask2d_chamfer(x=10,angle=30,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,18.3205080757],[1,18.3205080757],[11,1]]); + assert_approx(mask2d_chamfer(y=10,angle=30,inset=1),[[6.7735026919,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[6.7735026919,1]]); + assert_approx(mask2d_chamfer(edge=10,angle=30,inset=1),[[6,-0.01],[-0.01,-0.01],[-0.01,9.66025403784],[1,9.66025403784],[6,1]]); + assert_approx(mask2d_chamfer(x=10,angle=30,inset=1,excess=1),[[11,-1],[-1,-1],[-1,18.3205080757],[1,18.3205080757],[11,1]]); + assert_approx(mask2d_chamfer(y=10,angle=30,inset=1,excess=1),[[6.7735026919,-1],[-1,-1],[-1,11],[1,11],[6.7735026919,1]]); + assert_approx(mask2d_chamfer(edge=10,angle=30,inset=1,excess=1),[[6,-1],[-1,-1],[-1,9.66025403784],[1,9.66025403784],[6,1]]); +} +test_mask2d_chamfer(); + + +module test_mask2d_cove() { + $fn = 24; + assert_approx(mask2d_cove(r=10),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[2.58819045103,9.65925826289],[5,8.66025403784],[7.07106781187,7.07106781187],[8.66025403784,5],[9.65925826289,2.58819045103],[10,0]]); + assert_approx(mask2d_cove(d=20),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[2.58819045103,9.65925826289],[5,8.66025403784],[7.07106781187,7.07106781187],[8.66025403784,5],[9.65925826289,2.58819045103],[10,0]]); + assert_approx(mask2d_cove(r=10,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[3.58819045103,10.6592582629],[6,9.66025403784],[8.07106781187,8.07106781187],[9.66025403784,6],[10.6592582629,3.58819045103],[11,1]]); + assert_approx(mask2d_cove(d=20,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[3.58819045103,10.6592582629],[6,9.66025403784],[8.07106781187,8.07106781187],[9.66025403784,6],[10.6592582629,3.58819045103],[11,1]]); + assert_approx(mask2d_cove(r=10,inset=1,excess=1),[[11,-1],[-1,-1],[-1,11],[1,11],[3.58819045103,10.6592582629],[6,9.66025403784],[8.07106781187,8.07106781187],[9.66025403784,6],[10.6592582629,3.58819045103],[11,1]]); + assert_approx(mask2d_cove(d=20,inset=1,excess=1),[[11,-1],[-1,-1],[-1,11],[1,11],[3.58819045103,10.6592582629],[6,9.66025403784],[8.07106781187,8.07106781187],[9.66025403784,6],[10.6592582629,3.58819045103],[11,1]]); +} +test_mask2d_cove(); + + +module test_mask2d_roundover() { + $fn = 24; + assert_approx(mask2d_roundover(r=10),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[0.340741737109,7.41180954897],[1.33974596216,5],[2.92893218813,2.92893218813],[5,1.33974596216],[7.41180954897,0.340741737109],[10,0]]); + assert_approx(mask2d_roundover(d=20),[[10,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[0.340741737109,7.41180954897],[1.33974596216,5],[2.92893218813,2.92893218813],[5,1.33974596216],[7.41180954897,0.340741737109],[10,0]]); + assert_approx(mask2d_roundover(r=10,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[1.34074173711,8.41180954897],[2.33974596216,6],[3.92893218813,3.92893218813],[6,2.33974596216],[8.41180954897,1.34074173711],[11,1]]); + assert_approx(mask2d_roundover(d=20,inset=1),[[11,-0.01],[-0.01,-0.01],[-0.01,11],[1,11],[1.34074173711,8.41180954897],[2.33974596216,6],[3.92893218813,3.92893218813],[6,2.33974596216],[8.41180954897,1.34074173711],[11,1]]); + assert_approx(mask2d_roundover(r=10,inset=1,excess=1),[[11,-1],[-1,-1],[-1,11],[1,11],[1.34074173711,8.41180954897],[2.33974596216,6],[3.92893218813,3.92893218813],[6,2.33974596216],[8.41180954897,1.34074173711],[11,1]]); + assert_approx(mask2d_roundover(d=20,inset=1,excess=1),[[11,-1],[-1,-1],[-1,11],[1,11],[1.34074173711,8.41180954897],[2.33974596216,6],[3.92893218813,3.92893218813],[6,2.33974596216],[8.41180954897,1.34074173711],[11,1]]); +} +test_mask2d_roundover(); + + +module test_mask2d_dovetail() { + assert_approx(mask2d_dovetail(x=10),[[0,0],[-0.01,0],[-0.01,17.3205080757],[10,17.3205080757],[10,17.3205080757],[0,0]]); + assert_approx(mask2d_dovetail(y=10),[[0,0],[-0.01,0],[-0.01,10],[5.7735026919,10],[5.7735026919,10],[0,0]]); + assert_approx(mask2d_dovetail(edge=10),[[0,0],[-0.01,0],[-0.01,8.66025403784],[5,8.66025403784],[5,8.66025403784],[0,0]]); + assert_approx(mask2d_dovetail(x=10,angle=30),[[0,0],[-0.01,0],[-0.01,17.3205080757],[10,17.3205080757],[10,17.3205080757],[0,0]]); + assert_approx(mask2d_dovetail(y=10,angle=30),[[0,0],[-0.01,0],[-0.01,10],[5.7735026919,10],[5.7735026919,10],[0,0]]); + assert_approx(mask2d_dovetail(edge=10,angle=30),[[0,0],[-0.01,0],[-0.01,8.66025403784],[5,8.66025403784],[5,8.66025403784],[0,0]]); + assert_approx(mask2d_dovetail(x=10,angle=30,inset=1),[[1,0],[-0.01,0],[-0.01,18.3205080757],[11,18.3205080757],[11,18.3205080757],[1,1]]); + assert_approx(mask2d_dovetail(y=10,angle=30,inset=1),[[1,0],[-0.01,0],[-0.01,11],[6.7735026919,11],[6.7735026919,11],[1,1]]); + assert_approx(mask2d_dovetail(edge=10,angle=30,inset=1),[[1,0],[-0.01,0],[-0.01,9.66025403784],[6,9.66025403784],[6,9.66025403784],[1,1]]); + assert_approx(mask2d_dovetail(x=10,angle=30,inset=1,excess=1),[[1,0],[-1,0],[-1,18.3205080757],[11,18.3205080757],[11,18.3205080757],[1,1]]); + assert_approx(mask2d_dovetail(y=10,angle=30,inset=1,excess=1),[[1,0],[-1,0],[-1,11],[6.7735026919,11],[6.7735026919,11],[1,1]]); + assert_approx(mask2d_dovetail(edge=10,angle=30,inset=1,excess=1),[[1,0],[-1,0],[-1,9.66025403784],[6,9.66025403784],[6,9.66025403784],[1,1]]); +} +test_mask2d_dovetail(); + + +module test_mask2d_rabbet() { + assert_approx(mask2d_rabbet(10), [[10,-0.01],[-0.01,-0.01],[-0.01,10],[10,10]]); + assert_approx(mask2d_rabbet(size=10), [[10,-0.01],[-0.01,-0.01],[-0.01,10],[10,10]]); + assert_approx(mask2d_rabbet(size=[10,15]), [[10,-0.01],[-0.01,-0.01],[-0.01,15],[10,15]]); + assert_approx(mask2d_rabbet(size=[10,15],excess=1), [[10,-1],[-1,-1],[-1,15],[10,15]]); +} +test_mask2d_rabbet(); + + +module test_mask2d_teardrop() { + $fn=24; + assert_approx(mask2d_teardrop(r=10), [[5.85786437627,0],[5.85786437627,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[0.340741737109,7.41180954897],[1.33974596216,5],[2.92893218813,2.92893218813]]); + assert_approx(mask2d_teardrop(d=20), [[5.85786437627,0],[5.85786437627,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[0.340741737109,7.41180954897],[1.33974596216,5],[2.92893218813,2.92893218813]]); + assert_approx(mask2d_teardrop(r=10,angle=30), [[4.2264973081,0],[4.2264973081,-0.01],[-0.01,-0.01],[-0.01,10],[0,10],[0.340741737109,7.41180954897],[1.33974596216,5]]); + assert_approx(mask2d_teardrop(r=10,angle=30,excess=1), [[4.2264973081,0],[4.2264973081,-1],[-1,-1],[-1,10],[0,10],[0.340741737109,7.41180954897],[1.33974596216,5]]); +} +test_mask2d_teardrop(); + + +module test_mask2d_ogee() { + $fn=24; + assert_approx( + mask2d_ogee([ + "xstep",1, "ystep",1, // Starting shoulder. + "fillet",5, "round",5, // S-curve. + "ystep",1, "xstep",1 // Ending shoulder. + ]), + [[12,-0.01],[-0.01,-0.01],[-0.01,12],[1,12],[1,11],[1.32701564615,10.9892946162],[1.6526309611,10.9572243069],[1.97545161008,10.903926402],[2.29409522551,10.8296291314],[2.60719732652,10.7346506475],[2.91341716183,10.6193976626],[3.2114434511,10.4843637077],[3.5,10.3301270189],[3.7778511651,10.1573480615],[4.04380714504,9.96676670146],[4.2967290755,9.75919903739],[4.53553390593,9.53553390593],[4.75919903739,9.2967290755],[4.96676670146,9.04380714504],[5.15734806151,8.7778511651],[5.33012701892,8.5],[5.48436370766,8.2114434511],[5.61939766256,7.91341716183],[5.73465064748,7.60719732652],[5.82962913145,7.29409522551],[5.90392640202,6.97545161008],[5.95722430687,6.6526309611],[5.98929461619,6.32701564615],[6,6],[6.01070538381,5.67298435385],[6.04277569313,5.3473690389],[6.09607359798,5.02454838992],[6.17037086855,4.70590477449],[6.26534935252,4.39280267348],[6.38060233744,4.08658283817],[6.51563629234,3.7885565489],[6.66987298108,3.5],[6.84265193849,3.2221488349],[7.03323329854,2.95619285496],[7.24080096261,2.7032709245],[7.46446609407,2.46446609407],[7.7032709245,2.24080096261],[7.95619285496,2.03323329854],[8.2221488349,1.84265193849],[8.5,1.66987298108],[8.7885565489,1.51563629234],[9.08658283817,1.38060233744],[9.39280267348,1.26534935252],[9.70590477449,1.17037086855],[10.0245483899,1.09607359798],[10.3473690389,1.04277569313],[10.6729843538,1.01070538381],[11,1],[11,0],[12,0]] + ); +} +test_mask2d_ogee(); +