Merge pull request #1394 from adrianVmariano/master

path sweep end anchors
This commit is contained in:
Revar Desmera 2024-02-25 11:49:01 -08:00 committed by GitHub
commit af59a6c4a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 147 additions and 50 deletions

View file

@ -2764,17 +2764,47 @@ function reorient(
// See Also: reorient(), attachable()
// Usage:
// a = named_anchor(name, pos, [orient], [spin]);
// a = named_anchor(name, [pos], rot=, [flip=]);
// Description:
// Creates an anchor data structure. For a step-by-step explanation of attachments,
// Creates an anchor data structure. You can specify the position, orient direction and spin directly.
// Alternatively for the 3D case you can give a 4x4 rotation matrix which can specify the orient and spin, and optionally
// the position, using a translation component of the matrix. If you specify `pos` along with `rot` then the position you
// give overrides any translation included in `rot`. For a step-by-step explanation of attachments,
// see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments:
// name = The string name of the anchor. Lowercase. Words separated by single dashes. No spaces.
// pos = The [X,Y,Z] position of the anchor.
// orient = A vector pointing in the direction parts should project from the anchor position. Default: UP
// spin = If needed, the angle to rotate the part around the direction vector. Default: 0
function named_anchor(name, pos, orient=UP, spin=0) = [name, pos, orient, spin];
// ---
// rot = A 4x4 rotations matrix, which may include a translation
// flip = If true, flip the anchor the opposite direction. Default: false
function named_anchor(name, pos, orient, spin, rot, flip) =
assert(num_defined([orient,spin])==0 || num_defined([rot,flip])==0, "Cannot mix orient or spin with rot or flip")
assert(num_defined([pos,rot])>0, "Must give pos or rot")
is_undef(rot) ? [name, pos, default(orient,UP), default(spin,0)]
:
let(
flip = default(flip,false),
pos = default(pos,apply(rot,CTR)),
rotpart = _force_rot(rot),
dummy = assert(approx(det4(rotpart),1), "Input rotation is not a rotation matrix"),
dir = flip ? apply(rotpart,DOWN)
: apply(rotpart,UP),
rot = flip? affine3d_rot_by_axis(apply(rotpart,BACK),180)*rot
: rot,
decode=rot_decode(rot(to=UP,from=dir)*_force_rot(rot)),
spin = decode[0]*sign(decode[1].z)
)
[name, pos, dir, spin];
function _force_rot(T) =
[for(i=[0:3])
[for(j=[0:3]) j<3 ? T[i][j] :
i==3 ? 1
: 0]];
// Function: attach_geom()
// Synopsis: Returns the internal geometry description of an attachable object.
// Topics: Attachments
@ -3202,7 +3232,6 @@ function _attach_transform(anchor, spin, orient, geom, p) =
assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient))
let(
anchor = default(anchor, CENTER),
spin = default(spin, 0),
orient = default(orient, UP),
two_d = _attach_geom_2d(geom),
@ -3210,12 +3239,13 @@ function _attach_transform(anchor, spin, orient, geom, p) =
let(
anch = _find_anchor($attach_to, geom),
pos = anch[1]
) two_d? (
assert(two_d && is_num(spin))
affine3d_zrot(spin) *
rot(to=FWD, from=point3d(anch[2])) *
affine3d_translate(point3d(-pos))
) : (
)
two_d?
assert(is_num(spin))
affine3d_zrot(spin)
* rot(to=FWD, from=point3d(anch[2]))
* affine3d_translate(point3d(-pos))
:
assert(is_num(spin) || is_vector(spin,3))
let(
ang = vector_angle(anch[2], DOWN),
@ -3223,40 +3253,33 @@ function _attach_transform(anchor, spin, orient, geom, p) =
ang2 = (anch[2]==UP || anch[2]==DOWN)? 0 : 180-anch[3],
axis2 = rot(p=axis,[0,0,ang2])
)
affine3d_rot_by_axis(axis2,ang) * (
is_num(spin)? affine3d_zrot(ang2+spin) : (
affine3d_zrot(spin.z) *
affine3d_yrot(spin.y) *
affine3d_xrot(spin.x) *
affine3d_zrot(ang2)
)
) * affine3d_translate(point3d(-pos))
)
affine3d_rot_by_axis(axis2,ang)
* (is_num(spin)? affine3d_zrot(ang2+spin)
: affine3d_zrot(spin.z) * affine3d_yrot(spin.y) * affine3d_xrot(spin.x)
* affine3d_zrot(ang2))
* affine3d_translate(point3d(-pos))
) : (
let(
pos = _find_anchor(anchor, geom)[1]
) two_d? (
assert(two_d && is_num(spin))
affine3d_zrot(spin) *
affine3d_translate(point3d(-pos))
) : (
)
two_d?
assert(is_num(spin))
affine3d_zrot(spin) * affine3d_translate(point3d(-pos))
:
assert(is_num(spin) || is_vector(spin,3))
let(
axis = vector_axis(UP,orient),
ang = vector_angle(UP,orient)
)
affine3d_rot_by_axis(axis,ang) * (
is_num(spin)? affine3d_zrot(spin) : (
affine3d_zrot(spin.z) *
affine3d_yrot(spin.y) *
affine3d_xrot(spin.x)
)
) * affine3d_translate(point3d(-pos))
affine3d_rot_by_axis(axis,ang)
* ( is_num(spin)? affine3d_zrot(spin)
: affine3d_zrot(spin.z) * affine3d_yrot(spin.y) * affine3d_xrot(spin.x))
* affine3d_translate(point3d(-pos))
)
)
) is_undef(p)? m :
is_vnf(p)? [(p==EMPTY_VNF? p : apply(m, p[0])), p[1]] :
apply(m, p);
is_undef(p)? m
: is_vnf(p) && p==EMPTY_VNF? p
: apply(m, p);
function _get_cp(geom) =

