Compare commits

...

9 commits

Author SHA1 Message Date
Nick Timkovich
12ae36441f
Merge bb6325384a into 0fc47cf3c0 2025-11-17 00:38:35 +09:00
adrianVmariano
0fc47cf3c0
Merge pull request #1843 from adrianVmariano/master
doc fix
2025-11-14 23:50:00 -05:00
Adrian Mariano
0637b8731b doc fix 2025-11-14 20:09:36 -05:00
adrianVmariano
ad1437f8af
Merge pull request #1842 from adrianVmariano/master
doc fixes and better rounding for attach_prims()
2025-11-14 19:52:05 -05:00
Adrian Mariano
4a04048dd4 doc fixes and better rounding for attach_prims() 2025-11-14 18:55:53 -05:00
adrianVmariano
ded2c11456
Merge pull request #1841 from adrianVmariano/master
add spin to attach prism(), doc fixes
2025-11-14 16:47:21 -05:00
Adrian Mariano
c0981763e7 doc fixes, add spin to attach_prism() 2025-11-14 06:11:18 -05:00
Nick Timkovich
bb6325384a
Update README.md 2025-10-12 12:32:25 -05:00
Nick Timkovich
2ea6e2a4dd
Add 'get_bosl2.sh' cloner/updater. 2025-10-12 12:24:38 -05:00
5 changed files with 165 additions and 60 deletions

View file

@ -32,7 +32,12 @@ The BOSL2 library is an enormous library that provides many different kinds of c
You can find the full BOSL2 library documentation at: https://github.com/BelfrySCAD/BOSL2/wiki
## Installation
## Automated Install/Update with Git
`curl -sSL https://raw.githubusercontent.com/BelfrySCAD/BOSL2/refs/heads/master/get_bosl2.sh | bash`
## Manual Installation
1. Download the .zip or .tar.gz release file for this library. Currently you should be able to find this at https://github.com/BelfrySCAD/BOSL2/archive/refs/heads/master.zip
2. Unpack it. Make sure that you unpack the whole file structure. Some zipfile unpackers call this option "Use folder names". It should create either a `BOSL-v2.0` or `BOSL2-master` directory with the library files within it. You should see "examples", "scripts", "tests", and other subdirectories.

View file

@ -779,22 +779,20 @@ function _make_anchor_legal(anchor,geom) =
// do `attach(RIGHT,BOT)` this puts the bottom of the child onto the right anchor of the parent.
// When an object is attached to the top or bottom, its BACK direction remains pointing BACK.
// When an object is attached to one of the other anchors its FRONT is pointed DOWN and its
// BACK pointed UP. You can change this using the `spin=` argument to attach(). Note that this spin
// BACK pointed UP. You can change this using the `spin=` argument to attach(). This spin
// rotates around the attachment vector and is not the same as the spin argument to the child, which
// will usually rotate around some other direction that may be hard to predict. For 2D objects you cannot
// give spin because it is not possible to spin around the attachment vector; spinning the object around the Z axis
// would change the child orientation so that the anchors are no longer parallel. Furthermore, any spin
// will usually rotate around some other direction that may be hard to predict. Any spin
// parameter you give to the child is ignored so that the attachment condition of parallel anchors is preserved.
// For 2D objects you cannot give spin because it is not possible to spin around the attachment vector;
// spinning the object around the Z axis would change the child orientation so that the anchors are no longer parallel.
// .
// As with {{align()}} you can use the `align=` parameter to align the child to an edge or corner of the
// face where that child is attached. For example, `attach(TOP,BOT,align=RIGHT)` would stand the child
// up on the top while aligning it with the right edge of the top face, and `attach(RIGHT,BOT,align=TOP)`, which
// stand the object on the right face while aligning with the top edge. If you apply spin using the
// argument to `attach()`, then it is taken into account for the alignment. However, if you apply spin with
// argument to `attach()`, then it is taken into account for the alignment. However, if you apply spin as
// a parameter to the child, it is **not** taken into account. The special spin value "align"
// spins the child so that the child's BACK direction is pointed toward the aligned edge on the parent.
// Note that spin is not permitted for
// 2D objects because it would change the child orientation so that the anchors are no longer parallel.
// When you use `align=` you can also adjust the position using `inset=`, which shifts the child
// away from the edge or corner it is aligned to.
// .
@ -845,7 +843,7 @@ function _make_anchor_legal(anchor,geom) =
// overlap = Amount to sink child into the parent. Equivalent to `down(X)` after the attach. This defaults to the value in `$overlap`, which is `0` by default.
// inside = If `child` is given you can set `inside=true` to attach the child to the inside of the parent for diff() operations. Default: false
// shiftout = Shift an inside object outward so that it overlaps all the aligned faces. Default: 0
// spin = Amount to rotate the parent around the axis of the parent anchor. Can set to "align" to align the child's BACK with the parent aligned edge. (Permitted only in 3D.)
// spin = Angle to rotate the child around the axis of the parent anchor. Can set to "align" to align the child's BACK with the parent aligned edge. (Permitted only in 3D.)
// Side Effects:
// `$anchor` set to the parent anchor value used for the child.
// `$align` set to the align value used for the child.

