Compare commits

..

1 commit

Author SHA1 Message Date
Alex Matulich
fbdaf60359
Merge 835cbc0f00 into c442c5159a 2024-12-09 20:29:12 +00:00
7 changed files with 104 additions and 248 deletions

View file

@ -4998,127 +4998,4 @@ function _canonical_edge(edge) =
flip * edge; flip * edge;
// Section: Attachable Descriptions for Operating on Attachables or Restoring a Previous State
// Function: parent()
// Topics: Transforms, Attachments, Descriptions
// See Also: restore()
// Synopsis: Returns a description (transformation state and attachment geometry) of the parent
// Usage:
// PARENT() let( desc = parent() ) CHILDREN;
// Usage: in development releases only
// PARENT() { desc=parent(); CHILDREN; }
// Description:
// Returns a description of the closest attachable ancestor in the geometry tree, along with the current transformation. You can use this
// description to create new objects based on the described object or perform computations based on the described object. You can also use it to
// restore the context of the parent object and transformation state using {{restore()}}. Note that with OpenSCAD 2021.01 you need to use `let` for
// this function to work, and the definition of the variable is scoped to the children of the let module.
// (In development versions the use of let is no longer necessary.) Note that if OpenSCAD displays any warnings
// related to transformation operations then the transformation that parent() returns is likely to be incorrect, even if OpenSCAD
// continues to run and produces a valid result.
function parent() =
let(
geom = default($parent_geom, attach_geom([0,0,0]))
)
[$transform, geom];
// Module: restore()
// Synopsis: Restores transformation state and attachment geometry from a description
// SynTags: Trans
// Topics: Transforms, Attachments, Descriptions
// See Also: parent()
// Usage:
// restore([desc]) CHILDREN;
// Description:
// Restores the transformation and parent geometry contained in the specified description which you obtained with {{parent()}}.
// If you don't give a description then restores the global world coordinate system with a zero size cuboid object as the parent.
// Arguments:
// desc = saved description to restore. Default: restore to world coordinates
// Example(3D): The pink cube is a child of the green cube, but {{restore()}} restores the state to the yellow parent cube, so the pink cube attaches to the yellow cube
// left(5) cuboid(10)
// let(save_pt = parent())
// attach(RIGHT,BOT) recolor("green") cuboid(3)
// restore(save_pt)
// attach(FWD,BOT) recolor("pink") cuboid(3);
module restore(desc)
{
req_children($children);
if (is_undef(desc)){
T = matrix_inverse($transform);
$parent_geom = ["prismoid", [CTR, UP, 0]];
multmatrix(T) children();
}
else{
assert(!is_undef(desc) && is_list(desc) && len(desc)==2, "Invalid desc");
T = linear_solve($transform, desc[0]);
$parent_geom = desc[1];
multmatrix(T) children();
}
}
// Function desc_point()
// Synopsis: Computes the location in the current context of an anchor point from an attachable description
// Topics: Descriptions, Attachments
// See Also: parent(), desc_dist()
// Usage:
// point = desc_point(desc,[anchor]);
// Description:
// Computes the coordinates of the specified anchor point in the given description relative to the current transformation state.
// Arguments:
// desc = Description to use to get the point
// anchor = Anchor point that you want to extract. Default: CENTER
// Example(3D): In this example we translate away from the parent object and then compute points on that object. Note that with OpenSCAD 2021.01 you must use union() or alternatively place the pt1 and pt2 assignments in a let() statement. This is not necessary in development versions.
// cuboid(10) let(desc=parent())
// right(12) up(27)
// union(){
// pt1 = desc_point(desc,TOP+BACK+LEFT);
// pt2 = desc_point(desc,TOP+FWD+RIGHT);
// stroke([pt1,pt2,CENTER], closed=true, width=.5,color="red");
// }
// Example(3D): Here we compute the point on the parent so we can draw a line anchored on the child object that connects to a computed point on the parent
// cuboid(10) let(desc=parent())
// attach(FWD,BOT) cuboid([3,3,7])
// attach(TOP+BACK+RIGHT, BOT)
// stroke([[0,0,0], desc_point(desc,TOP+FWD+RIGHT)],width=.5,color="red");
function desc_point(desc, anchor=CENTER) =
is_undef(desc) ? linear_solve($transform, [0,0,0,0])
: let(
anch = _find_anchor(anchor, desc[1]),
T = linear_solve($transform, desc[0])
)
apply(T, anch[1]);
// Function desc_dist()
// Synopsis: Computes the distance between two points specified by attachable descriptions
// Topics: Descriptions, Attachments
// See Also: parent(), desc_point()
// Usage:
// dist = desc_dist(desc1,anchor1,desc2,anchor2);
// dest = desc_dist(desc1=, desc2=, [anchor1=], [anchor2=]);
// Description:
// Computes the distance between two points specified using attachable descriptions and optional anchor
// points. If you omit the anchor point(s) then the computation uses the CENTER anchor.
// Example: Computes the distance between a point on each cube.
// cuboid(10) let(desc=parent())
// right(15) cuboid(10)
// echo(desc_dist(parent(),TOP+RIGHT+BACK, desc, TOP+LEFT+FWD));
function desc_dist(desc1,anchor1=CENTER, desc2, anchor2=CENTER)=
let(
anch1 = _find_anchor(anchor1, desc1[1]),
anch2 = _find_anchor(anchor2, desc2[1]),
Tinv = matrix_inverse($transform),
T1 = Tinv*desc1[0],
T2 = Tinv*desc2[0],
pt1 = apply(T1,anch1[1]),
pt2 = apply(T2,anch2[1])
)
norm(pt1-pt2);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -161,8 +161,6 @@ module color_overlaps(color="red") {
%children(); %children();
} }
// Section: Setting Object Transparency
// Module: ghost() // Module: ghost()
// Synopsis: Sets transparency for attachable children and their descendents. // Synopsis: Sets transparency for attachable children and their descendents.
// SynTags: Trans // SynTags: Trans

