Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Richard Milewski
2025-06-21 12:38:55 -07:00
18 changed files with 414 additions and 94 deletions

View File

@@ -15,7 +15,7 @@ Requires OpenSCAD 2021.01 or later.
The BOSL2 library is an enormous library that provides many different kinds of capabilities to simplify the development of models in OpenSCAD, and to make things possible that are difficult in native OpenSCAD. Some of the things BOSL2 provides are:
* **Attachments.** Unless you make models containing just one object, the attachments features can revolutionize your modeling. They let you position components of a model relative to other components so you don't have to keep track of the positions and orientations of parts of the model. You can instead place something on the TOP of something else, perhaps aligned to the RIGHT. For a full introduction to attachments, consult the [Attachments Tutorial.](https://github.com/BelfrySCAD/BOSL2/wiki/Tutorial-Attachments)
* **Attachments.** Unless you make models containing just one object, the attachments features can revolutionize your modeling. They let you position components of a model relative to other components so you don't have to keep track of the positions and orientations of parts of the model. You can instead place something on the TOP of something else, perhaps aligned to the RIGHT. For a full introduction to attachments, consult the [Attachments Tutorial.](https://github.com/BelfrySCAD/BOSL2/wiki/Tutorial-Attachment-Overview)
* **Rounding and filleting.** Rounding and filleting is hard in OpenSCAD. The library provides modules like [cuboid()](https://github.com/BelfrySCAD/BOSL2/wiki/shapes3d.scad#module-cuboid) to make a cube with any of the edges rounded, [offset_sweep()](https://github.com/BelfrySCAD/BOSL2/wiki/rounding.scad#functionmodule-offset_sweep) to round the ends of a linear extrusion, and [prism_connector()](https://github.com/BelfrySCAD/BOSL2/wiki/rounding.scad#module-prism_connector) which works with the attachments feature to create filleted prisms between a variety of objects, or holes through a single object with rounded edges at the ends. You can also use [edge_profile()](https://github.com/BelfrySCAD/BOSL2/wiki/attachments.scad#module-edge_profile) to apply a variety of different mask profiles to chosen edges of a cubic shape, or you can directly subtract 3d mask shapes from an edge of objects that are not cubes.
* **Shorthands.** The shorthands make your code a little shorter, and more importantly, they can make it significantly easier to read. Compare `up(z)` to `translate([0,0,z])`. The shorthands include operations for creating [copies of objects](https://github.com/BelfrySCAD/BOSL2/wiki/distributors.scad) and for applying [transformations](https://github.com/BelfrySCAD/BOSL2/wiki/transforms.scad) to objects, including [rot()](https://github.com/BelfrySCAD/BOSL2/wiki/transforms.scad#functionmodule-rot) which extends `rotate()` in some useful ways that are not easy to do directly.
* **Complex object support.** The [path_sweep()](https://github.com/BelfrySCAD/BOSL2/wiki/skin.scad#functionmodule-path_sweep) function/module takes a 2d polygon moves it through space along a path and sweeps out a 3d shape as it moves. You can link together a series of arbitrary polygons with [skin()](https://github.com/BelfrySCAD/BOSL2/wiki/skin.scad#functionmodule-skin) or [vnf_vertex_array().](https://github.com/BelfrySCAD/BOSL2/wiki/vnf.scad#functionmodule-vnf_vertex_array) Support for [beziers](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad) and [NURBS](https://github.com/BelfrySCAD/BOSL2/wiki/nurbs.scad) can help you construct the building blocks you need. [Metaballs](https://github.com/BelfrySCAD/BOSL2/wiki/isosurface.scad#functionmodule-metaballs) can create organic surfaces that blend shapes together.

View File

@@ -1046,6 +1046,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
// used when attachable() places the child
$anchor_override = all_zero(child_adjustment)? inside?child:undef
: child+child_adjustment;
reference = two_d? BACK : UP;
// inset_dir is the direction for insetting when alignment is in effect
inset_dir = is_undef(align) ? CTR
@@ -1058,7 +1059,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
spinaxis = two_d? UP : anchor_dir;
olap = - overlap * reference - inset*inset_dir + shiftout * (inset_dir + factor*reference);
olap = - overlap * reference - inset*inset_dir + shiftout * (inset_dir + factor*reference*($anchor_inside?-1:1));
if (norot || (approx(anchor_dir,reference) && anchor_spin==0))
translate(pos) rot(v=spinaxis,a=factor*spin) translate(olap) default_tag("remove",removetag) children();
else
@@ -3985,14 +3986,20 @@ function _find_anchor(anchor, geom)=
axy = point2d(anch),
bot = point3d(v_mul(point2d(size )/2, axy), -h/2),
top = point3d(v_mul(point2d(size2)/2, axy) + shift, h/2),
degenerate = sum(v_abs(point2d(anch)))==1 && (point2d(bot)==[0,0] || v_mul(point2d(size2)/2, axy)==[0,0]),
edge = top-bot,
other_edge = degenerate ? move(shift,mirror(axy, move(-shift,top))) - mirror(point3d(point2d(anch)), p=bot):CTR,
pos = point3d(cp) + lerp(bot,top,u) + offset,
// Find vectors of the faces involved in the anchor
facevecs =
[
if (anch.x!=0) unit(rot(from=UP, to=[edge.x,0,max(0.01,h)], p=[axy.x,0,0]), UP),
if (anch.y!=0) unit(rot(from=UP, to=[0,edge.y,max(0.01,h)], p=[0,axy.y,0]), UP),
if (anch.z!=0) unit([0,0,anch.z],UP)
if (anch.z!=0 && !degenerate) unit([0,0,anch.z],UP),
if (anch.z!=0 && degenerate && anch.y!=0)
unit(rot(from=UP, to=[0,other_edge.y,max(0.01,h)], p=[0,-axy.y,0]), UP),
if (anch.z!=0 && degenerate && anch.x!=0)
unit(rot(from=UP, to=[other_edge.x,0,max(0.01,h)], p=[-axy.x,0,0]), UP),
],
dir = anch==CENTER? UP
: len(facevecs)==1? unit(facevecs[0],UP)
@@ -4031,8 +4038,8 @@ function _find_anchor(anchor, geom)=
// with a correction for top/bottom (anchor.z).
// Otherwise use the standard BACK/UP definition
// The precomputed oang value seems to be wrong, at least when axis!=UP
spin = is_def(edgedir) && !approx(edgedir.z,0) ? _compute_spin(final_dir, edgedir * (edgedir*UP>0?1:-1))
spin = is_def(edgedir) && degenerate ? _compute_spin(final_dir, unit(((BACK+RIGHT)*edgedir)*edgedir))
: is_def(edgedir) && !approx(edgedir.z,0) ? _compute_spin(final_dir, edgedir * (edgedir*UP>0?1:-1))
: is_def(edgedir) ? _compute_spin(final_dir,
edgedir * (approx(unit(cross(UP,edgedir)),unit([final_dir.x,final_dir.y,0])*anchor.z) ? 1 : -1))
: _compute_spin(final_dir, final_dir==DOWN || final_dir==UP ? BACK : UP)
@@ -4162,6 +4169,7 @@ function _find_anchor(anchor, geom)=
let(
vnf=geom[1],
override = geom[2](anchor)
,fd=echo(cp=cp)
) // CENTER anchors anchor on cp, "origin" anchors on [0,0]
approx(anchor,CTR)? [anchor, default(override[0],cp),default(override[1],UP),default(override[2], 0)] :
vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor,UP), 0] :
@@ -4442,15 +4450,15 @@ module show_anchors(s=10, std=true, custom=true) {
if (std) {
for (anchor=_standard_anchors(two_d=two_d)) {
if(two_d) {
attach(anchor) anchor_arrow2d(s);
attach(anchor,BOT) anchor_arrow2d(s);
} else {
attach(anchor) anchor_arrow(s);
attach(anchor,BOT) anchor_arrow(s);
}
}
}
if (custom) {
for (anchor=last($parent_geom)) {
attach(anchor[0]) {
attach(anchor[0],BOT) {
if(two_d) {
anchor_arrow2d(s, color="cyan");
} else {

View File

@@ -577,14 +577,14 @@ module partition(size=100, spread=10, cutsize=10, cutpath="jigsaw", gap=0, cutpa
move(vec) {
$idx = 0;
intersection() {
children();
if ($children>0) children();
partition_mask(l=rsize.x, w=rsize.y, h=rsize.z, cutsize=cutsize, cutpath=cutpath, gap=gap, cutpath_centered=cutpath_centered, spin=spin);
}
}
move(-vec) {
$idx = 1;
intersection() {
children();
if ($children>0) children();
partition_mask(l=rsize.x, w=rsize.y, h=rsize.z, cutsize=cutsize, cutpath=cutpath, gap=gap, cutpath_centered=cutpath_centered, inverse=true, spin=spin);
}
}

View File

@@ -4145,7 +4145,9 @@ function _prism_fillet_prism(name, basepoly, bot, top, d, k, N, overlap, uniform
// The prism will connect anchor points described by the two descriptions you supply. The supported object
// types are prismoids, VNFs, cylinders, spheres, and linear sweeps. For prismoids and VNFs you can use any anchor on a face
// or edge anchors that include edge geometry. For spheres you can use any anchor. In the case of cylinders and linear sweeps you can
// attach to the flat top or bottom in any case, but for side attachments, the shape must not have scaling (so it cannot
// attach to the flat top or bottom, or to named face anchors in any case. When you do this, the attachment is treated as an infinite plane.
// You can attach to the side of the extrusion and follow the shape of the extrusion using the standard anchors only, but the
// shape must not have scaling (so it cannot
// be conical) and it must not have any shift. Only right angle cylinders and extrusions are supported.
// Anchors on the top and bottom edges are also not supported. When connecting to an extrusion the selected anchor
// point must lie on the surface of the shape. This may requires setting `atype="intersect"` when creating the extrusion.
@@ -4479,7 +4481,7 @@ function _get_obj_type(ind,geom,anchor,prof) =
)
[[x,-y],[0,0], [x,y]]
: starts_with(geom[0], "extrusion") ?
anchor==UP || anchor==DOWN ? "plane"
anchor==UP || anchor==DOWN || starts_with(anchor,"face") ? "plane"
:
assert(geom[3]==0, str("Extrusion in desc", ind, " has nonzero twist, which is not supported."))
assert(geom[5]==[0,0], str("Extrusion in desc", ind, " has nonzero shift, which is not supported."))

View File

@@ -190,7 +190,7 @@ function cube(size=1, center, anchor, spin=0, orient=UP) =
// );
// Example: Roundings and Chamfers can be as large as the full size of the cuboid, so long as the edges would not interfere.
// cuboid([40,20,10], rounding=20, edges=[FWD+RIGHT,BACK+LEFT]);
// Example: Standard Connectors
// Example: Standard anchors
// cuboid(40) show_anchors();
module cuboid(
@@ -616,6 +616,11 @@ function cuboid(
// The anchors on the top and bottom faces have spin pointing back. The anchors on the side faces have spin point UP.
// The anchors on the top and bottom edges also have anchors that point clockwise as viewed from outside the shapep.
// The anchors on the side edges and the corners have spin with positive Z component, pointing along the edge where the anchor is located.
// A degenerate prismoid with a line segment for the top or bottom has its top or bottom edge anchors set to provide an anchor for that top
// or bottom edge. So for example, if the top is `[0,10]` then the top edge is parallel to the Y axis and you can anchor to that
// edge using the `TOP+RIGHT` or `TOP+LEFT` anchors; these anchors point in the direction that divides the edge in half and provide
// the `$edge_angle` and `$edge_length` values generally provided by edge anchors. The UP or DOWN anchor is in the same location but always points
// in the Z direction and provides no edge data.
// Arguments:
// size1 = [width, length] of the bottom end of the prism.
// size2 = [width, length] of the top end of the prism.
@@ -689,9 +694,14 @@ function cuboid(
// mask2d_roundover(h=5,mask_angle=$edge_angle);
// }
// }
// Example(Spin,VPD=160,VPT=[0,0,10]): Standard Connectors
// Example(Spin,VPD=160,VPT=[0,0,10]): Standard anchors
// prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5])
// show_anchors();
// Example(3D): When the top or bottom is degenerate, you can anchor to and round the degenerate edge by using either one of the edge anchors that correspond to that edge. But note that {{edge_profile()}} does not work for this degenerate case. We used `TOP+RIGHT` below as the anchor point, but `TOP+LEFT` will produce an identical result.
// diff()
// prismoid([10,14],[0,8], shift=[4,3], h=7)
// attach(TOP+RIGHT, FWD+LEFT, inside=true)
// rounding_edge_mask(r=2,l=$edge_length+6);
module prismoid(
size1=undef, size2=undef, h, shift=[undef,undef],
@@ -841,8 +851,7 @@ function prismoid(
// being located at the bottom of the shape, so confirm anchor positions before use.
// Additional named face and edge anchors are located on the side faces and vertical edges of the prism.
// You can use `EDGE(i)`, `EDGE(TOP,i)` and `EDGE(BOT,i)` as a shorthand for accessing the named edge anchors, and `FACE(i)` for the face anchors.
// When you use `shift`, which moves the top face of the prism, the spin for the side face and edges anchors will align
// the child with the edge or face direction. The "edge0" anchor identifies an edge located along the X+ axis, and then edges
// The "edge0" anchor identifies an edge located along the X+ axis, and then edges
// are labeled counting up in the clockwise direction. Similarly "face0" is the face immediately clockwise from "edge0", and face
// labeling proceeds clockwise. The top and bottom edge anchors label edges directly above and below the face with the same label.
// If you set `realign=true` then "face0" is oriented in the X+ direction.
@@ -1753,6 +1762,7 @@ function rect_tube(
// "hypot" = Center of angled wedge face, perpendicular to that face.
// "hypot_left" = Left side of angled wedge face, bisecting the angle between the left side and angled faces.
// "hypot_right" = Right side of angled wedge face, bisecting the angle between the right side and angled faces.
// "top_edge" = Top edge anchor which, unlike the UP anchor, points in direction that bisects the edge, and provides `$edge_length` and `$edge_angle`.
//
// Example: Centered
// wedge([20, 40, 15], center=true);
@@ -1766,6 +1776,11 @@ function rect_tube(
// Example(3D,Med,VPR=[55.00,0.00,25.00],VPD=151.98,VPT=[2.30,-11.81,-5.66]): Named Anchors
// wedge([40, 80, 30], center=true)
// show_anchors(std=false);
// Example(3D): Rounding the top of the wedge using the "top_edge" anchor
// diff()
// wedge([10,15,7])
// attach("top_edge", FWD+LEFT, inside=true)
// rounding_edge_mask(r=2, l=$edge_length+1);
module wedge(size=[1, 1, 1], center, anchor, spin=0, orient=UP)
{
@@ -1778,10 +1793,13 @@ module wedge(size=[1, 1, 1], center, anchor, spin=0, orient=UP)
left_dir = unit(hypot_dir+LEFT);
right_dir = unit(hypot_dir+RIGHT);
hedge_spin=vector_angle(spindir,rot(from=UP,to=left_dir, p=BACK));
topedge_dir = [0, each unit(unit([size.z,size.y])+[-1,0])];
anchors = [
named_anchor("hypot", CTR, hypot_dir, 180),
named_anchor("hypot_left", [-size.x/2,0,0], left_dir,-hedge_spin),
named_anchor("hypot_right", [size.x/2,0,0], right_dir,hedge_spin),
named_anchor("top_edge", [0,-size.y/2,size.z/2], topedge_dir, _compute_spin(topedge_dir,RIGHT),
info=[["edge_angle",atan2(size.y,size.z)],["edge_length",size.x]])
];
attachable(anchor,spin,orient, size=size, anchors=anchors) {
if (size.z > 0) {
@@ -1811,10 +1829,13 @@ function wedge(size=[1,1,1], center, anchor, spin=0, orient=UP) =
left_dir = unit(hypot_dir+LEFT),
right_dir = unit(hypot_dir+RIGHT),
hedge_spin=vector_angle(spindir,rot(from=UP,to=left_dir, p=BACK)),
topedge_dir = [0, each unit(unit([size.z,size.y])+[-1,0])],
anchors = [
named_anchor("hypot", CTR, hypot_dir, 180),
named_anchor("hypot_left", [-size.x/2,0,0], left_dir,-hedge_spin),
named_anchor("hypot_right", [size.x/2,0,0], right_dir,hedge_spin),
named_anchor("top_edge", [0,-size.y/2,size.z/2], topedge_dir, _compute_spin(topedge_dir,RIGHT),
info=[["edge_angle",atan2(size.y,size.z)],["edge_length",size.x]])
]
)
reorient(anchor,spin,orient, size=size, anchors=anchors, p=vnf);

130
skin.scad
View File

@@ -541,6 +541,15 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
// correct only for twisted objects, and corner anchors may point in unexpected directions in some cases. These anchors also ignore any applied texture.
// If you need anchors directly computed from the surface you can pass the vnf from linear_sweep
// to {{vnf_polyhedron()}}, which computes anchors directly from the full VNF.
// Additional named face and edge anchors are located on the side faces and vertical edges of the prism.
// When you sweep a polygon you can use `EDGE(i)`, `EDGE(TOP,i)` and `EDGE(BOT,i)` as a shorthand for
// accessing the named edge anchors, and `FACE(i)` for the face anchors.
// The "edge0" anchor identifies an edge located along the X+ axis, and then edges
// are labeled counting up in the clockwise direction. Similarly "face0" is the face immediately clockwise from "edge0", and face
// labeling proceeds clockwise. The top and bottom edge anchors label edges directly above and below the face with the same label.
// When you sweep a region, the region is decomposed using {{region_parts()}} and the anchors are generated for the region components
// in the order produced by the decomposition, working entirely through each component and then on to the next component.
// The anchors for twisted shapes may be inaccurate.
// Arguments:
// region = The 2D [Region](regions.scad) or polygon that is to be extruded.
// h / height / l / length = The height to extrude the region. Default: 1
@@ -574,6 +583,12 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
// "origin" = Centers the extruded shape vertically only, but keeps the original path positions in the X and Y. Oriented UP.
// "original_base" = Keeps the original path positions in the X and Y, but at the bottom of the extrusion. Oriented DOWN.
// "original_top" = Keeps the original path positions in the X and Y, but at the top of the extrusion. Oriented UP.
// "edge0", "edge1", etc. = Center of each side edge, spin pointing up along the edge. Can access with EDGE(i)
// "face0", "face1", etc. = Center of each side face, spin pointing up. Can access with FACE(i)
// "top_edge0", "top_edge1", etc = Center of each top edge, spin pointing clockwise (from top). Can access with EDGE(TOP,i)
// "bot_edge0", "bot_edge1", etc = Center of each bottom edge, spin pointing clockwise (from bottom). Can access with EDGE(BOT,i)
// "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
// Example: Extruding a Compound Region.
// rgn1 = [for (d=[10:10:60]) circle(d=d,$fn=8)];
// rgn2 = [square(30,center=false)];
@@ -743,7 +758,7 @@ module linear_sweep(
anchor = center==true? "origin" :
center == false? "original_base" :
default(anchor, "original_base");
vnf = linear_sweep(
vnf_geom = linear_sweep(
region, height=h, style=style, caps=caps,
twist=twist, scale=scale, shift=shift,
texture=texture,
@@ -755,31 +770,73 @@ module linear_sweep(
tex_depth=tex_depth,
tex_samples=tex_samples,
slices=slices,
maxseg=maxseg,
anchor="origin"
maxseg=maxseg, atype=atype,
anchor="origin", _return_geom=true
);
anchors = [
named_anchor("original_base", [0,0,-h/2], DOWN),
named_anchor("original_top", [0,0,h/2], UP),
];
cp = default(cp, "centroid");
geom = atype=="hull"? attach_geom(cp=cp, region=region, h=h, extent=true, shift=shift, scale=scale, twist=twist, anchors=anchors) :
atype=="intersect"? attach_geom(cp=cp, region=region, h=h, extent=false, shift=shift, scale=scale, twist=twist, anchors=anchors) :
atype=="bbox"?
let(
bounds = pointlist_bounds(flatten(region)),
size = bounds[1] - bounds[0],
midpt = (bounds[0] + bounds[1])/2
)
attach_geom(cp=[0,0,0], size=point3d(size,h), offset=point3d(midpt), shift=shift, scale=scale, twist=twist, anchors=anchors) :
assert(in_list(atype, ["hull","intersect","bbox"]), "\nAnchor type must be \"hull\", \"intersect\", or \"bbox\".");
attachable(anchor,spin,orient, geom=geom) {
vnf_polyhedron(vnf, convexity=convexity);
attachable(anchor,spin,orient, geom=vnf_geom[1]) {
vnf_polyhedron(vnf_geom[0], convexity=convexity);
children();
}
}
function _make_all_prism_anchors(bot, top, startind=0) =
let(
facenormal= [
for(i=idx(bot))
let(
edge0 = [top[i],bot[i]], // vertical edge at i
edge1 = [select(top,i+1),select(bot,i+1)], // vertical edge at i+1
facenormal = unit(unit(cross(edge1[1]-edge0[0], edge0[1]-edge0[0]))+
unit(cross(edge0[0]-edge1[1], edge1[0]-edge1[1])))
)
facenormal
],
anchors = [for(i=idx(bot))
let(
edge1 = [top[i],bot[i]], // vertical edge at i
edge2 = [select(top,i+1),select(bot,i+1)], // vertical edge at i+1
facecenter = mean(concat(edge1,edge2)),
facespin = _compute_spin(facenormal[i], UP),
side_edge_center = mean(edge1),
side_edge_dir = top[i]-bot[i],
side_edge_normal = unit(vector_bisect(facenormal[i],select(facenormal,i-1))),
side_edge_spin = _compute_spin(side_edge_normal, side_edge_dir),
side_edge_angle = 180-vector_angle(facenormal[i], select(facenormal,i-1)),
side_edge_len = norm(side_edge_dir),
top_edge_center = (edge2[0]+edge1[0])/2,
top_edge_dir = edge2[0]-edge1[0],
bot_edge_center = (edge1[1]+edge2[1])/2,
bot_edge_dir = edge1[1]-edge2[1],
topnormal = unit(facenormal[i]+UP),
botnormal = unit(facenormal[i]+DOWN),
topedgespin = _compute_spin(topnormal, top_edge_dir),
botedgespin = _compute_spin(botnormal, bot_edge_dir),
topedgeangle = 180-vector_angle(UP,facenormal[i])
)
each [
named_anchor(str("face",i+startind), facecenter, facenormal[i], facespin),
named_anchor(str("edge",i+startind), side_edge_center, side_edge_normal, side_edge_spin,
info=[["edge_angle",side_edge_angle], ["edge_length",side_edge_len]]),
named_anchor(str("top_edge",i+startind), top_edge_center, topnormal, topedgespin,
info=[["edge_angle",topedgeangle],["edge_length",norm(top_edge_dir)]]),
named_anchor(str("bot_edge",i+startind), bot_edge_center, botnormal, botedgespin,
info=[["edge_angle",180-topedgeangle],["edge_length",norm(bot_edge_dir)]]),
named_anchor(str("top_corner",i+startind), top[i], unit(side_edge_normal+UP),
_compute_spin(unit(side_edge_normal+UP),side_edge_dir)),
named_anchor(str("bot_corner",i+startind), bot[i], unit(side_edge_normal+DOWN),
_compute_spin(unit(side_edge_normal+DOWN),side_edge_dir))
]
]
)
anchors;
function linear_sweep(
region, height, center,
twist=0, scale=1, shift=[0,0],
@@ -788,7 +845,7 @@ function linear_sweep(
texture, tex_size=[5,5], tex_reps, tex_counts,
tex_inset=false, tex_rot=0,
tex_scale, tex_depth, tex_samples, h, l, length,
anchor, spin=0, orient=UP
anchor, spin=0, orient=UP, _return_geom=false
) =
assert(num_defined([tex_reps,tex_counts])<2, "\nIn linear_sweep() the 'tex_counts' parameter has been replaced by 'tex_reps'. You cannot give both.")
assert(num_defined([tex_scale,tex_depth])<2, "\nIn linear_sweep() the 'tex_scale' parameter has been replaced by 'tex_depth'. You cannot give both.")
@@ -804,23 +861,21 @@ function linear_sweep(
assert(is_vector(shift, 2), str(shift))
assert(is_bool(caps) || is_bool_list(caps,2), "\ncaps must be boolean or a list of two booleans.")
let(
h = one_defined([h, height,l,length],"h,height,l,length",dflt=1)
)
!is_undef(texture)? _textured_linear_sweep(
h = one_defined([h, height,l,length],"h,height,l,length",dflt=1),
regions = region_parts(region),
vnf = !is_undef(texture)?
_textured_linear_sweep(
region, h=h, caps=caps,
texture=texture, tex_size=tex_size,
counts=tex_reps, inset=tex_inset,
rot=tex_rot, tex_scale=tex_depth,
twist=twist, scale=scale, shift=shift,
style=style, samples=tex_samples,
anchor=anchor, spin=spin, orient=orient
) :
let(
style=style, samples=tex_samples)
: let(
caps = is_bool(caps) ? [caps,caps] : caps,
anchor = center==true? "origin" :
center == false? "original_base" :
default(anchor, "original_base"),
regions = region_parts(region),
slices = default(slices, max(1,ceil(abs(twist)/5))),
scale = is_num(scale)? [scale,scale] : point2d(scale),
topmat = move(shift) * scale(scale) * rot(-twist),
@@ -857,10 +912,23 @@ function linear_sweep(
) vnf_vertex_array(verts, caps=false, col_wrap=true, style=style),
if (caps[0]) for (rgn = regions) vnf_from_region(rgn, down(h/2), reverse=true),
if (caps[1]) for (rgn = trgns) vnf_from_region(rgn, up(h/2), reverse=false)
]),
])
)
vnf,
regparts = flatten(regions),
sizes = [0,each cumsum([for(entry=regparts) len(entry)])],
ganchors = [
for(i=idx(regparts))
let(
bot = path3d(regparts[i],-h/2),
top = path3d(move(shift,scale(scale, zrot(-twist, regparts[i]))),h/2)
)
each _make_all_prism_anchors(bot,top, startind=sizes[i])
],
anchors = [
named_anchor("original_base", [0,0,-h/2], DOWN),
named_anchor("original_top", [0,0,h/2], UP),
each ganchors
],
cp = default(cp, "centroid"),
geom = atype=="hull"? attach_geom(cp=cp, region=region, h=h, extent=true, shift=shift, scale=scale, twist=twist, anchors=anchors) :
@@ -873,7 +941,7 @@ function linear_sweep(
)
attach_geom(cp=[0,0,0], size=point3d(size,h), offset=point3d(midpt), shift=shift, scale=scale, twist=twist, anchors=anchors) :
assert(in_list(atype, ["hull","intersect","bbox"]), "\nAnchor type must be \"hull\", \"intersect\", or \"bbox\".")
) reorient(anchor,spin,orient, geom=geom, p=vnf);
) _return_geom ? [vnf,geom] : reorient(anchor,spin,orient, geom=geom, p=vnf);
// Function&Module: rotate_sweep()

View File

@@ -1082,7 +1082,7 @@ function zscale(z=1, p=_NO_ARG, cp=0) =
function mirror(v, p=_NO_ARG) =
assert(is_vector(v))
assert(p==_NO_ARG || is_list(p),"Invalid pointlist")
let(m = len(v)==2? affine2d_mirror(v) : affine3d_mirror(v))
let(m = affine3d_mirror(v))
p==_NO_ARG? m : apply(m,p);

View File

@@ -1,3 +1,5 @@
[Previous: Using position()](Tutorial-Attachment-Position)
# Aligning children with align()
You may have noticed that with position() and orient(), specifying the
@@ -125,3 +127,5 @@ cyl(h=20,d=10,$fn=128)
align([1,.3],TOP)
color("lightblue")cuboid(5);
```
[Next: Using attach()](Tutorial-Attachment-Attach)

View File

@@ -1,3 +1,5 @@
[Prev: Using align()](Tutorial-Attachment-Align)
# Attachment using attach()
The `attach()` module can stick the child object to the parent object
@@ -674,3 +676,5 @@ circle(d=50){
trapezoid(w1=30,w2=0,h=30);
}
```
[Next: Attachable Parts](Tutorial-Attachment-Parts)

View File

@@ -1,3 +1,5 @@
[Previous: Attachments Overview](Tutorial-Attachment-Overview)
# Basic Object Positioning: Anchor, Spin and Orient
When you create attachable objects using BOSL2 you have some options
@@ -320,3 +322,5 @@ For 2D shapes, you can mix `anchor=` with `spin=`, but not with `orient=`.
include <BOSL2/std.scad>
square([40,30], anchor=BACK+LEFT, spin=30);
```
[Next: Relative Positioning of Children](Tutorial-Attachment-Relative-Positioning)

View File

@@ -1,3 +1,5 @@
[Prev: Attachable Parts](Tutorial-Attachment-Parts)
## Coloring Attachables
Usually, when coloring a shape with the `color()` module, the parent color overrides the colors of
all children. This is often not what you want:
@@ -57,3 +59,5 @@ affecting its descendents.
As with all of the attachable features, these color, highlight and ghost modules only work
on attachable objects, so they will have no effect on objects you
create using `linear_extrude()` or `rotate_extrude()`.
[Next: Tagged Operations with Attachments](Tutorial-Attachment-Tags)

View File

@@ -1,3 +1,5 @@
[Prev: Tagged Operations with Attachments](Tutorial-Attachment-Tags)
# Using Attachment for Edge Profiling
You can use attachment in various ways to create edge profiles on
@@ -163,4 +165,4 @@ cube([50,60,70],center=true)
mask2d_roundover(10);
```
[Next: Making Attachable Objects](Tutorial-Attachment-Making)

View File

@@ -1,3 +1,5 @@
[Prev: Edge Profiling with Attachment](Tutorial-Attachment-Edge-Profiling)
# Making Attachables
To make a shape attachable, you just need to wrap it with an `attachable()` module with a
@@ -531,3 +533,140 @@ module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
}
cubic_barbell(100) show_anchors(30);
```
## Making Gometry With attach_geom()
Sometimes it may be advantageous to create the attachable geometry as
a data structure. This can be particularly useful if you want to
implement anchor types with an object, because it allows for an easy
way to create different anchor options without having to repeat code.
Suppose we create a simple tetrahedron object:
```openscad-3D
include<BOSL2/std.scad>
module tetrahedron(base, height, spin=0, anchor=FWD+LEFT+BOT, orient=UP)
{
base_poly = path3d([[0,0],[0,base],[base,0]]);
top = [0,0,height];
vnf = vnf_vertex_array([base_poly, repeat(top,3)],col_wrap=true,cap1=true);
attachable(anchor=anchor,orient=orient,spin=spin,vnf=vnf,cp="centroid"){
vnf_polyhedron(vnf);
children();
}
}
tetrahedron(20,18) show_anchors();
```
For this module we have used VNF anchors, but this tetrahedron is the
corner of a cuboid, so maybe sometimes you prefer to use anchors
based on the corresponding cuboid (its bounding box). You can create a module with bounding
box anchors like this, where we have explicitly centered the VNF
to make it work with the prismoid type anchoring:
```openscad-3D
include<BOSL2/std.scad>
module tetrahedron(base, height, spin=0, anchor=FWD+LEFT+BOT, orient=UP)
{
base_poly = path3d([[0,0],[0,base],[base,0]]);
top = [0,0,height];
vnf = move([-base/2,-base/2,-height/2],
vnf_vertex_array([base_poly, repeat(top,3)],col_wrap=true,cap1=true));
attachable(anchor=anchor,orient=orient,spin=spin,size=[base,base,height]){
vnf_polyhedron(vnf);
children();
}
}
tetrahedron(20,18) show_anchors();
```
The arguments needed to attachable are different in this case. If you
want to conditionally switch between these two modes of operation,
this presents a complication. While it is possible to work around
this by conditionally setting parameters to `undef`, the resulting
code will be more complex and harder to read. A better solution is to
compute the geometry conditionally. Then the geometry can be passed
to `attachable()`.
```openscad-3D;Big
include<BOSL2/std.scad>
module tetrahedron(base, height, atype="vnf", spin=0, anchor=FWD+LEFT+BOT, orient=UP)
{
assert(atype=="vnf" || atype=="box");
base_poly = path3d([[0,0],[0,base],[base,0]]);
top = [0,0,height];
vnf = move([-base/2,-base/2,-height/2],
vnf_vertex_array([base_poly, repeat(top,3)],col_wrap=true,cap1=true));
geom = atype=="vnf" ? attach_geom(vnf=vnf,cp="centroid")
: attach_geom(size=[base,base,height]);
attachable(anchor=anchor,orient=orient,spin=spin,geom=geom){
vnf_polyhedron(vnf);
children();
}
}
tetrahedron(20,18,atype="vnf")
color("green")attach(TOP,BOT) cuboid(4);
right(25)
tetrahedron(20,18,atype="box")
color("lightblue")attach(TOP,BOT) cuboid(4);
```
Here we have created an `atype` argument that accepts two attachment
types and we compute the geometry conditionally based on the atype
setting. We can then invoke `attachable()` once with the `geom`
parameter to specify the geometry.
## Creating Attachable Parts
If your object has multiple distinct parts you may wish to create
attachble parts for your object. In the library, `tube()` create
an attachable part called "inside" that lets you attach to the inside
of the tube.
Below we create an example where an object is made from two
cylindrical parts, and we want to be able to attach to either
one. In order to create attchable parts you must pass a list of the parts
to `attachable()`. You create a part using the `define_part()`
function which requires the part's name and its geometry. You can
optionally provide a transformation using the `T=` parameter and give
a flat with the `inside=` parameter.
```openscad-3D;Big
include<BOSL2/std.scad>
module twocyl(d1, d2, sep, h, ang=20)
{
parts = [
define_part("left", attach_geom(r=d1/2,h=h),
T=left(sep/2)*yrot(-ang)),
define_part("right", attach_geom(r=d2/2,h=h),
T=right(sep/2)*yrot(ang)),
];
attachable(size=[sep+d1/2+d2/2,max(d1,d2),h], parts=parts){
union(){
left(sep/2) yrot(-ang) cyl(d=d1,h=h);
right(sep/2) yrot(ang) cyl(d=d2,h=h);
}
children();
}
}
twocyl(d1=10,d2=13,sep=20,h=10){
attach_part("left") attach(FWD,BOT)
color("lightblue") cuboid(3);
attach_part("right") attach(RIGHT,BOT)
color("green") cuboid(3);
}
```
In the above example we create a parts list containing two parts named
"left" and "right". Each part has its own geometry corresponding to
the size of the cylinder, and it has a transformation specifying where
the cylinder is located relative to the part's overall geometry.
If you create an "inside" part for a tube, the inside object will
naturally have its anchors on the inner cylinder **pointing
outward**. You can anchor on the inside by setting `inside=true` when
invoking `attach()` or `align()`, but another option is to set `inside=true`
with `define_part()`. This marks the geometry as an inside geometry, which cause `align()` and
`attach()` to invert the meaning of the `inside` parameter so that
objects will attach on the inside by default.

View File

@@ -24,4 +24,6 @@ tutorial. The non-attachables are `polyhedron()`, `linear_extrude()`,
Some of these have attachable alternatives: `vnf_polyhedron()`,
`linear_sweep()`, `rotate_sweep()`, and `region()`.
[Next: Basic Positioning](Tutorial-Attachment-Basic-Positioning)

View File

@@ -0,0 +1,50 @@
[Prev: Using attach()](Tutorial-Attachment-Attach)
# Attachment Parts
Some objects provide named attachable parts that you can select
instead of using the main geometry for the object. One important kind
of attachable part is the inside of a tube.
Here is a tube with its anchors shown:
```openscad-3D
include<BOSL2/std.scad>
tube(id=20,h=15,wall=3)
show_anchors();
```
The anchors are all on the outside wall of the tube and give you no
method for placing a child **inside** the tube. In order to attach
inside the tube, we select the "inside" part using the `attach_part()`
module.
```openscad-3D
include<BOSL2/std.scad>
tube(id=20,h=15,wall=3)
attach_part("inside")
align(BACK,TOP)
color("lightblue") cuboid(4);
```
Now when we align the cube to the BACK wall of the tube it appears on
the inside of the tube. If you need to attach to both the inside and
outside you can place some attachments using `attach_part()` and some
with the standard attachment geometry on the outside like this:
```openscad-3D
include<BOSL2/std.scad>
diff()
tube(id=20,h=15,wall=3){
attach([1,-1/2],BOT)
color("green")cyl(d=4,h=3,$fn=12);
attach_part("inside"){
attach(LEFT,BOT,align=TOP)
color("lightblue")cuboid(4);
attach(BACK,CTR,align=TOP,inside=true, inset=-0.1)
cuboid(4);
}
}
```
[Next: Using Color with Attachments](Tutorial-Attachment-Color)

View File

@@ -1,3 +1,5 @@
[Previous: Relative Positioning of Children](Tutorial-Attachment-Relative-Positioning)
# Placing Children using position()
If you make an object a child of another object then the child object
@@ -203,3 +205,5 @@ prismoid([50,50],[30,30],h=40)
orient(RIGHT)
anchor_arrow(40);
```
[Next: Using align()](Tutorial-Attachment-Align)

View File

@@ -1,3 +1,5 @@
[Prev: Basic Positioning](Tutorial-Attachment-Basic-Positioning)
# Relative Positioning: Placing Children using position(), align(), and attach()
Relative positioning is one of the most useful and powerful features
@@ -17,3 +19,5 @@ Relative positioning means that since objects are positioned relative
to other objects, you do not need to keep track of absolute positions
and orientations of objects in your model. This makes models simpler,
more intuitive, and easier to maintain.
[Next: Using position()](Tutorial-Attachment-Position)

View File

@@ -1,3 +1,5 @@
[Prev: Using Color with Attachments](Tutorial-Attachment-Color)
# Tagged Operations
BOSL2 introduces the concept of tags. Tags are names that can be given to attachables, so that
@@ -183,3 +185,5 @@ cube(50, center=true) {
tag("keep")xcyl(h=100, d=20);
}
```
[Next: Edge Profiling with Attachment](Tutorial-Attachment-Edge-Profiling)