62
get_bosl2.sh Normal file
View file

@ -0,0 +1,62 @@
#!/bin/bash
set -o errexit
set -o nounset
repo_url="https://github.com/BelfrySCAD/BOSL2.git"
lib_target_dirname="BOSL2"
# determine lib dir
if command -v openscad &> /dev/null 2>&1; then
libdir="$(openscad --info 2>/dev/null | grep "OpenSCAD library path:" -A1 | tail -n1 | xargs)"
if [ -z "$libdir" ]; then
echo "ABORT: Could not determine OpenSCAD library path from 'openscad --info'"
exit 1
fi
echo "OpenSCAD library path determined from 'openscad --info': $libdir"
if [ ! -d "$libdir" ]; then
echo "ABORT: Library folder does not exist."
exit 1
fi
if [ ! -x "$libdir" ] || [ ! -w "$libdir" ]; then
echo "ABORT: Library folder is not accessible (write+execute)."
exit 1
fi
else
echo "Could not find 'openscad' command. Guessing library path based on OS."
uname_out="$(uname -s)"
case "${uname_out}" in
Linux*) machine="Linux";;
Darwin*) machine="Mac";;
*) machine="UNKNOWN:${uname_out}"
esac
if [ "$machine" == "Mac" ]; then
libdir="$HOME/Documents/OpenSCAD/libraries"
elif [ "$machine" == "Linux" ]; then
libdir="$HOME/.local/share/OpenSCAD/libraries"
else
echo "WARNING: running on an unknown system: ${machine}."
libdir="$HOME/.local/share/OpenSCAD/libraries"
fi
if [ ! -d "$libdir" ]; then
echo "ABORT: Assumed OpenSCAD library folder '$libdir' does not exist"
exit 1
fi
fi
if ! command -v git &> /dev/null 2>&1; then
echo "ABORT: Git is missing. Please install git."
exit 1
fi
# clone or update
if [ -d "$libdir/$lib_target_dirname" ]; then
echo "Updating BOSL2 library in $libdir/$lib_target_dirname"
git -C "$libdir/$lib_target_dirname" pull
else
echo "New installation into $libdir/$lib_target_dirname"
git clone "$repo_url" "$libdir/$lib_target_dirname"
fi

View file