View file

@ -137,7 +137,7 @@ include<BOSL2/beziers.scad>
// Example(2D,NoAxes): Explicitly specified knots only change the quadratic clamped curve slightly. Knot count is len(control)-degree+1 = 9. // Example(2D,NoAxes): Explicitly specified knots only change the quadratic clamped curve slightly. Knot count is len(control)-degree+1 = 9.
// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
// knots = [0,1,3,5,9,13,14,19,21]; // knots = [0,1,3,5,9,13,14,19,21];
// debug_nurbs(pts,2,knots=knots); // debug_nurbs(pts,2);
// Example(2D,NoAxes): Combining explicit knots with mult for the quadratic curve to add a corner // Example(2D,NoAxes): Combining explicit knots with mult for the quadratic curve to add a corner
// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
// knots = [0,1,3,9,13,14,19,21]; // knots = [0,1,3,9,13,14,19,21];
@ -242,7 +242,7 @@ function nurbs_curve(control,degree,splinesteps,u, mult,weights,type="clamped",
type=="open" ? assert(len(xknots)==len(control)+degree+1, str("For open spline, knot vector with multiplicity must have length ", type=="open" ? assert(len(xknots)==len(control)+degree+1, str("For open spline, knot vector with multiplicity must have length ",
len(control)+degree+1," but has length ", len(xknots))) len(control)+degree+1," but has length ", len(xknots)))
xknots xknots
: type=="clamped" ? assert(len(xknots) == len(control)+1-degree, str("For clamped spline of degree ",degree,", knot vector with multiplicity must have length ", : type=="clamped" ? assert(len(xknots) == len(control)+1-degree, str("For clamped spline, knot vector with multiplicity must have length ",
len(control)+1-degree," but has length ", len(xknots))) len(control)+1-degree," but has length ", len(xknots)))
assert(xknots[0]!=xknots[1] && last(xknots)!=select(xknots,-2), assert(xknots[0]!=xknots[1] && last(xknots)!=select(xknots,-2),
"For clamped splint, first and last knots cannot repeat (must have multiplicity one") "For clamped splint, first and last knots cannot repeat (must have multiplicity one")
@ -288,8 +288,7 @@ function nurbs_curve(control,degree,splinesteps,u, mult,weights,type="clamped",
; ;
!done !done
; ;
output = (uind<len(adjusted_u) && approx(adjusted_u[uind],knot[kind]) && kind>kmult[0]-1 && ((kmultind>=len(kmult)-1 || kind+kmult[kmultind]>=len(control)))) output = (uind<len(adjusted_u) && approx(adjusted_u[uind],knot[kind]) && ((kmultind>=len(kmult)-1 || kind+kmult[kmultind]>=len(control)))) ? kind-kmult[kmultind-1]
?kind-kmult[kmultind-1]
: (uind<len(adjusted_u) && adjusted_u[uind]>=knot[kind] && adjusted_u[uind]>=knot[kind] && adjusted_u[uind]<knot[kind+kmult[kmultind]]) ? kind : (uind<len(adjusted_u) && adjusted_u[uind]>=knot[kind] && adjusted_u[uind]>=knot[kind] && adjusted_u[uind]<knot[kind+kmult[kmultind]]) ? kind
: undef, : undef,
done = uind==len(adjusted_u), done = uind==len(adjusted_u),
@ -301,7 +300,7 @@ function nurbs_curve(control,degree,splinesteps,u, mult,weights,type="clamped",
if (is_def(output)) output] if (is_def(output)) output]
) )
[for(i=idx(adjusted_u)) [for(i=idx(adjusted_u))
_nurbs_pt(knot,slice(control, knotidx[i]-degree,knotidx[i]), adjusted_u[i], 1, degree, knotidx[i]) _nurbs_pt(knot,select(control, knotidx[i]-degree,knotidx[i]), adjusted_u[i], 1, degree, knotidx[i])
]; ];

View file