View file

@ -956,12 +956,12 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
// path = [[0,0],[6,2],[9,7],[8,10]];
// xdistribute(spacing=10){
// offset_stroke(path, width = 2);
// offset_stroke(path, start="round", end="round", width = 2);
// offset_stroke(path, start="round", end="round", width = 2, $fn=32);
// offset_stroke(path, start="pointed", end="pointed", width = 2);
// }
// fwd(10) xdistribute(spacing=10){
// offset_stroke(arc, width = 2);
// offset_stroke(arc, start="round", end="round", width = 2);
// offset_stroke(arc, start="round", end="round", width = 2, $fn=32);
// offset_stroke(arc, start="pointed", end="pointed", width = 2);
// }
// Example(2D): The effect of the `rounded` and `chamfer` options is most evident at sharp corners. This only affects the middle of the path, not the ends.

View file

@ -1482,9 +1482,17 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals
// orient = Vector to rotate top towards after spin
// atype = Select "hull" or "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"
// Side Effects:
// `$transforms` is set to the array of transformation matrices that define the swept object.
// `$scales` is set to the array of scales that were applied at each point to create the swept object.
// Anchor Types:
// "hull" = Anchors to the virtual convex hull of the shape.
// "intersect" = Anchors to the surface of the shape.
// Extra Anchors:
// start = When `closed==false`, the origin point of the shape, on the starting face of the object
// end = When `closed==false`, the origin point of the shape, on the ending face of the object
// start-centroid = When `closed==false`, the centroid of the shape, on the starting face of the object
// end-centroid = When `closed==false`, the centroid of the shape, on the ending face of the object
// Example(NoScales): A simple sweep of a square along a sine wave:
// path = [for(theta=[-180:5:180]) [theta/10, 10*sin(theta)]];
// sq = square(6,center=true);
@ -1759,39 +1767,74 @@ module spiral_sweep(poly, h, r, turns=1, taper, r1, r2, d, d1, d2, internal=fals
// path_sweep(left(.05,square([1.1,1])), curve, closed=true,
// method="manual", normal=UP);
// }
// Example(Med,NoScales,VPR=[78.1,0,43.2],VPT=[2.18042,-0.485127,1.90371],VPD=74.4017): The "start" and "end" anchors are located at the origin point of the swept shape.
// shape = back_half(right_half(star(n=5,id=5,od=10)),y=-1);
// path = arc(angle=[0,180],d=30);
// path_sweep(shape,path,method="natural"){
// attach(["start","end"]) anchor_arrow(s=5);
// }
// Example(Med,NoScales,VPR=[78.1,0,43.2],VPT=[2.18042,-0.485127,1.90371],VPD=74.4017): The "start" and "end" anchors are located at the origin point of the swept shape.
// shape = back_half(right_half(star(n=5,id=5,od=10)),y=-1);
// path = arc(angle=[0,180],d=30);
// path_sweep(shape,path,method="natural"){
// attach(["start-centroid","end-centroid"]) anchor_arrow(s=5);
// }
// Example(Med,NoScales,VPR=[78.1,0,43.2],VPT=[2.18042,-0.485127,1.90371],VPD=74.4017): Note that the "start" anchors are backwards compared to the direction of the sweep, so you have to attach the TOP to align the shape with its ends.
// shape = back_half(right_half(star(n=5,id=5,od=10)),y=-1)[0];
// path = arc(angle=[0,180],d=30);
// path_sweep(shape,path,method="natural",scale=[1,1.5])
// recolor("red"){
// attach("start",TOP) stroke([path3d(shape)],width=.5);
// attach("end") stroke([path3d(yscale(1.5,shape))],width=.5);
// }
module path_sweep(shape, path, method="incremental", normal, closed, twist=0, twist_by_length=true, scale=1, scale_by_length=true,
symmetry=1, last_normal, tangent, uniform=true, relaxed=false, caps, style="min_edge", convexity=10,
anchor="origin",cp="centroid",spin=0, orient=UP, atype="hull",profiles=false,width=1)
{
dummy = assert(is_region(shape) || is_path(shape,2), "shape must be a 2D path or region");
vnf = path_sweep(shape, path, method, normal, closed, twist, twist_by_length, scale, scale_by_length,
symmetry, last_normal, tangent, uniform, relaxed, caps, style);
if (profiles){
dummy = assert(is_region(shape) || is_path(shape,2), "shape must be a 2D path or region")
assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\"");
tran = path_sweep(shape, path, method, normal, closed, twist, twist_by_length, scale, scale_by_length,
symmetry, last_normal, tangent, uniform, relaxed,transforms=true);
trans_scale = path_sweep(shape, path, method, normal, closed, twist, twist_by_length, scale, scale_by_length,
symmetry, last_normal, tangent, uniform, relaxed, caps, style, transforms=true,_return_scales=true);
transforms = trans_scale[0];
scales = trans_scale[1];
firstscale = is_num(scales[0]) ? 1/scales[0] : [1/scales[0].x, 1/scales[0].y];
lastscale = is_num(last(scales)) ? 1/last(scales) : [1/last(scales).x, 1/last(scales).y];
vnf = sweep(is_path(shape)?clockwise_polygon(shape):shape, transforms, closed=false, caps=caps,style=style);
shapecent = point3d(centroid(shape));
$transforms = transforms;
$scales = scales;
anchors = closed ? []
:
[
named_anchor("start", rot=transforms[0]*scale(firstscale), flip=true),
named_anchor("end", rot=last(transforms)*scale(lastscale)),
named_anchor("start-centroid", rot=transforms[0]*move(shapecent)*scale(firstscale), flip=true),
named_anchor("end-centroid", rot=last(transforms)*move(shapecent)*scale(lastscale))
];
if (profiles){
rshape = is_path(shape) ? [path3d(shape)]
: [for(s=shape) path3d(s)];
attachable(anchor,spin,orient, vnf=vnf, extent=atype=="hull", cp=cp) {
for(T=tran) stroke([for(part=rshape)apply(T,part)],width=width);
attachable(anchor,spin,orient, vnf=vnf, extent=atype=="hull", cp=cp, anchors=anchors) {
for(T=transforms) stroke([for(part=rshape)apply(T,part)],width=width);
children();
}
}
else
vnf_polyhedron(vnf,convexity=convexity,anchor=anchor, spin=spin, orient=orient, atype=atype, cp=cp)
attachable(anchor,spin,orient,vnf=vnf,extent=atype=="hull", cp=cp,anchors=anchors){
vnf_polyhedron(vnf,convexity=convexity);
children();
}
}
function path_sweep(shape, path, method="incremental", normal, closed, twist=0, twist_by_length=true, scale=1, scale_by_length=true,
symmetry=1, last_normal, tangent, uniform=true, relaxed=false, caps, style="min_edge", transforms=false,
anchor="origin",cp="centroid",spin=0, orient=UP, atype="hull") =
anchor="origin",cp="centroid",spin=0, orient=UP, atype="hull",_return_scales=false) =
is_1region(path) ? path_sweep(shape=shape,path=path[0], method=method, normal=normal, closed=default(closed,true),
twist=twist, scale=scale, scale_by_length=scale_by_length, twist_by_length=twist_by_length, symmetry=symmetry, last_normal=last_normal,
tangent=tangent, uniform=uniform, relaxed=relaxed, caps=caps, style=style, transforms=transforms,
anchor=anchor, cp=cp, spin=spin, orient=orient, atype=atype) :
anchor=anchor, cp=cp, spin=spin, orient=orient, atype=atype, _return_scales=_return_scales) :
let(closed=default(closed,false))
assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\"")
assert(!closed || twist % (360/symmetry)==0, str("For a closed sweep, twist must be a multiple of 360/symmetry = ",360/symmetry))
@ -1943,7 +1986,9 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0,
apply(transform_list[L], rshape)),
dummy = ends_match ? 0 : echo("WARNING: ***** The points do not match when closing the model in path_sweep() *****")
)
transforms ? transform_list
transforms && _return_scales
? [transform_list,scale]
: transforms ? transform_list
: sweep(is_path(shape)?clockwise_polygon(shape):shape, transform_list, closed=false, caps=fullcaps,style=style,
anchor=anchor,cp=cp,spin=spin,orient=orient,atype=atype);