@ -46,6 +46,7 @@ function _inset_corner(corner, mask_angle, inset, excess, flat_top) =
// If called as a function, returns a 2D path of the outline of the mask shape.
// .
// The roundover can be specified by radius, diameter, height, cut, or joint length.
// .
// ![Types of Roundovers](images/rounding/figure_1_1.png)
// .
// If you need roundings to agree on edges of different mask_angle, e.g. to round the base of a prismoid, then you need all of the
@ -253,10 +254,12 @@ function mask2d_roundover(r, inset=0, mask_angle=90, excess=0.01, clip_angle, fl
// If called as a function, returns a 2D path of the outline of the mask shape.
// .
// The roundover can be specified by joint length or cut distance. (Radius is not meaningful for this type of mask.) You must also specify the
// continuous curvature smoothness parameter, `k`, which defaults to 0.5. This diagram shows a roundover for the default k value.
// continuous curvature smoothness parameter, `k`, which defaults to 0.5. This diagram shows a roundover for the default k value.
// .
// ![Types of Roundovers](images/rounding/figure_1_2.png)
// .
// With `k=0.75` the transition into the roundover is shorter and faster. The cut length is bigger for the same joint length.
// .
// ![Types of Roundovers](images/rounding/figure_1_3.png)
// .
// The diagrams above show symmetric roundovers, but you can also create asymmetric roundovers by giving a list of two values for `joint`. In this
@ -375,6 +378,7 @@ module mask2d_smooth(mask_angle, cut, joint, height, h, k=0.5, excess=.01, inset
// If called as a function, 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.
// The roundover can be specified by radius, diameter, height, cut, or joint length.
// .
// ![Types of Roundovers](images/rounding/figure_1_1.png)
// Arguments:
// r = Radius of the rounding.

View file

@ -2226,7 +2226,7 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
// value and the rounding is symmetric around each edge. However, you can specify a 2-vector for the joint distance to produce asymmetric
// rounding which is different on the two sides of the edge. This may be useful when one one edge in your polygon is much larger than another.
// For the top and bottom you can specify negative joint distances. If you give a scalar negative value, then the roundover flares
// outward. If you give a vector value then a negative value, then if `joint_top[0]` is negative the shape flares outward, but if
// outward. If you give a vector value then if `joint_top[0]` is negative the shape flares outward, but if
// `joint_top[1]` is negative, the shape flares upward. At least one value must be non-negative. The same rules apply for joint_bot.
// The joint_sides parameter must be entirely nonnegative.
// .
@ -2243,7 +2243,7 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
// can also use "intersect" to get the intersection anchors to the unrounded object. If you prefer anchors that respect the rounding
// then use "surf_hull" or "intersect_hull". Lastly, in the special case of a prism with four sides, you can use "prismoid" anchoring
// which attempts to assign standard prismoid anchors to the shape by assigning as RIGHT the face that is closest to the RIGHT direction,
// and defining the other anchors around the shape baesd on that choice.
// and defining the other anchors around the shape based on that choice.
// .
// Note that rounded_prism() is not well suited to rounding shapes that have already been rounded, or that have many points.
// It works best when the top and bottom are polygons with well-defined corners. When the polygons have been rounded already,
@ -4465,7 +4465,7 @@ function _prism_fillet_prism(name, basepoly, bot, top, d, k, N, overlap, uniform
// Get the object type from the specified geometry and anchor point
// Note that profile is needed just to find its dimensions for making a big enough edge profile
function _get_obj_type(ind,geom,anchor,prof,edger=0) =
function _get_obj_type(ind,geom,anchor,prof,edge_r,edge_joint,edge_k) =
geom[0]=="spheroid" ? "sphere"
: geom[0]=="conoid" ? let(
axis = geom[5],
@ -4491,8 +4491,9 @@ function _get_obj_type(ind,geom,anchor,prof,edger=0) =
x = y/tan(edge_angle/2),
corner=[[x,-y],[0,0], [x,y]]
)
round_corners(corner, r=edger, closed=false)
is_def(edge_r)? round_corners(corner, r=edge_r, closed=false)
: is_def(edge_joint) ? echo(jk=edge_joint,edge_k)round_corners(corner, method="smooth",joint=edge_joint, k=edge_k, closed=false)
: corner
: starts_with(geom[0], "extrusion") ?
anchor==UP || anchor==DOWN || starts_with(anchor,"face") ? "plane"
:
@ -4858,7 +4859,7 @@ module prism_connector(profile, desc1, anchor1, desc2, anchor2, shift1, shift2,
// Topics: Rounding, Extrusion, Sweep
// See Also: join_prism(), prism_connector()
// Usage:
// PARENT() attach_prism(profile, anchor, [fillet], [rounding], [l=/h=/length=/height=], [endpoint=], [T=], [shift=], [scale=], [inside=], [n=], [n_base=], [n_end=], [k=], [k_base=], [k_end=], [overlap=], [uniform=], [smooth_normals=], [debug=] ) CHILDREN;
// PARENT() attach_prism(profile, anchor, [fillet], [rounding], [l=/h=/length=/height=], [endpoint=], [T=], [shift=], [scale=], [inside=], [n=], [n_base=], [n_end=], [k=], [k_base=], [k_end=], [overlap=], [uniform=], [smooth_normals=], [edge_r=], [edge_joint=], [edge_k=], [debug=] ) CHILDREN;
// Description:
// Constructs a filleted prism that attaches to the specified anchor point of the parent object, with an optional rounded free end.
// This is an alternative interface to {{join_prism()}}. The `profile` parameter gives the cross section of the prism the module constructions.
@ -4878,7 +4879,7 @@ module prism_connector(profile, desc1, anchor1, desc2, anchor2, shift1, shift2,
// when the anchor point is not on the surface.
// .
// If you specify a length or height then he prism normally appears in the anchor direction, perpendicular to the parent object. You can
// adjust its angle by setting the transoformation. For example, `T=xrot(20)` will rotate the prism so it is a 20 deg angle. It will shift
// adjust its angle by setting the transformation. For example, `T=xrot(20)` will rotate the prism so it is a 20 deg angle. It will shift
// to the right as seen from above. The transformation applies in the anchored coordinate system where Z is perpendicular to the parent
// and Y points in the spin direction. When `T` is a rotation the face of the prism will be perpendicular to the prism axis.
// You can also specify the prism endpoint as a point in space, again in the anchored coordinate system. In this case you cannot give
@ -4900,17 +4901,28 @@ module prism_connector(profile, desc1, anchor1, desc2, anchor2, shift1, shift2,
// Normally {{join_prism()}} will issue an error in this situation. The `debug` parameter is passed through to {{join_prism()}} and
// tells that module to display invalid self-intersecting geometry to help you understand the problem.
// .
// When connecting to an edge, artifacts may occur at the corners where the prism doesn't meet the object in the ideal fashion.
// The `overlap` parameter creates an extension of the prism into the parent object. Unlike `overlap` for {{attach()}} it actually extends
// the prism rather than shifting it by the overlap. When connecting to curved parent objects, be sure the overlap is sufficient or you may
// create a hole in between the prism and its parent. When subtracting a prism to create a hole, insufficient overlap will leave parts of
// the parent object behind, blocking the hole.
// .
// When connecting to an unrounded edge, artifacts may occur at the corners where the prism doesn't meet the object in the ideal fashion.
// Adjsting the points on your prism profile so that a point falls close to the corner will achieve the best result, and make sure
// that `smooth_normals` is disabled (the default for edges) because it results in a completely incorrect fillet in this case.
// If you connect to an extrusion object, the default value for `smooth_normals` is true, which generally works better when
// for a uniformly sampled smooth object, but if your object has corners you may get better results by setting `smooth_normals=false`.
// For this reason, the default is false when connecting to an edge that is not rounded.
// .
// When connecting to a rounded edge, the edge geometry must be circular or a smooth bezier rounding. In the circular case
// you must provide the `edge_r` parameter to specify the radius of the circular rounding. In the latter case you
// must give `edge_joint` to specify the size of the rounding, and you may optionally provide `edge_k` as required. The default
// of `edge_k=0.5` matches the default for {{rounded_prism()}}.
// .
// The "end" named anchor is located at the end face of the prism and (unlike {{join_prism()}}) provides a normal anchor
// that is perpendicular to the end face of the prism.
// Arguments:
// profile = path giving cross section to extrude to create the connecting prism
// anchor = parent anchor where prism will be attached
// anchor = parent anchor where prism will be attached or a list of anchors
// fillet = fillet size. Default: 0
// rounding = end rounding of prism. Default: 0
// ---
@ -4920,7 +4932,10 @@ module prism_connector(profile, desc1, anchor1, desc2, anchor2, shift1, shift2,
// inside = if true attach the prism on the inside of the parent for diff() operations. Default: false
// shift = shift anchor point, a scalar for cylinders, extrusions, or edges, a 2-vector for faces, not permitted for spheres
// scale = scale the profile by this factor at the end. Default: 1
// spin = angle to rotate the prism around its axis before attaching it. Default: 0
// edge_r = when attaching to an edge, assume it has a circular rounding with this radius
// edge_joint = when attaching to an edge, assume it has a smooth bezier rounding with this joint length
// edge_k = when attaching to an edge with a bezier rounding, use this k parameter value. Default: 0.5 (matches rounded_prism)
// n = number of facets to use for fillets and roundings
// n_base = number of facets to use for fillets at the base
// n_end = number of facets to use for roundings at the end
@ -4929,7 +4944,7 @@ module prism_connector(profile, desc1, anchor1, desc2, anchor2, shift1, shift2,
// k_end = rounding curvature parameter for end. Default: 0.7
// uniform = set to false to get non-uniform filleting. Default: true
// overlap = the amount of overlap of the prism fillet into the parent object. Default: 1
// smooth_normals = controls whether normals are smoothed when the parent is a prism or edge; no effect otherwise. Default: false if object is an edge, true otherwise
// smooth_normals = controls whether normals are smoothed when the parent is a prism or edge; no effect otherwise. Default: false if object is an unrounded edge, true otherwise
// debug = pass-through to the {{join_prism()}} debug parameter. If true then various cases where the fillet self intersects will be displayed instead of creating an error. Default: false
// Named Anchors:
// "end" = End of the attached prism. (A normal anchor that is perpendicular to the end face of the prism.)
@ -4947,6 +4962,9 @@ module prism_connector(profile, desc1, anchor1, desc2, anchor2, shift1, shift2,
// Example(3D): Attaching to sphere with scaling of the prism
// sphere(d=20)
// attach_prism(circle(r=4,$fn=64), RIGHT+TOP+FWD, fillet=2, rounding=1.5, l=7, scale=.5);
// Example(3D): Here we've attached a rectangular prism to a cylinder. It makes a nice joint at the sides but the top doesn't look right. This is because {{rect()}} only provides four points, which is not sufficient to match the curvature of the cylinder. You need to resample with {{subdivide_path()}} or some other reampling method as shown in the next example.
// cyl(d=10,h=8)
// attach_prism(rect(4), FWD+RIGHT, 1/2, rounding=1/2, l=4, edge_r=2);
// Example(3D): Attaching to an extrusion. Here we used a rounded rectangle and resample it to ensure enough points to match the curve of the ellipse.
// $fn=128;
// rr = subdivide_path(rect(7,rounding=2), maxlen=.5);
@ -4968,14 +4986,28 @@ module prism_connector(profile, desc1, anchor1, desc2, anchor2, shift1, shift2,
// Example(3D): Adjusting prism angle with a transformation. The prism end is perpendicular to the prism axis.
// cuboid(20)
// attach_prism(circle(r=4,$fn=32), FWD, fillet=1, rounding=1.5, l=10, T=xrot(-20));
// Example(3D): Creating a hole
// Example(3D): Creating a hole. The default overlap is too small to fully clear out the hole.
// diff()
// cyl(d=20,h=22,$fn=128*2)
// attach_prism(circle(r=6,$fn=64), RIGHT+FWD, inside=true,
// fillet=2, rounding=3, l=8, overlap=2);
// Example(3D): Attaching with multiple anchor points
// cuboid(12)
// attach_prism(circle(r=3,$fn=32), [TOP, RIGHT+FWD],length=4,fillet=2);
// Example(3D): Attaching to a rounded cuboid edge (circular rounding)
// cuboid([12,12,15],rounding=2, $fn=32)
// attach_prism(circle(r=3,$fn=128),RIGHT+FWD, length=4, fillet=2, edge_r=2);
// Example(3D): Attaching the the edge on a {{rounded_prism()}}. The `$fn` value given to `attach_prism()` controls the number of facets used to model the curved edge that the prism mates to.
// joint = 3;
// rounded_prism(rect(12), rect(8), h=15, joint_sides=joint,atype="prismoid")
// attach_prism(circle(r=3,$fn=128),RIGHT+FWD, length=4, fillet=2, edge_joint=joint, $fn=32);
module attach_prism(profile, anchor, fillet=0, rounding=0, inside=false, l, length, h, height, endpoint, T=IDENT, shift=0, overlap=1,
n,n_base, n_end, k, k_base, k_end, uniform=true, smooth_normals, edge_r, debug=false, scale=1)
n,n_base, n_end, k, k_base, k_end, uniform=true, smooth_normals, edge_r, edge_joint, edge_k=0.5, debug=false, scale=1, spin=0)
{
length = one_defined([l, h, length, height],"l,h,length,height",dflt=undef);
profile = force_path(profile,"profile");
@ -4987,51 +5019,55 @@ module attach_prism(profile, anchor, fillet=0, rounding=0, inside=false, l, leng
assert(is_matrix(T,4,4), "T must be a 4x4 transformation matrix")
assert(is_undef(length) || all_positive([length]), "length/height must be a positive value")
assert(point3d(anchor)!=CTR, "CENTER anchor is not permitted")
;
edge_r=default(edge_r,0);
assert(num_defined([edge_r,edge_joint])<=1, "Cannot give both edge_r and edge_joint")
;
n_base = first_defined([n_base,n,15]);
n_end = first_defined([n_end,n,15]);
k_base = first_defined([k_base, k, 0.7]);
k_end = first_defined([k_end,k,0.7]);
anchor = is_string(anchor) ? anchor : point3d(anchor);
anchor_list = is_string(anchor) || is_vector(anchor) ? [anchor] : anchor;
type = _get_obj_type(undef,$parent_geom,anchor,profile,edge_r);
offset = in_list(type,["cyl","sphere"]) ? (inside?-1:1)*$parent_geom[1] : 0;
base_r = (inside?-1:1)*(in_list(type,["cyl","sphere"]) ? $parent_geom[1] : 1);
spin = -90;
base_edge = _is_geom_an_edge($parent_geom,anchor);
smooth_normals = default(smooth_normals, !base_edge);
shift = type=="sphere" ? assert(shift==0, "Cannot give a (nonzero) shift for attaching to a spherical object") [0,0]
: type=="cyl" ? assert(is_finite(shift), "Value shift for a cylindrical object must be a scalar") [0,shift]
: is_list(type) ? assert(is_finite(shift), "Value shift for an edge must be a scalar") [0,shift]
: assert(is_finite(shift) || is_vector(shift,2), "Value for shift for a planar face must be a scalar or 2-vector")
force_list(shift,2,0);
T = is_def(endpoint) ? move([endpoint.y-shift.y,-endpoint.x-shift.x])
: zrot(spin)*T;
mod_type = is_list(type) && inside ? zrot(180,type) : type;
anchors = [named_anchor("end", is_def(endpoint) ? yrot(inside?180:0,endpoint) : point3d(shift)+apply(yrot(inside?180:0)*zrot(-spin)*T,[0,0,(inside?1:1)*length]),
is_def(endpoint) ? (inside?-1:1)*UP : unit( apply(yrot(inside?180:0)*zrot(-spin)*T,[0,0,length])-apply(zrot(-spin)*T,CTR)),
inside && is_undef(endpoint) ? 180 : 0)
];
vnf= join_prism(zrot(spin,profile), mod_type, base_r=base_r,
l=is_def(endpoint)?endpoint.z:length,
prism_end_T=T,
base_fillet=fillet,
end_round=rounding,
base_overlap=overlap, uniform=uniform, smooth_normals=smooth_normals,
base_n=n_base, aux_n=n_end, base_k=k_base, aux_k=k_end,debug=debug,scale=scale);
attach(anchor)
default_tag("remove", inside)
attachable(vnf=vnf, anchors=anchors) {
translate(shift)
yrot(inside?180:0)
down(offset)
zrot(-spin)
vnf_polyhedron(vnf);
children();
}
for(anchor=anchor_list){
anchor = is_string(anchor) ? anchor : point3d(anchor);
type = _get_obj_type(undef,$parent_geom,anchor,profile,edge_r,edge_joint,edge_k);
offset = in_list(type,["cyl","sphere"]) ? (inside?-1:1)*$parent_geom[1] : 0;
base_r = (inside?-1:1)*(in_list(type,["cyl","sphere"]) ? $parent_geom[1] : 1);
spinfix = -90;
base_edge = _is_geom_an_edge($parent_geom,anchor);
smooth_normals = default(smooth_normals, !base_edge || default(edge_r,0)>0 || default(edge_joint,0)>0);
shift = type=="sphere" ? assert(shift==0, "Cannot give a (nonzero) shift for attaching to a spherical object") [0,0]
: type=="cyl" ? assert(is_finite(shift), "Value shift for a cylindrical object must be a scalar") [0,shift]
: is_list(type) ? assert(is_finite(shift), "Value shift for an edge must be a scalar") [0,shift]
: assert(is_finite(shift) || is_vector(shift,2), "Value for shift for a planar face must be a scalar or 2-vector")
force_list(shift,2,0);
T = is_def(endpoint) ? move([endpoint.y-shift.y,-endpoint.x-shift.x])
: zrot(spinfix)*T;
mod_type = is_list(type) && inside ? zrot(180,type) : type;
anchors = [named_anchor("end", is_def(endpoint) ? yrot(inside?180:0,endpoint) : point3d(shift)+apply(yrot(inside?180:0)*zrot(-spinfix)*T,[0,0,(inside?1:1)*length]),
is_def(endpoint) ? (inside?-1:1)*UP : unit( apply(yrot(inside?180:0)*zrot(-spinfix)*T,[0,0,length])-apply(zrot(-spinfix)*T,CTR)),
inside && is_undef(endpoint) ? 180 : 0)
];
vnf= join_prism(zrot(spin+spinfix,profile), mod_type, base_r=base_r,
l=is_def(endpoint)?endpoint.z:length,
prism_end_T=T,
base_fillet=fillet,
end_round=rounding,
base_overlap=overlap, uniform=uniform, smooth_normals=smooth_normals,
base_n=n_base, aux_n=n_end, base_k=k_base, aux_k=k_end,debug=debug,scale=scale);
attach(anchor)
default_tag("remove", inside)
attachable(vnf=vnf, anchors=anchors) {
translate(shift)
yrot(inside?180:0)
down(offset)
zrot(-spinfix)
vnf_polyhedron(vnf);
children();
}
}
}