mirror of
https://github.com/revarbat/BOSL2.git
synced 2025-01-16 13:50:23 +01:00
Merge pull request #727 from adrianVmariano/master
doc tweaks/vnf_merge split
This commit is contained in:
commit
2fec5a381d
@ -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(); ...}
|
||||
|
@ -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()
|
||||
|
@ -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] == [];
|
||||
|
||||
|
||||
|
||||
|
26
edges.scad
26
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)])) :
|
||||
|
@ -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)]
|
||||
|
@ -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]]),
|
||||
|
@ -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(
|
||||
|
@ -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:
|
||||
|
503
masks2d.scad
Normal file
503
masks2d.scad
Normal file
@ -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 <BOSL2/std.scad>
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
|
@ -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 <BOSL2/std.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]),
|
||||
|
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
529
shapes2d.scad
529
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 <BOSL2/std.scad>
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -367,7 +365,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 +1007,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 +1049,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);
|
||||
|
||||
|
||||
@ -1221,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
|
||||
|
@ -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],
|
||||
|
18
skin.scad
18
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(
|
||||
@ -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))],
|
||||
@ -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]<bestcost ? result[1] : bestmap,
|
||||
bestpoly = result[0]<bestcost ? shifted : bestpoly,
|
||||
@ -1555,8 +1555,8 @@ function _skin_distance_match(poly1,poly2) =
|
||||
// These shifts are needed to handle the case when points from both ends of one curve map to a single point on the other
|
||||
bigshift = len(bigmap) - max(max_index(bigmap,all=true))-1,
|
||||
smallshift = len(smallmap) - max(max_index(smallmap,all=true))-1,
|
||||
newsmall = polygon_shift(repeat_entries(small,unique_count(smallmap)[1]),smallshift),
|
||||
newbig = polygon_shift(repeat_entries(map_poly[1],unique_count(bigmap)[1]),bigshift)
|
||||
newsmall = list_rotate(repeat_entries(small,unique_count(smallmap)[1]),smallshift),
|
||||
newbig = list_rotate(repeat_entries(map_poly[1],unique_count(bigmap)[1]),bigshift)
|
||||
)
|
||||
swap ? [newbig, newsmall] : [newsmall,newbig];
|
||||
|
||||
@ -1571,8 +1571,8 @@ function _skin_aligned_distance_match(poly1, poly2) =
|
||||
map = _dp_extract_map(result[1]),
|
||||
shift0 = len(map[0]) - max(max_index(map[0],all=true))-1,
|
||||
shift1 = len(map[1]) - max(max_index(map[1],all=true))-1,
|
||||
new0 = polygon_shift(repeat_entries(poly1,unique_count(map[0])[1]),shift0),
|
||||
new1 = polygon_shift(repeat_entries(poly2,unique_count(map[1])[1]),shift1)
|
||||
new0 = list_rotate(repeat_entries(poly1,unique_count(map[0])[1]),shift0),
|
||||
new1 = list_rotate(repeat_entries(poly2,unique_count(map[1])[1]),shift1)
|
||||
)
|
||||
[new0,new1];
|
||||
|
||||
@ -1598,7 +1598,7 @@ function _skin_tangent_match(poly1, poly2) =
|
||||
curve_offset = centroid(small)-centroid(big),
|
||||
cutpts = [for(i=[0:len(small)-1]) _find_one_tangent(big, select(small,i,i+1),curve_offset=curve_offset)],
|
||||
shift = last(cutpts)+1,
|
||||
newbig = polygon_shift(big, shift),
|
||||
newbig = list_rotate(big, shift),
|
||||
repeat_counts = [for(i=[0:len(small)-1]) posmod(cutpts[i]-select(cutpts,i-1),len(big))],
|
||||
newsmall = repeat_entries(small,repeat_counts)
|
||||
)
|
||||
|
3
std.scad
3
std.scad
@ -17,7 +17,8 @@ include <attachments.scad>
|
||||
include <shapes3d.scad>
|
||||
include <shapes2d.scad>
|
||||
include <drawing.scad>
|
||||
include <masks.scad>
|
||||
include <masks3d.scad>
|
||||
include <masks2d.scad>
|
||||
include <paths.scad>
|
||||
include <edges.scad>
|
||||
include <lists.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]));
|
||||
|
@ -40,10 +40,9 @@ test_circle_2tangents();
|
||||
test_circle_3points();
|
||||
test_circle_point_tangents();
|
||||
|
||||
test_noncollinear_triple();
|
||||
test__noncollinear_triple();
|
||||
test_polygon_area();
|
||||
test_is_polygon_convex();
|
||||
test_polygon_shift();
|
||||
test_reindex_polygon();
|
||||
test_align_polygon();
|
||||
test_centroid();
|
||||
@ -787,15 +786,6 @@ module test_is_polygon_convex() {
|
||||
*test_is_polygon_convex();
|
||||
|
||||
|
||||
module test_polygon_shift() {
|
||||
path = [[1,1],[-1,1],[-1,-1],[1,-1]];
|
||||
assert(polygon_shift(path,1) == [[-1,1],[-1,-1],[1,-1],[1,1]]);
|
||||
assert(polygon_shift(path,2) == [[-1,-1],[1,-1],[1,1],[-1,1]]);
|
||||
}
|
||||
*test_polygon_shift();
|
||||
|
||||
|
||||
|
||||
module test_reindex_polygon() {
|
||||
pent = subdivide_path([for(i=[0:4])[sin(72*i),cos(72*i)]],5);
|
||||
circ = circle($fn=5,r=2.2);
|
||||
@ -827,13 +817,13 @@ module test_align_polygon() {
|
||||
*test_align_polygon();
|
||||
|
||||
|
||||
module test_noncollinear_triple() {
|
||||
assert(noncollinear_triple([[1,1],[2,2],[3,3],[4,4],[4,5],[5,6]]) == [0,5,3]);
|
||||
assert(noncollinear_triple([[1,1],[2,2],[8,3],[4,4],[4,5],[5,6]]) == [0,2,5]);
|
||||
module test__noncollinear_triple() {
|
||||
assert(_noncollinear_triple([[1,1],[2,2],[3,3],[4,4],[4,5],[5,6]]) == [0,5,3]);
|
||||
assert(_noncollinear_triple([[1,1],[2,2],[8,3],[4,4],[4,5],[5,6]]) == [0,2,5]);
|
||||
u = unit([5,3]);
|
||||
assert_equal(noncollinear_triple([for(i = [2,3,4,5,7,12,15]) i * u], error=false),[]);
|
||||
assert_equal(_noncollinear_triple([for(i = [2,3,4,5,7,12,15]) i * u], error=false),[]);
|
||||
}
|
||||
*test_noncollinear_triple();
|
||||
*test__noncollinear_triple();
|
||||
|
||||
|
||||
module test_centroid() {
|
||||
|
@ -133,6 +133,9 @@ module test_list_rotate() {
|
||||
assert(list_rotate([1,2,3,4,5],5) == [1,2,3,4,5]);
|
||||
assert(list_rotate([1,2,3,4,5],6) == [2,3,4,5,1]);
|
||||
assert(list_rotate([],3) == []);
|
||||
path = [[1,1],[-1,1],[-1,-1],[1,-1]];
|
||||
assert(list_rotate(path,1) == [[-1,1],[-1,-1],[1,-1],[1,1]]);
|
||||
assert(list_rotate(path,2) == [[-1,-1],[1,-1],[1,1],[-1,1]]);
|
||||
}
|
||||
test_list_rotate();
|
||||
|
||||
|
92
tests/test_masks2d.scad
Normal file
92
tests/test_masks2d.scad
Normal file
@ -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();
|
||||
|
@ -100,10 +100,11 @@ test_teardrop2d();
|
||||
|
||||
module test_glued_circles() {
|
||||
$fn=24;
|
||||
assert_approx(glued_circles(r=15, spread=40, tangent=30), [[35,0],[34.4888873943,-3.88228567654],[32.9903810568,-7.5],[30.6066017178,-10.6066017178],[27.5,-12.9903810568],[23.8822856765,-14.4888873943],[20,-15],[16.1177143235,-14.4888873943],[12.5,-12.9903810568],[12.5,-12.9903810568],[6.47047612756,-10.4928704942],[0,-9.64101615138],[-6.47047612756,-10.4928704942],[-12.5,-12.9903810568],[-12.5,-12.9903810568],[-16.1177143235,-14.4888873943],[-20,-15],[-23.8822856765,-14.4888873943],[-27.5,-12.9903810568],[-30.6066017178,-10.6066017178],[-32.9903810568,-7.5],[-34.4888873943,-3.88228567654],[-35,0],[-34.4888873943,3.88228567654],[-32.9903810568,7.5],[-30.6066017178,10.6066017178],[-27.5,12.9903810568],[-23.8822856765,14.4888873943],[-20,15],[-16.1177143235,14.4888873943],[-12.5,12.9903810568],[-6.47047612756,10.4928704942],[0,9.64101615138],[6.47047612756,10.4928704942],[12.5,12.9903810568],[12.5,12.9903810568],[16.1177143235,14.4888873943],[20,15],[23.8822856765,14.4888873943],[27.5,12.9903810568],[30.6066017178,10.6066017178],[32.9903810568,7.5],[34.4888873943,3.88228567654]]);
|
||||
assert_approx(glued_circles(d=30, spread=40, tangent=30), [[35,0],[34.4888873943,-3.88228567654],[32.9903810568,-7.5],[30.6066017178,-10.6066017178],[27.5,-12.9903810568],[23.8822856765,-14.4888873943],[20,-15],[16.1177143235,-14.4888873943],[12.5,-12.9903810568],[12.5,-12.9903810568],[6.47047612756,-10.4928704942],[0,-9.64101615138],[-6.47047612756,-10.4928704942],[-12.5,-12.9903810568],[-12.5,-12.9903810568],[-16.1177143235,-14.4888873943],[-20,-15],[-23.8822856765,-14.4888873943],[-27.5,-12.9903810568],[-30.6066017178,-10.6066017178],[-32.9903810568,-7.5],[-34.4888873943,-3.88228567654],[-35,0],[-34.4888873943,3.88228567654],[-32.9903810568,7.5],[-30.6066017178,10.6066017178],[-27.5,12.9903810568],[-23.8822856765,14.4888873943],[-20,15],[-16.1177143235,14.4888873943],[-12.5,12.9903810568],[-6.47047612756,10.4928704942],[0,9.64101615138],[6.47047612756,10.4928704942],[12.5,12.9903810568],[12.5,12.9903810568],[16.1177143235,14.4888873943],[20,15],[23.8822856765,14.4888873943],[27.5,12.9903810568],[30.6066017178,10.6066017178],[32.9903810568,7.5],[34.4888873943,3.88228567654]]);
|
||||
assert_approx(glued_circles(d=30, spread=30, tangent=45), [[30,0],[29.4888873943,-3.88228567654],[27.9903810568,-7.5],[25.6066017178,-10.6066017178],[22.5,-12.9903810568],[18.8822856765,-14.4888873943],[15,-15],[11.1177143235,-14.4888873943],[7.5,-12.9903810568],[4.3933982822,-10.6066017178],[4.3933982822,-10.6066017178],[3.1066017178,-9.61920798589],[1.60809538023,-8.99850633757],[0,-8.7867965644],[-1.60809538023,-8.99850633757],[-3.1066017178,-9.61920798589],[-4.3933982822,-10.6066017178],[-4.3933982822,-10.6066017178],[-7.5,-12.9903810568],[-11.1177143235,-14.4888873943],[-15,-15],[-18.8822856765,-14.4888873943],[-22.5,-12.9903810568],[-25.6066017178,-10.6066017178],[-27.9903810568,-7.5],[-29.4888873943,-3.88228567654],[-30,0],[-29.4888873943,3.88228567654],[-27.9903810568,7.5],[-25.6066017178,10.6066017178],[-22.5,12.9903810568],[-18.8822856765,14.4888873943],[-15,15],[-11.1177143235,14.4888873943],[-7.5,12.9903810568],[-4.3933982822,10.6066017178],[-3.1066017178,9.61920798589],[-1.60809538023,8.99850633757],[0,8.7867965644],[1.60809538023,8.99850633757],[3.1066017178,9.61920798589],[4.3933982822,10.6066017178],[4.3933982822,10.6066017178],[7.5,12.9903810568],[11.1177143235,14.4888873943],[15,15],[18.8822856765,14.4888873943],[22.5,12.9903810568],[25.6066017178,10.6066017178],[27.9903810568,7.5],[29.4888873943,3.88228567654]]);
|
||||
assert_approx(glued_circles(d=30, spread=30, tangent=-30), [[30,0],[29.4888873943,-3.88228567654],[27.9903810568,-7.5],[25.6066017178,-10.6066017178],[22.5,-12.9903810568],[22.5,-12.9903810568],[11.6468570296,-17.4859000695],[0,-19.0192378865],[-11.6468570296,-17.4859000695],[-22.5,-12.9903810568],[-22.5,-12.9903810568],[-25.6066017178,-10.6066017178],[-27.9903810568,-7.5],[-29.4888873943,-3.88228567654],[-30,0],[-29.4888873943,3.88228567654],[-27.9903810568,7.5],[-25.6066017178,10.6066017178],[-22.5,12.9903810568],[-11.6468570296,17.4859000695],[0,19.0192378865],[11.6468570296,17.4859000695],[22.5,12.9903810568],[22.5,12.9903810568],[25.6066017178,10.6066017178],[27.9903810568,7.5],[29.4888873943,3.88228567654]]);
|
||||
assert_approx(glued_circles(r=15, spread=40, tangent=30), deduplicate([[35,0],[34.4888873943,-3.88228567654],[32.9903810568,-7.5],[30.6066017178,-10.6066017178],[27.5,-12.9903810568],[23.8822856765,-14.4888873943],[20,-15],[16.1177143235,-14.4888873943],[12.5,-12.9903810568],[12.5,-12.9903810568],[6.47047612756,-10.4928704942],[0,-9.64101615138],[-6.47047612756,-10.4928704942],[-12.5,-12.9903810568],[-12.5,-12.9903810568],[-16.1177143235,-14.4888873943],[-20,-15],[-23.8822856765,-14.4888873943],[-27.5,-12.9903810568],[-30.6066017178,-10.6066017178],[-32.9903810568,-7.5],[-34.4888873943,-3.88228567654],[-35,0],[-34.4888873943,3.88228567654],[-32.9903810568,7.5],[-30.6066017178,10.6066017178],[-27.5,12.9903810568],[-23.8822856765,14.4888873943],[-20,15],[-16.1177143235,14.4888873943],[-12.5,12.9903810568],[-6.47047612756,10.4928704942],[0,9.64101615138],[6.47047612756,10.4928704942],[12.5,12.9903810568],[12.5,12.9903810568],[16.1177143235,14.4888873943],[20,15],[23.8822856765,14.4888873943],[27.5,12.9903810568],[30.6066017178,10.6066017178],[32.9903810568,7.5],[34.4888873943,3.88228567654]]));
|
||||
assert_approx(glued_circles(d=30, spread=40, tangent=30),deduplicate( [[35,0],[34.4888873943,-3.88228567654],[32.9903810568,-7.5],[30.6066017178,-10.6066017178],[27.5,-12.9903810568],[23.8822856765,-14.4888873943],[20,-15],[16.1177143235,-14.4888873943],[12.5,-12.9903810568],[12.5,-12.9903810568],[6.47047612756,-10.4928704942],[0,-9.64101615138],[-6.47047612756,-10.4928704942],[-12.5,-12.9903810568],[-12.5,-12.9903810568],[-16.1177143235,-14.4888873943],[-20,-15],[-23.8822856765,-14.4888873943],[-27.5,-12.9903810568],[-30.6066017178,-10.6066017178],[-32.9903810568,-7.5],[-34.4888873943,-3.88228567654],[-35,0],[-34.4888873943,3.88228567654],[-32.9903810568,7.5],[-30.6066017178,10.6066017178],[-27.5,12.9903810568],[-23.8822856765,14.4888873943],[-20,15],[-16.1177143235,14.4888873943],[-12.5,12.9903810568],[-6.47047612756,10.4928704942],[0,9.64101615138],[6.47047612756,10.4928704942],[12.5,12.9903810568],[12.5,12.9903810568],[16.1177143235,14.4888873943],[20,15],[23.8822856765,14.4888873943],[27.5,12.9903810568],[30.6066017178,10.6066017178],[32.9903810568,7.5],[34.4888873943,3.88228567654]]));
|
||||
assert_approx(glued_circles(d=30, spread=30, tangent=45),deduplicate( [[30,0],[29.4888873943,-3.88228567654],[27.9903810568,-7.5],[25.6066017178,-10.6066017178],[22.5,-12.9903810568],[18.8822856765,-14.4888873943],[15,-15],[11.1177143235,-14.4888873943],[7.5,-12.9903810568],[4.3933982822,-10.6066017178],[4.3933982822,-10.6066017178],[3.1066017178,-9.61920798589],[1.60809538023,-8.99850633757],[0,-8.7867965644],[-1.60809538023,-8.99850633757],[-3.1066017178,-9.61920798589],[-4.3933982822,-10.6066017178],[-4.3933982822,-10.6066017178],[-7.5,-12.9903810568],[-11.1177143235,-14.4888873943],[-15,-15],[-18.8822856765,-14.4888873943],[-22.5,-12.9903810568],[-25.6066017178,-10.6066017178],[-27.9903810568,-7.5],[-29.4888873943,-3.88228567654],[-30,0],[-29.4888873943,3.88228567654],[-27.9903810568,7.5],[-25.6066017178,10.6066017178],[-22.5,12.9903810568],[-18.8822856765,14.4888873943],[-15,15],[-11.1177143235,14.4888873943],[-7.5,12.9903810568],[-4.3933982822,10.6066017178],[-3.1066017178,9.61920798589],[-1.60809538023,8.99850633757],[0,8.7867965644],[1.60809538023,8.99850633757],[3.1066017178,9.61920798589],[4.3933982822,10.6066017178],[4.3933982822,10.6066017178],[7.5,12.9903810568],[11.1177143235,14.4888873943],[15,15],[18.8822856765,14.4888873943],[22.5,12.9903810568],[25.6066017178,10.6066017178],[27.9903810568,7.5],[29.4888873943,3.88228567654]]));
|
||||
assert_approx(glued_circles(d=30, spread=30, tangent=-30), deduplicate([[30,0],[29.4888873943,-3.88228567654],[27.9903810568,-7.5],[25.6066017178,-10.6066017178],[22.5,-12.9903810568],[22.5,-12.9903810568],[11.6468570296,-17.4859000695],[0,-19.0192378865],[-11.6468570296,-17.4859000695],[-22.5,-12.9903810568],[-22.5,-12.9903810568],[-25.6066017178,-10.6066017178],[-27.9903810568,-7.5],[-29.4888873943,-3.88228567654],[-30,0],[-29.4888873943,3.88228567654],[-27.9903810568,7.5],[-25.6066017178,10.6066017178],[-22.5,12.9903810568],[-11.6468570296,17.4859000695],[0,19.0192378865],[11.6468570296,17.4859000695],[22.5,12.9903810568],[22.5,12.9903810568],[25.6066017178,10.6066017178],[27.9903810568,7.5],[29.4888873943,3.88228567654]]));
|
||||
assert_approx(glued_circles(d=30, spread=50, tangent=0),[[40, 0], [39.4888873943, -3.88228567654], [37.9903810568, -7.5], [35.6066017178, -10.6066017178], [32.5, -12.9903810568], [28.8822856765, -14.4888873943], [25, -15], [-25, -15], [-28.8822856765, -14.4888873943], [-32.5, -12.9903810568], [-35.6066017178, -10.6066017178], [-37.9903810568, -7.5], [-39.4888873943, -3.88228567654], [-40, 0], [-39.4888873943, 3.88228567654], [-37.9903810568, 7.5], [-35.6066017178, 10.6066017178], [-32.5, 12.9903810568], [-28.8822856765, 14.4888873943], [-25, 15], [25, 15], [28.8822856765, 14.4888873943], [32.5, 12.9903810568], [35.6066017178, 10.6066017178], [37.9903810568, 7.5], [39.4888873943, 3.88228567654]]);
|
||||
}
|
||||
test_glued_circles();
|
||||
|
||||
@ -125,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
|
||||
|
@ -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() {
|
||||
|
@ -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)),
|
||||
|
103
vnf.scad
103
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,29 +313,9 @@ 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) =
|
||||
let(
|
||||
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)
|
||||
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:
|
||||
@ -346,7 +324,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) =
|
||||
@ -373,8 +351,8 @@ function _path_path_closest_vertices(path1,path2) =
|
||||
function _join_paths_at_vertices(path1,path2,v1,v2) =
|
||||
let(
|
||||
repeat_start = !approx(path1[v1],path2[v2]),
|
||||
path1 = clockwise_polygon(polygon_shift(path1,v1)),
|
||||
path2 = ccw_polygon(polygon_shift(path2,v2))
|
||||
path1 = clockwise_polygon(list_rotate(path1,v1)),
|
||||
path2 = ccw_polygon(list_rotate(path2,v2))
|
||||
)
|
||||
[
|
||||
each path1,
|
||||
@ -533,7 +511,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);
|
||||
|
||||
@ -596,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);
|
||||
@ -673,7 +684,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 +788,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 +954,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 +1326,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 +1338,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 +1348,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 +1362,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),
|
||||
|
Loading…
x
Reference in New Issue
Block a user