@ -1378,10 +1378,10 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
// atype = Select "hull", "intersect", "surf_hull" or "surf_intersect" anchor types. Default: "hull" // atype = Select "hull", "intersect", "surf_hull" or "surf_intersect" anchor types. Default: "hull"
// cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" // cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid"
// Anchor Types: // Anchor Types:
// "hull" = Anchors to the convex hull of the linear sweep of the path, ignoring any end roundings. (default) // hull = Anchors to the convex hull of the linear sweep of the path, ignoring any end roundings. (default)
// "intersect" = Anchors to the surface of the linear sweep of the path, ignoring any end roundings. // intersect = Anchors to the surface of the linear sweep of the path, ignoring any end roundings.
// "surf_hull" = Anchors to the convex hull of the offset_sweep shape, including end treatments. // surf_hull = Anchors to the convex hull of the offset_sweep shape, including end treatments.
// "surf_intersect" = Anchors to the surface of the offset_sweep shape, including any end treatments. // surf_intersect = Anchors to the surface of the offset_sweep shape, including any end treatments.
// Named Anchors: // Named Anchors:
// "base" = Anchor to the base of the shape in its native position, ignoring any "extra" // "base" = Anchor to the base of the shape in its native position, ignoring any "extra"
// "top" = Anchor to the top of the shape in its native position, ignoring any "extra" // "top" = Anchor to the top of the shape in its native position, ignoring any "extra"
@ -2120,11 +2120,13 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
// "top_corner0", "top_corner1", etc = Top corner, pointing in direction of associated edge anchor, spin up along associated edge // "top_corner0", "top_corner1", etc = Top corner, pointing in direction of associated edge anchor, spin up along associated edge
// "bot_corner0", "bot_corner1", etc = Bottom corner, pointing in direction of associated edge anchor, spin up along associated edge // "bot_corner0", "bot_corner1", etc = Bottom corner, pointing in direction of associated edge anchor, spin up along associated edge
// Anchor Types: // Anchor Types:
// "hull" = Anchors to the VNF of the **unrounded** prism using VNF hull anchors (default) // hull = Anchors to the convex hull of the linear sweep of the path, ignoring any end roundings. (default)
// "intersect" = Anchors to the VNF of the **unrounded** prism using VNF intersection anchors (default) // intersect = Anchors to the surface of the linear sweep of the path, ignoring any end roundings.
// "surf_hull" = Use VNF hull anchors to the rounded VNF // surf_hull = Anchors to the convex hull of the offset_sweep shape, including end treatments.
// "surf_intersect" = USe VFN intersection anchors to the rounded VNF // surf_intersect = Anchors to the surface of the offset_sweep shape, including any end treatments.
// "prismoid" = For four sided prisms only, defined standard prismsoid anchors, with RIGHT set to the face closest to the RIGHT direction.
// "hull" = Anchors to the virtual convex hull of the prism.
// "intersect" = Anchors to the surface of the prism.
// Example: Uniformly rounded pentagonal prism // Example: Uniformly rounded pentagonal prism
// rounded_prism(pentagon(3), height=3, // rounded_prism(pentagon(3), height=3,
// joint_top=0.5, joint_bot=0.5, joint_sides=0.5); // joint_top=0.5, joint_bot=0.5, joint_sides=0.5);
@ -3382,7 +3384,7 @@ module join_prism(polygon, base, base_r, base_d, base_T=IDENT,
overlap, base_overlap,aux_overlap, overlap, base_overlap,aux_overlap,
n=15, base_n, end_n, aux_n, n=15, base_n, end_n, aux_n,
fillet, base_fillet,aux_fillet,end_round, fillet, base_fillet,aux_fillet,end_round,
k=0.7, base_k,aux_k,end_k,start,end, k=0.7, base_k,aux_k,end_k,
uniform=true, base_uniform, aux_uniform, uniform=true, base_uniform, aux_uniform,
debug=false, anchor="origin", extent=true, cp="centroid", atype="hull", orient=UP, spin=0, debug=false, anchor="origin", extent=true, cp="centroid", atype="hull", orient=UP, spin=0,
convexity=10) convexity=10)
@ -3397,7 +3399,7 @@ module join_prism(polygon, base, base_r, base_d, base_T=IDENT,
fillet=fillet, base_fillet=base_fillet, aux_fillet=aux_fillet, end_round=end_round, fillet=fillet, base_fillet=base_fillet, aux_fillet=aux_fillet, end_round=end_round,
k=k, base_k=base_k, aux_k=aux_k, end_k=end_k, k=k, base_k=base_k, aux_k=aux_k, end_k=end_k,
uniform=uniform, base_uniform=base_uniform, aux_uniform=aux_uniform, uniform=uniform, base_uniform=base_uniform, aux_uniform=aux_uniform,
debug=debug, start=start, end=end, debug=debug,
return_axis=true return_axis=true
); );
axis = vnf_start_end[2] - vnf_start_end[1]; axis = vnf_start_end[2] - vnf_start_end[1];
@ -3422,7 +3424,7 @@ function join_prism(polygon, base, base_r, base_d, base_T=IDENT,
fillet, base_fillet,aux_fillet,end_round, fillet, base_fillet,aux_fillet,end_round,
k=0.7, base_k,aux_k,end_k, k=0.7, base_k,aux_k,end_k,
uniform=true, base_uniform, aux_uniform, uniform=true, base_uniform, aux_uniform,
debug=false, return_axis=false, start, end) = debug=false, return_axis=false) =
let( let(
objects=["cyl","cylinder","plane","sphere"], objects=["cyl","cylinder","plane","sphere"],
length = one_defined([h,height,l,length], "h,height,l,length", dflt=undef) length = one_defined([h,height,l,length], "h,height,l,length", dflt=undef)
@ -3440,7 +3442,6 @@ function join_prism(polygon, base, base_r, base_d, base_T=IDENT,
assert(is_num(scale) && scale>=0, "Prism scale must be non-negative") assert(is_num(scale) && scale>=0, "Prism scale must be non-negative")
assert(num_defined([end_k,aux_k])<2, "Cannot define both end_k and aux_k") assert(num_defined([end_k,aux_k])<2, "Cannot define both end_k and aux_k")
assert(num_defined([end_n,aux_n])<2, "Cannot define both end_n and aux_n") assert(num_defined([end_n,aux_n])<2, "Cannot define both end_n and aux_n")
assert(prism_end_T==IDENT || num_defined([start,end])==0, "Cannot give prism_end_T with either start or end")
let( let(
base_r = get_radius(r=base_r,d=base_d), base_r = get_radius(r=base_r,d=base_d),
aux_r = get_radius(r=aux_r,d=aux_d), aux_r = get_radius(r=aux_r,d=aux_d),
@ -3468,33 +3469,31 @@ function join_prism(polygon, base, base_r, base_d, base_T=IDENT,
polygon=clockwise_polygon(polygon), polygon=clockwise_polygon(polygon),
start_center = CENTER, start_center = CENTER,
aux_T_horiz = submatrix(aux_T,[0:2],[0:2]) == ident(3) && aux_T[2][3]==0, aux_T_horiz = submatrix(aux_T,[0:2],[0:2]) == ident(3) && aux_T[2][3]==0,
dir = num_defined([start,end])==2 ? end-start dir = aux=="none" ? apply(aux_T,UP)
: aux=="none" ? apply(aux_T,UP)
: aux_T_horiz && in_list([base,aux], [["sphere","sphere"], ["cyl","cylinder"],["cylinder","cyl"], ["cyl","cyl"], ["cylinder", "cylinder"]]) ? : aux_T_horiz && in_list([base,aux], [["sphere","sphere"], ["cyl","cylinder"],["cylinder","cyl"], ["cyl","cyl"], ["cylinder", "cylinder"]]) ?
unit(apply(aux_T, aux_r*UP)) unit(apply(aux_T, aux_r*UP))
: apply(aux_T,CENTER)==CENTER ? apply(aux_T,UP) : apply(aux_T,CENTER)==CENTER ? apply(aux_T,UP)
: apply(aux_T,CENTER), : apply(aux_T,CENTER),
flip = short ? -1 : 1, flip = short ? -1 : 1,
axisline = [CENTER, flip*dir] + repeat(default(start,CENTER),2),
start = base=="sphere" ? start = base=="sphere" ?
let( answer = _sphere_line_isect_best(abs(base_r),axisline, sign(base_r)*flip*dir)) let( answer = _sphere_line_isect_best(abs(base_r),[CENTER,flip*dir], sign(base_r)*flip*dir))
assert(answer,"Prism center doesn't intersect sphere (base)") assert(answer,"Prism center doesn't intersect sphere (base)")
answer answer
: base=="cyl" || base=="cylinder" ? : base=="cyl" || base=="cylinder" ?
assert(dir.y!=0 || dir.z!=0, "Prism direction parallel to the cylinder") assert(dir.y!=0 || dir.z!=0, "Prism direction parallel to the cylinder")
let( let(
mapped = apply(yrot(90),axisline), mapped = apply(yrot(90),[CENTER,flip*dir]),
answer = _cyl_line_intersection(abs(base_r),mapped,sign(base_r)*mapped[1]) answer = _cyl_line_intersection(abs(base_r),mapped,sign(base_r)*mapped[1])
) )
assert(answer,"Prism center doesn't intersect cylinder (base)") assert(answer,"Prism center doesn't intersect cylinder (base)")
apply(yrot(-90),answer) apply(yrot(-90),answer)
: is_path(base) ? : is_path(base) ?
let( let(
mapped = apply(yrot(-90),axisline), mapped = apply(yrot(90),[CENTER,flip*dir]),
answer = _prism_line_isect(pair(base,wrap=true),mapped,mapped[1])[0] answer = _prism_line_isect(pair(base,wrap=true),mapped,mapped[1])[0]
) )
assert(answer,"Prism center doesn't intersect prism (base)") assert(answer,"Prism center doesn't intersect prism (base)")
apply(yrot(90),answer) apply(yrot(-90),answer)
: start_center, : start_center,
aux_T = aux=="none" ? move(start)*prism_end_T*move(-start)*move(length*dir)*move(start) aux_T = aux=="none" ? move(start)*prism_end_T*move(-start)*move(length*dir)*move(start)
: aux_T, : aux_T,
@ -3502,8 +3501,7 @@ function join_prism(polygon, base, base_r, base_d, base_T=IDENT,
aux = aux=="none" && aux_fillet!=0 ? "plane" : aux, aux = aux=="none" && aux_fillet!=0 ? "plane" : aux,
end_center = apply(aux_T,CENTER), end_center = apply(aux_T,CENTER),
ndir = base_r<0 ? unit(start_center-start) : unit(end_center-start_center,UP), ndir = base_r<0 ? unit(start_center-start) : unit(end_center-start_center,UP),
end_prelim = is_def(end) ? end end_prelim = apply(move(start)*prism_end_T*move(-start),
:apply(move(start)*prism_end_T*move(-start),
aux=="sphere" ? aux=="sphere" ?
let( answer = _sphere_line_isect_best(abs(aux_r), [start,start+ndir], -sign(aux_r)*ndir)) let( answer = _sphere_line_isect_best(abs(aux_r), [start,start+ndir], -sign(aux_r)*ndir))
assert(answer,"Prism center doesn't intersect sphere (aux)") assert(answer,"Prism center doesn't intersect sphere (aux)")
@ -3610,7 +3608,6 @@ function _sphere_line_isect_best(R, line, ref) =
// point, ind ind and u are the segment index and u value. Prism is z-aligned. // point, ind ind and u are the segment index and u value. Prism is z-aligned.
function _prism_line_isect(poly_pairs, line, ref) = function _prism_line_isect(poly_pairs, line, ref) =
let( let(
line2d = path2d(line), line2d = path2d(line),
ref=point2d(ref), ref=point2d(ref),
ilist = [for(j=idx(poly_pairs)) ilist = [for(j=idx(poly_pairs))
@ -3624,7 +3621,7 @@ function _prism_line_isect(poly_pairs, line, ref) =
isect2d = ilist[ind][0], isect2d = ilist[ind][0],
isect_ind = ilist[ind][1], isect_ind = ilist[ind][1],
isect_u = ilist[ind][2], isect_u = ilist[ind][2],
slope = (line[1].z-line[0].z)/norm(line2d[1]-line2d[0]), slope = (line[1].z-line[0].z)/norm(line[1]-line[0]),
z = slope * norm(line2d[0]-isect2d) + line[0].z z = slope * norm(line2d[0]-isect2d) + line[0].z
) )
[point3d(isect2d,z),isect_ind, isect_u]; [point3d(isect2d,z),isect_ind, isect_u];
@ -3638,6 +3635,35 @@ function _prism_fillet(name, base, R, bot, top, d, k, N, overlap,uniform,debug)
: is_path(base,2) ? _prism_fillet_prism(name, base, bot, top, d, k, N, overlap,uniform,debug) : is_path(base,2) ? _prism_fillet_prism(name, base, bot, top, d, k, N, overlap,uniform,debug)
: assert(false,"Unknown base type"); : assert(false,"Unknown base type");
function _prism_fillet_plane(name, bot, top, d, k, N, overlap,debug) =
let(
dir = sign(top[0].z-bot[0].z),
isect = [for (i=idx(top)) plane_line_intersection([0,0,1,0], [top[i],bot[i]])],
base_normal = -path3d(path_normals(path2d(isect), closed=true)),
mesh = transpose([for(i=idx(top))
let(
base_angle = vector_angle(top[i],isect[i],isect[i]+sign(d)*base_normal[i]),
// joint length
// d = r,
r=abs(d)*tan(base_angle/2),
// radius
//d = r/tan(base_angle/2),
// cut
//r = r / (1/sin(base_angle/2) - 1),
//d = r/tan(base_angle/2),
prev = unit(top[i]-isect[i]),
next = sign(d)*dir*base_normal[i],
center = r/sin(base_angle/2) * unit(prev+next) + isect[i]
)
[
each arc(N, cp=center, points = [isect[i]+prev*abs(d), isect[i]+next*d]),
isect[i]+next*d+[0,0,-overlap*dir]
]
])
)
assert(debug || is_path_simple(path2d(select(mesh,-2)),closed=true),"Fillet doesn't fit: it intersects itself")
mesh;
function _prism_fillet_plane(name, bot, top, d, k, N, overlap,debug) = function _prism_fillet_plane(name, bot, top, d, k, N, overlap,debug) =
let( let(

View file

@ -1994,77 +1994,97 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) =
// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable // Topics: Shapes (2D), Paths (2D), Path Generators, Attachable
// See Also: circle(), square(), supershape() // See Also: circle(), square(), supershape()
// Usage: As Module // Usage: As Module
// squircle(size, [squareness], [style]) [ATTACHMENTS]; // squircle(squareness, size, [style]) [ATTACHMENTS];
// Usage: As Function // Usage: As Function
// path = squircle(size, [squareness], [style]); // path = squircle(squareness, size, [style]);
// Description: // Description:
// A [squircle](https://en.wikipedia.org/wiki/Squircle) is a shape intermediate between a square/rectangle and a circle/ellipse.Squircles are sometimes used to make dinner plates (more area for the same radius as a circle), keyboard buttons, and smartphone icons. Old CRT television screens also resembled elongated squircles. // A [squircle](https://en.wikipedia.org/wiki/Squircle) is a shape intermediate between a square/rectangle and a circle/ellipse.Squircles are sometimes used to make dinner plates (more area for the same radius as a circle), keyboard buttons, and smartphone icons. Old CRT television screens also resembled elongated squircles.
// . // .
// There are multiple approaches to constructing a squircle. One approach is a special case of superellipse (shown in {{supershape}} example 3), and uses exponents between 2 and infinity to adjust the shape. Another, the Fernández-Guasti squircle or FG squircle, arises from work in optics and uses a "squareness" parameter between 0 and 1 to adjust the shape. We use the same squareness parameter for both types, adjusting the internal FG parameter or superellipse exponent as needed to achieve the same squircle corner extents. // There are multiple approaches to constructing a squircle. One approach is a special case of superellipse (shown in {{supershape}} example 3), and uses exponents to adjust the shape. Another, called Fernández-Guasti squircle or FG squircle, arises from work in optics and uses a "squareness" parameter between 0 and 1 to adjust the shape.
// . // .
// The FG style and superellipse style squircles are visually almost indistinguishable, with the superellipse having slightly rounder "corners" than FG for a given value of squareness. Either style requires just the two parameters `squareness` and `size`. The vertex distribution is adjusted to be more dense at the corners for smoothness at low values of `$fn`. // The FG style and superellipse style squircles are visually almost indistinguishable, with the superellipse having slightly rounder "corners" than FG for a given value of squareness. Either style requires just the two parameters `squareness` and `size`. The vertex distribution is adjusted to be more dense at the corners for smoothness at low values of `$fn`.
// . // .
// When called as a module, creates a 2D squircle with the desired squareness. // When called as a module, creates a 2D squircle with the desired squareness.
// When called as a function, returns a 2D path for a squircle. // When called as a function, returns a 2D path for a squircle.
// Arguments: // Arguments:
// size = Same as the `size` parameter in `square()`, can be a single number or a vector `[xsize,ysize]`. // squareness = Value between 0 and 1. Controls the shape of the squircle. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. Otherwise, this parameter sets the location of a squircle "corner" at the specified interpolated position between a circle and a square. For the "superellipse" style, the special case where the superellipse exponent is 4 (also known as *Lamé's quartic curve*) results in a squircle at the geometric mean between radial points on the circle and square, corresponding to squareness=0.456786. Default: 0.5
// squareness = Value between 0 and 1. Controls the shape, setting the location of a squircle "corner" at the specified interpolated position between a circle and a square. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. For the "superellipse" style, the special case where the superellipse exponent is 4 (also known as *Lamé's quartic curve*) results in a squircle at the geometric mean between radial points on the circle and square, corresponding to squareness=0.456786. Default: 0.5 // size = Same as the `size` parameter in `square()`, can be a single number or an `[xsize,ysize]` vector. Default: [1,1]
// style = method for generating a squircle, "fg" for Fernández-Guasti and "superellipse" for superellipse. Default: "fg" // style = method for generating a squircle, "fg" for Fernández-Guasti and "superellipse" for superellipse. Default: "fg"
// atype = anchor type, "box" for bounding box corners and sides, "perim" for the squircle corners // atype = anchor type, "box" for bounding box corners and sides, "perim" for the squircle corners
// $fn = Number of points. The special variables `$fs` and `$fa` are ignored. If set, `$fn` must be 12 or greater, and is rounded to the nearest multiple of 4. Points are generated so they are more dense around sharper curves. Default if not set: 48 // $fn = Number of points. The special variables `$fs` and `$fa` are ignored. If set, `$fn` must be 12 or greater, and is rounded to the nearest multiple of 4. Points are generated non-uniformly around the squircle so they are more dense at sharper curves. Default if not set: 40
// Examples(2D): // Examples(2D):
// squircle(size=50, squareness=0.4); // squircle(squareness=0.4, size=50);
// squircle([80,60], 0.7, $fn=64); // squircle(0.8, [80,60], $fn=64);
// Examples(2D): Ten increments of squareness parameter for a superellipse squircle // Examples(2D): Ten increments of squareness parameter for a superellipse squircle
// for(sq=[0:0.1:1]) // for(sq=[0:0.1:1])
// stroke(squircle(100, sq, style="superellipse", $fn=128), closed=true, width=0.5); // stroke(squircle(sq, 100, style="superellipse", $fn=128), closed=true, width=0.5);
// Examples(2D): Standard vector anchors are based on the bounding box // Examples(2D): Standard vector anchors are based on the bounding box
// squircle(50, 0.6) show_anchors(); // squircle(0.6, 50) show_anchors();
// Examples(2D): Perimeter anchors, anchoring at bottom left and spinning 20° // Examples(2D): Perimeter anchors, anchoring at bottom left and spinning 20°
// squircle([60,40], 0.5, anchor=(BOTTOM+LEFT), atype="perim", spin=20) // squircle(0.5, [60,40], anchor=(BOTTOM+LEFT), atype="perim", spin=20)
// show_anchors(); // show_anchors();
module squircle(size, squareness=0.5, style="fg", atype="box", anchor=CENTER, spin=0) { module squircle(squareness=0.5, size=[1,1], style="fg", atype="box", anchor=CENTER, spin=0) {
check = assert(squareness >= 0 && squareness <= 1); check = assert(squareness >= 0 && squareness <= 1);
anchorchk = assert(in_list(atype, ["box", "perim"])); anchorchk = assert(in_list(atype, ["box", "perim"]));
size = is_num(size) ? [size,size] : point2d(size); size = is_num(size) ? [size,size] : point2d(size);
assert(all_positive(size), "All components of size must be positive."); assert(all_positive(size), "All components of size must be positive.");
path = squircle(size, squareness, style, atype, _module_call=true);
if (atype == "box") { if (atype == "box") {
attachable(anchor, spin, two_d=true, size=size, extent=false) { path = squircle(squareness, size, style);
attachable(anchor, spin, two_d=true, size=size) {
polygon(path); polygon(path);
children(); children();
} }
} else { // atype=="perim" } else { // atype=="perim"
attachable(anchor, spin, two_d=true, extent=true, path=path) { override_path = squircle(squareness, size, style, atype, _return_override=true);
polygon(path); attachable(anchor, spin, two_d=true, size=size, extent=false, override=override_path[1]) {
polygon(override_path[0]);
children(); children();
} }
} }
} }
function squircle(size, squareness=0.5, style="fg", atype="box", anchor=CENTER, spin=0, _module_call=false) = function squircle(squareness=0.5, size=[1,1], style="fg", atype="box", anchor=CENTER, spin=0, _return_override=false) =
assert(squareness >= 0 && squareness <= 1) assert(squareness >= 0 && squareness <= 1)
assert(is_num(size) || is_vector(size,2)) assert(is_num(size) || is_vector(size,2))
assert(in_list(atype, ["box", "perim"])) assert(in_list(atype, ["box", "perim"]))
let( let(
size = is_num(size) ? [size,size] : point2d(size), path =
path = style == "fg" ? _squircle_fg(size, squareness) style == "fg" ? _squircle_fg(squareness, size)
: style == "superellipse" ? _squircle_se(size, squareness) : style == "superellipse" ? _squircle_se(squareness, size)
: assert(false, "Style must be \"fg\" or \"superellipse\""), : assert(false, "Style must be \"fg\" or \"superellipse\""),
) reorient(anchor, spin, two_d=true, size=atype=="box"?size:undef, path=_module_call?undef:path, p=path, extent=true); size = is_num(size) ? [size,size] : point2d(size),
a = 0.5 * size[0],
b = 0.5 * size[1],
override = atype == "box" ? undef
: let(
sn = style=="fg" ? _linearize_squareness(squareness)
: _squircle_se_exponent(squareness),
derivq1 = style=="fg" ? // 1+derivative of squircle in first quadrant
function (x) let(s2=sn*sn, a2=a*a, b2=b*b, x2=x*x, denom=a2-s2*x2) a2*b*(s2-1)*x/(denom*denom*sqrt((a2-x2)/denom)) + 1
: function (x) let(n=sn) 1 - (b/a)*((a/x)^n - 1)^(1/n-1),
xc = root_find(derivq1, 0.01, a-0.01), // find where slope=-1
yc = style=="fg" ?
let(s2=sn*sn, a2=a*a, b2=b*b, x2=xc*xc) sqrt(b2*(a2-x2)/(a2-s2*x2))
: b*(1-(xc/a)^sn)^(1/sn),
corners = [[xc,yc], [-xc,yc], [-xc,-yc], [xc,-yc]],
anchorpos = [[1,1],[-1,1],[-1,-1],[1,-1]]
) [ for(i=[0:3]) [anchorpos[i], [corners[i]]] ]
) _return_override
? [reorient(anchor, spin, two_d=true, size=size, p=path, extent=false, override=override), override]
: reorient(anchor, spin, two_d=true, size=size, p=path, extent=false, override=override);
/* FG squircle functions */ /* FG squircle functions */
function _squircle_fg(size, squareness) = [ function _squircle_fg(squareness, size) = [
let( let(
sq = _linearize_squareness(squareness), sq = _linearize_squareness(squareness),
size = is_num(size) ? [size,size] : point2d(size), size = is_num(size) ? [size,size] : point2d(size),
aspect = size[1] / size[0], aspect = size[1] / size[0],
r = 0.5 * size[0], r = 0.5 * size[0],
astep = $fn>=12 ? 90/round($fn/4) : 360/48 astep = $fn>=12 ? 90/round($fn/4) : 9
) for(a=[360:-astep:0.01]) let( ) for(a=[360:-astep:0.01]) let(
theta = a + sq * sin(4*a) * 30/PI, // tighter angle steps at corners theta = a + sq * sin(4*a) * 30/PI, // tighter angle steps at corners
p = squircle_radius_fg(sq, r, theta) p = squircle_radius_fg(sq, r, theta)
@ -2084,13 +2104,13 @@ function _linearize_squareness(s) =
/* Superellipse squircle functions */ /* Superellipse squircle functions */
function _squircle_se(size, squareness) = [ function _squircle_se(squareness, size) = [
let( let(
n = _squircle_se_exponent(squareness), n = _squircle_se_exponent(squareness),
size = is_num(size) ? [size,size] : point2d(size), size = is_num(size) ? [size,size] : point2d(size),
ra = 0.5*size[0], ra = 0.5*size[0],
rb = 0.5*size[1], rb = 0.5*size[1],
astep = $fn>=12 ? 90/round($fn/4) : 360/48, astep = $fn>=12 ? 90/round($fn/4) : 9,
fgsq = _linearize_squareness(min(0.998,squareness)) // works well for distributing theta fgsq = _linearize_squareness(min(0.998,squareness)) // works well for distributing theta
) for(a=[360:-astep:0.01]) let( ) for(a=[360:-astep:0.01]) let(
theta = a + fgsq*sin(4*a)*30/PI, // tighter angle steps at corners theta = a + fgsq*sin(4*a)*30/PI, // tighter angle steps at corners

View file

@ -1355,9 +1355,9 @@ module frame_map(x,y,z,p,reverse=false)
// Function&Module: skew() // Function&Module: skew()
// //
// Synopsis: Skews (or shears) children along various axes. // Synopsis: Skews children along various axes.
// SynTags: Trans, Path, VNF, Mat // SynTags: Trans, Path, VNF, Mat
// Topics: Affine, Matrices, Transforms, Skewing, Shearing // Topics: Affine, Matrices, Transforms, Skewing
// See Also: move(), rot(), scale() // See Also: move(), rot(), scale()
// //
// Usage: As Module // Usage: As Module
@ -1368,7 +1368,7 @@ module frame_map(x,y,z,p,reverse=false)
// mat = skew([sxy=]|[axy=], [sxz=]|[axz=], [syx=]|[ayx=], [syz=]|[ayz=], [szx=]|[azx=], [szy=]|[azy=]); // mat = skew([sxy=]|[axy=], [sxz=]|[axz=], [syx=]|[ayx=], [syz=]|[ayz=], [szx=]|[azx=], [szy=]|[azy=]);
// //
// Description: // Description:
// Skews geometry by the given skew factors. Skewing is also referred to as shearing. // Skews geometry by the given skew factors.
// * Called as the built-in module, skews all children. // * Called as the built-in module, skews all children.
// * Called as a function with a point in the `p` argument, returns the skewed point. // * Called as a function with a point in the `p` argument, returns the skewed point.
// * Called as a function with a list of points in the `p` argument, returns the list of skewed points. // * Called as a function with a list of points in the `p` argument, returns the list of skewed points.
@ -1572,48 +1572,4 @@ function _apply(transform,points) =
"), data of dimension ",datadim)); "), data of dimension ",datadim));
// Section: Saving and restoring
$transform = IDENT;
module translate(v)
{
$transform = $transform * (is_vector(v) && (len(v)==2 || len(v)==3) ? affine3d_translate(point3d(v)) : IDENT);
_translate(v) children();
}
module rotate(a,v)
{
rot3 = is_finite(a) && is_vector(v) && (len(v)==2 || len(v)==3) ? affine3d_rot_by_axis(v,a)
: is_finite(a) ? affine3d_zrot(a)
: same_shape(a,[0]) ? affine3d_xrot(a.x)
: same_shape(a,[0,0]) ? affine3d_yrot(a.y)*affine3d_xrot(a.x)
: same_shape(a,[0,0,0])? affine3d_zrot(a.z)*affine3d_yrot(a.y)*affine3d_xrot(a.x)
: IDENT;
$transform = $transform * rot3;
_rotate(a=a,v=v) children();
}
module scale(v)
{
s3 = is_finite(v) ? affine3d_scale([v,v,v])
: is_vector(v) ? affine3d_scale(v)
: IDENT;
$transform = $transform * s3;
_scale(v) children();
}
module multmatrix(m)
{
m3 = !is_matrix(m) ? IDENT
: len(m)>0 && len(m)<=4 && len(m[0])>0 && len(m[0])<=4 ? submatrix_set(IDENT, m)
: IDENT;
$transform = $transform * m3;
_multmatrix(m) children();
}
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -343,26 +343,6 @@ function vector_bisect(v1,v2) =
) v3; ) v3;
// Function: vector_perp()
// Synopsis: Returns component of a vector perpendicular to a second vector
// Topics: Vectors, Math
// Usage:
// perp = vector_perp(v,w);
// Description:
// Returns the component of vector w that is perpendicular to vector v. Vectors must have the same length.
// Arguments:
// v = reference vector
// w = vector whose perpendicular component is returned
// Example(2D): We extract the component of the red vector that is perpendicular to the yellow vector. That component appears in blue.
// v = [12,6];
// w = [13,22];
// stroke([[0,0],v],endcap2="arrow2");
// stroke([[0,0],w],endcap2="arrow2",color="red");
// stroke([[0,0],vector_perp(v,w)], endcap2="arrow2", color="blue");
function vector_perp(v,w) =
assert(is_vector(v) && is_vector(w) && len(v)==len(w), "Invalid or mismatched inputs")
w - w*v*v/(v*v);
// Section: Vector Searching // Section: Vector Searching