Merge pull request #1717 from adrianVmariano/master

Add attach_part() and attachable_part() to support object part geometries
This commit is contained in:
adrianVmariano
2025-06-16 19:22:10 -04:00
committed by GitHub
2 changed files with 116 additions and 9 deletions

View File

@@ -33,6 +33,8 @@ $parent_orient = UP;
$parent_size = undef;
$parent_geom = undef;
$attach_inside = false; // If true, flip the meaning of the inside parameter for align() and attach()
$edge_angle = undef;
$edge_length = undef;
@@ -700,12 +702,10 @@ module align(anchor,align=CENTER,inside=false,inset=0,shiftout=0,overlap)
overlap = (overlap!=undef)? overlap : $overlap;
dummy1=assert($parent_geom != undef, "No object to align to.")
assert(is_undef($attach_to), "Cannot use align() as a child of attach()");
if (is_undef($align_msg) || $align_msg)
echo("ALERT: align() has changed, May 1, 2024. See the wiki and attach(align=). $align_msg=false disables this message");
anchor = is_vector(anchor) ? [anchor] : anchor;
align = is_vector(align) ? [align] : align;
two_d = _attach_geom_2d($parent_geom);
factor = inside?-1:1;
factor = ($anchor_inside ? -1 : 1)*(inside?-1:1);
for (i = idx(anchor)) {
$align_msg=false; // Remove me when removing the message above
face = anchor[i];
@@ -938,6 +938,8 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
assert(is_finite(spin) || spin=="align", "Spin must be a number (unless align is given)")
assert((is_undef(overlap) || is_finite(overlap)) && (is_def(overlap) || is_undef($overlap) || is_finite($overlap)),
str("Provided ",is_def(overlap)?"":"$","overlap is not valid."));
removetag = inside;
inside = $anchor_inside ? !inside : inside;
if (is_def(to))
echo("The 'to' option to attach() is deprecated and will be removed in the future. Use 'child' instead.");
if (is_def(from))
@@ -1059,13 +1061,13 @@ 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);
if (norot || (approx(anchor_dir,reference) && anchor_spin==0))
translate(pos) rot(v=spinaxis,a=factor*spin) translate(olap) default_tag("remove",inside) children();
translate(pos) rot(v=spinaxis,a=factor*spin) translate(olap) default_tag("remove",removetag) children();
else
translate(pos)
rot(v=spinaxis,a=factor*spin)
rot(anchor_spin,from=reference,to=anchor_dir)
translate(olap)
default_tag("remove",inside) children();
default_tag("remove",removetag) children();
}
}
}
@@ -3065,6 +3067,7 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
// right(75) plug();
module attachable(
anchor, spin, orient,
size, size2, shift,
@@ -3077,6 +3080,7 @@ module attachable(
two_d=false,
axis=UP,override,
geom,
parts=[],
expose_tags=false, keep_color=false
) {
dummy1 =
@@ -3110,6 +3114,8 @@ module attachable(
$attach_to = undef;
$anchor_override=undef;
$attach_alignment=undef;
$parent_parts = parts;
$anchor_inside = false;
if (expose_tags || _is_shown()){
if (!keep_color)
_color($color)
@@ -3151,6 +3157,42 @@ module _show_ghost()
}
// Module: attach_part()
// Synopsis: Select a named attachable part for subsequent attachment operations
// Topics: Attachment
// See Also: attach(), align(), attachable(), part_geometry()
// Usage:
// PARENT() attach_part(name) CHILDREN;
// Description:
// Selects an attachable part using a name defined by the parent object. Any operations
// that use the parent geometry such as {{attach()}}, {{align()}}, {{position()}} or {{parent()}}
// will reference the geometry for the specified part. This allows you to access the inner wall
// of tubes, for example.
// Example: This example shows attaching the light blue cube normally, on the outside of the tube, and the pink cube using the "inside" attachment part.
// tube(ir1=10,ir2=20,h=20, wall=3){
// color("lightblue")attach(RIGHT,BOT) cuboid(4);
// color("pink")
// attach_part("inside")
// attach(BACK,BOT) cuboid(4);
// }
module attach_part(name)
{
req_children($children);
dummy=assert(!is_undef($parent_parts), "Parent does not exist or does not have any parts");
ind = search([name], $parent_parts, 1,0)[0];
dummy2 = assert(ind!=[], str("Parent does not have a part named ",name));
$parent_geom = $parent_parts[ind][1];
$anchor_inside = $parent_parts[ind][2];
multmatrix($parent_parts[ind][3])
children();
}
function _is_geometry(entry) = is_list(entry) && is_string(entry[0]);
// Function: reorient()
// Synopsis: Calculates the transformation matrix needed to reorient an object.
// SynTags: Trans, Path, VNF
@@ -3539,6 +3581,43 @@ function attach_geom(
: ["point", cp, offset, anchors];
// Function: part_geometry()
// Synopsis: Creates an attachable part data structure.
// Topics: Attachments
// See Also: attachable()
// Usage:
// part = part_geometry(name, geom, [inside=], [T=]);
// Description:
// Create a named attachable part that can be passed in the `parts` parameter of {{attachable()}}
// and then selected using {{attach_part()}}.
// Example(3D): This example shows how to create a custom object with two different parts that are both transformed away from the origin. The basic object is two cylinders with a cube shaped attachment geometry that doesn't match the object very well. The "left" and "right" parts attach to each of the two cylinders.
// module twocyl(d, sep, h, ang=20)
// {
// parts = [
// part_geometry("left", attach_geom(r=d/2,h=h), T=left(sep/2)*yrot(-ang)),
// part_geometry("right", attach_geom(r=d/2,h=h), T=right(sep/2)*yrot(ang)),
// ];
// attachable(size=[sep+d,d,h], parts=parts){
// union(){
// left(sep/2) yrot(-ang) cyl(d=d,h=h);
// right(sep/2) yrot(ang) cyl(d=d,h=h);
// }
// children();
// }
// }
// twocyl(d=10,sep=30,h=10){
// attach(TOP,TOP) cuboid(3);
// color("pink")attach_part("left")attach(TOP,BOT) cuboid(3);
// color("green")attach_part("right")attach(TOP,BOT) cuboid(3);
// }
function part_geometry(name, geom, inside=false, T=IDENT) =
assert(is_string(name), "name must be a string")
assert(_is_geometry(geom), "geometry appears invalid")
assert(is_bool(inside), "inside must be boolean")
assert(is_matrix(T,4), "T must be a 4x4 transformation matrix")
[name, geom, inside, T];

View File

@@ -1478,7 +1478,11 @@ function textured_tile(
// more general ones, so `rounding2` is determined from `rounding`. The constant
// width default will apply when the inner rounding and chamfer are both undef.
// You can give an inner chamfer or rounding as a list with undef entries if you want to specify
// some corner roundings and allow others to be computed.
// some corner roundings and allow others to be computed.
// .
// Attachment to the rectangular tube will place objects on the **outside** of the tube.
// If you need to anchor to the inside of a tube, use {{attach_part()}} with the part name "inside"
// to switch goeomtry to the inside.
// Arguments:
// h/l/height/length = The height or length of the rectangular tube. Default: 1
// size = The outer [X,Y] size of the rectangular tube.
@@ -1565,7 +1569,14 @@ function textured_tile(
// size=100, wall=10, h=30,
// rounding=[0,10,20,30], ichamfer=[8,8,undef,undef]
// );
// Example: An example from above with a cube attached to the inside using {{attach_part()}}.
// rect_tube(
// size=100, wall=10, h=30,
// chamfer=[0,10,0,20],
// rounding=[10,0,20,0]
// )
// attach_part("inside")
// attach(BACK,BOT) cuboid(20);
function _rect_tube_rounding(factor,ir,r,alternative,size,isize) =
@@ -1677,7 +1688,10 @@ module rect_tube(
ichamfer1 = _rect_tube_rounding(1/sqrt(2),ichamfer1_temp, chamfer1, irounding1_temp, size1, isize1);
ichamfer2 = _rect_tube_rounding(1/sqrt(2),ichamfer2_temp, chamfer2, irounding2_temp, size2, isize2);
anchor = get_anchor(anchor, center, BOT, BOT);
attachable(anchor,spin,orient, size=[each size1, h], size2=size2, shift=shift) {
parts = [
part_geometry("inside", attach_geom(size=[each isize1, h], size2=isize2, shift=shift), inside=true)
];
attachable(anchor,spin,orient, size=[each size1, h], size2=size2, shift=shift, parts=parts) {
down(h/2) {
difference() {
prismoid(
@@ -2739,6 +2753,10 @@ module zcyl(
// .
// Chamfering and rounding lengths are measured based on the corners of the object except for the inner diameter when `circum=true`, in
// which case chamfers and roundings are measured from the facets. This only matters when `$fn` is small.
// .
// Attachment to the tube will place objects on the **outside** of the tube.
// If you need to anchor to the inside of a tube, use {{attach_part()}} with the part name "inside"
// to switch goeomtry to the inside.
// Usage: Basic cylindrical tube, specifying inner and outer radius or diameter
// tube(h|l, or, ir, [center], [realign=], [anchor=], [spin=],[orient=]) [ATTACHMENTS];
// tube(h|l, od=, id=, ...) [ATTACHMENTS];
@@ -2835,6 +2853,13 @@ module zcyl(
// half_of(v=[-1,1]) color("lightblue") cyl(d=9, h=12, $fn=32);
// Example: Round ended hexagonal tube using `rounding_fn` to get sufficient facets on the roundings
// tube(or=10, ir=7, h=10, $fn=6, rounding_fn=64, rounding=1.3, teardrop=true);
// Example: This example shows a regular attachment to the outside of the tube in light blue and then using {{attach_part()}} to attach the pink cube to the inside of the tube.
// tube(ir1=10,ir2=20,h=20, wall=3){
// color("lightblue")attach(RIGHT,BOT) cuboid(4);
// color("pink")
// attach_part("inside")
// attach(BACK,BOT) cuboid(4);
// }
function tube(
h, or, ir, center,
@@ -2945,7 +2970,10 @@ module tube(
last(ipath)+[0,1],
[0,h/2+1]
];
attachable(anchor,spin,orient, r1=r1, r2=r2, l=h) {
parts = [
part_geometry("inside", attach_geom(r1=ir1, r2=ir2, l=h), inside=true)
];
attachable(anchor,spin,orient, r1=r1, r2=r2, l=h, parts=parts) {
down(h/2) skew(sxz=shift.x/h, syz=shift.y/h) up(h/2)
difference(){
zrot(realign? 180/osides : 0)rotate_extrude($fn=osides,angle=360) polygon(outside);