View file

@ -1242,7 +1242,36 @@ create using `linear_extrude()` or `rotate_extrude()`.
To make a shape attachable, you just need to wrap it with an `attachable()` module with a
basic description of the shape's geometry. By default, the shape is expected to be centered
at the origin. The `attachable()` module expects exactly two children. The first will be
the shape to make attachable, and the second will be `children()`, literally.
the shape to make attachable, and the second will be `children()`,
literally.
### Pass-through Attachables
The simplest way to make your own attachable module is to simply pass
through to a pre-existing attachable submodule. This could be
appropriate if you want to rename a module, or if the anchors of an
existing module are suited to (or good enough for) your object. In
order for your attachable module to work properly you need to accept
the `anchor`, `spin` and `orient` parameters, give them suitable
defaults, and pass them to the attachable submodule. Don't forget to
pass the children to the attachable submodule as well, or your new
module will ignore its children.
```openscad-3D
include <BOSL2/std.scad>
module cutcube(anchor=CENTER,spin=0,orient=UP)
{
tag_scope(){
diff()
cuboid(15, rounding=2, anchor=anchor,spin=spin,orient=orient){
tag("remove")attach(TOP)cuboid(5);
children();
}
}
}
diff()
cutcube()
tag("remove")attach(RIGHT) cyl(d=2,h=8);
```
### Prismoidal/Cuboidal Attachables
To make a cuboidal or prismoidal shape attachable, you use the `size`, `size2`, and `offset`