Add compound parts

This commit is contained in:
Adrian Mariano
2025-09-22 17:03:07 -04:00
parent 3806630c3a
commit 24070e117f
4 changed files with 92 additions and 36 deletions

View File

@@ -73,6 +73,7 @@ PrioritizeFiles:
DefineHeader(BulletList): Side Effects DefineHeader(BulletList): Side Effects
DefineHeader(Table;Headers=Anchor Name|Position): Named Anchors DefineHeader(Table;Headers=Anchor Name|Position): Named Anchors
DefineHeader(Table;Headers=Anchor Type|What it is): Anchor Types DefineHeader(Table;Headers=Anchor Type|What it is): Anchor Types
DefineHeader(Table;Headers=Attachable_Part|What it is): Attachable Parts
DefineHeader(Table;Headers=Name|Definition): Terminology DefineHeader(Table;Headers=Name|Definition): Terminology
DefineHeader(BulletList): Requirements DefineHeader(BulletList): Requirements
DefineSynTags: DefineSynTags:

View File

@@ -1056,8 +1056,6 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
* rot(from=parent_abstract_anchor[2],to=UP) * rot(from=parent_abstract_anchor[2],to=UP)
* rot(v=anchor,-spin), * rot(v=anchor,-spin),
align); align);
spinaxis = two_d? UP : anchor_dir; spinaxis = two_d? UP : anchor_dir;
olap = - overlap * reference - inset*inset_dir + shiftout * (inset_dir + factor*reference*($anchor_inside?-1:1)); olap = - overlap * reference - inset*inset_dir + shiftout * (inset_dir + factor*reference*($anchor_inside?-1:1));
if (norot || (approx(anchor_dir,reference) && anchor_spin==0)) if (norot || (approx(anchor_dir,reference) && anchor_spin==0))
@@ -1073,22 +1071,27 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
} }
// Module: attach_part() // Module: attach_part()
// Synopsis: Select a named attachable part for subsequent attachment operations // Synopsis: Select a named attachable part for subsequent attachment operations
// Topics: Attachment // Topics: Attachment
// See Also: attach(), align(), attachable(), define_part(), parent_part() // See Also: attach(), align(), attachable(), define_part(), parent_part()
// Usage: // Usage:
// PARENT() attach_part(name) CHILDREN; // PARENT() attach_part(name, [ind]) CHILDREN;
// Description: // Description:
// Most attachable objects have a single geometry defined that is used by the attachment commands, // Most attachable objects have a single geometry defined that is used by the attachment commands,
// but some objects also define attachable parts. This module selects // but some objects also define attachable parts. This module selects
// an attachable part using a name defined by the parent object. Any operations // 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()}} // that use the parent geometry such as {{attach()}}, {{align()}}, {{position()}} or {{parent()}}
// references the geometry for the specified part. This allows you to access the inner wall // references the geometry for the specified part. This allows you to access the inner wall
// of tubes, for example. Note that you cannot call `attach_part()` as a child of another `attach_part()`. // of tubes, for example. Note that you cannot call `attach_part()` as a child of another `attach_part()`.
// .
// If you select a compound part then you can also provide an index to select the desired component
// of that part. The index wraps around in both directions so for example, -1 gives the last component, and
// the selection cannot fail due to being out of bounds. The default index is zero. The `ind`
// parameter has no effect on simple parts.
// Arguments: // Arguments:
// name = name of part to use for subsequent attachments. // name = name of part to use for subsequent attachments.
// ind = index for use with compount parts. Default: 0
// 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. // 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){ // tube(ir1=10,ir2=20,h=20, wall=3){
// color("lightblue")attach(RIGHT,BOT) cuboid(4); // color("lightblue")attach(RIGHT,BOT) cuboid(4);
@@ -1097,20 +1100,27 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
// attach(BACK,BOT) cuboid(4); // attach(BACK,BOT) cuboid(4);
// } // }
module attach_part(name) module attach_part(name, ind=0)
{ {
req_children($children); req_children($children);
dummy=assert(!is_undef($parent_parts), "\nParent does not exist or does not have any parts."); part = _get_part(name,ind);
ind = search([name], $parent_parts, 1,0)[0]; $parent_geom = part[0];
dummy2 = assert(ind!=[], str("\nParent does not have a part named \"",name,"\".")); $anchor_inside = part[1];
$parent_geom = $parent_parts[ind][1]; T = part[2];
$anchor_inside = $parent_parts[ind][2];
T = $parent_parts[ind][3];
$parent_parts = []; $parent_parts = [];
multmatrix(T) multmatrix(T)
children(); children();
} }
function _get_part(name, ind) =
assert(!is_undef($parent_parts), "\nParent does not exist or does not have any parts.")
let(
pind = search([name], $parent_parts, 1,0)[0]
)
assert(pind!=[], str("\nParent does not have a part named \"",name,"\"."))
select($parent_parts[pind][1],ind);
// Section: Tagging // Section: Tagging
@@ -3631,18 +3641,22 @@ function attach_geom(
// Function: define_part() // Function: define_part()
// Synopsis: Creates an attachable part data structure. // Synopsis: Creates an attachable part data structure.
// Topics: Attachments // Topics: Attachments
// See Also: attachable() // See Also: attachable(), attach_part(), parent_part()
// Usage: // Usage:
// part = define_part(name, geom, [inside=], [T=]); // part = define_part(name, geom, [inside=], [T=]);
// Description: // Description:
// Create a named attachable part that can be passed in the `parts` parameter of {{attachable()}} // Create a named attachable part that can be passed in the `parts` parameter of {{attachable()}}
// and then selected using {{attach_part()}}. // and then selected using {{attach_part()}}. You can create a simple part or a compound part.
// A simple part has a single geometry and associated transformation matrix and `inside` flag.
// A compound part is a part with multiple components that can be indexed numerically. To create
// a compound part either `geom` should be a list of geometries or `T` a list of transformations,
// or they should both be lists of compatible length.
// Arguments: // Arguments:
// name = name of part // name = name of part
// geom = geometry of part produced by {{attach_geom()}} // geom = geometry of part (or list for compound parts) produced by {{attach_geom()}}
// --- // ---
// inside = if true, reverse the attachment direction for children. Default: false // inside = if true, reverse the attachment direction for children. Default: false
// T = Transformation to apply to children. Default: IDENT // T = Transformation to apply to children (or list for compound parts). Default: IDENT
// 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 well. The "left" and "right" parts attach to each of the two cylinders. // 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 well. The "left" and "right" parts attach to each of the two cylinders.
// module twocyl(d, sep, h, ang=20) // module twocyl(d, sep, h, ang=20)
// { // {
@@ -3663,15 +3677,49 @@ function attach_geom(
// color("pink")attach_part("left")attach(TOP,BOT) cuboid(3); // color("pink")attach_part("left")attach(TOP,BOT) cuboid(3);
// color("green")attach_part("right")attach(TOP,BOT) cuboid(3); // color("green")attach_part("right")attach(TOP,BOT) cuboid(3);
// } // }
// Example(3D): This example shows a custom object with a compound part. The "cube" part provides access to all the cubes in the ring by index number.
// $fn=18;
// module cube_ring(size, r, n)
// {
// size = force_list(size,3);
// Tlist = zrot_copies(n=n, r=r);
// parts = [ define_part("cube",
// attach_geom(size=size),
// T=Tlist)];
// attachable(r=r, h=size.z, parts=parts){
// for(T=Tlist) multmatrix(T) cuboid(size);
// children();
// }
// }
// cube_ring(14, 30, 7){
// color("orange")
// attach(TOP,BOT) cuboid([3,52,1]);
// color("lightblue")
// attach_part("cube",1) attach(TOP,BOT) cyl(d1=9,d2=4,h=5);
// color("pink")
// attach_part("cube",-1) attach(TOP,TOP) cyl(d1=9,d2=4,h=5);
// }
function define_part(name, geom, inside=false, T=IDENT) = function define_part(name, geom, inside=false, T=IDENT) =
assert(is_string(name), "\n'name' must be a string.") let(
assert(_is_geometry(geom), "\ngeometry appears invalid.") geom_n = _is_geometry(geom) ? 1 : assert(is_list(geom), "\ngeom is invalid") len(geom),
assert(is_bool(inside), "\n'inside' must be boolean.") T_n = is_matrix(T) ? 1
assert(is_matrix(T,4), "\nT must be a 4×4 transformation matrix.") : assert(is_consistent(T, ident(4)), "\nT must be a transformation or list of transformations")
[name, geom, inside, T]; len(T)
)
assert(geom_n==T_n || geom_n==1 || T_n==1, "\ngeom and T have inconsistent lengths")
let(
n = max(T_n,geom_n),
geom_list = _is_geometry(geom) ? repeat(geom,n) : geom,
T = is_matrix(T) ? repeat(T,n) : T,
inside = is_bool(inside) ? repeat(inside,n)
: assert(is_bool_list(inside,n), str("\ninside must be a boolean list with length ",n))
inside
)
let(
partlist = [for(i=[0:n-1]) [geom_list[i], inside[i], T[i]]]
)
[name, partlist];
@@ -5223,12 +5271,12 @@ function parent() =
// Function: parent_part() // Function: parent_part()
// Topics: Transforms, Attachments, Descriptions // Topics: Transforms, Attachments, Descriptions
// See Also: restore(), parent() // See Also: restore(), parent(), attach_part(), define_part()
// Synopsis: Returns a description (transformation state and attachment geometry) of a part defined by the parent // Synopsis: Returns a description (transformation state and attachment geometry) of a part defined by the parent
// Usage: // Usage:
// PARENT() let( desc = parent_part(name) ) CHILDREN; // PARENT() let( desc = parent_part(name, [ind]) ) CHILDREN;
// Usage: in development releases only // Usage: in development releases only
// PARENT() { desc=parent_part(name); CHILDREN; } // PARENT() { desc=parent_part(name, [ind]); CHILDREN; }
// Description: // Description:
// Returns a description of the parent part with the specified name. You can use this // Returns a description of the parent part with the specified name. 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 // description to create new objects based on the described object or perform computations based on the described object. You can also use it to
@@ -5236,7 +5284,12 @@ function parent() =
// this function to work, and the definition of the variable is scoped to the children of the let module. // 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 // (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_part() returns is likely to be incorrect, even if OpenSCAD // related to transformation operations then the transformation that parent_part() returns is likely to be incorrect, even if OpenSCAD
// continues to run and produces a valid result. // continues to run and produces a valid result. If the chosen part is a compound part then you select which component you want by giving the index, `ind`.
// The index wraps around in both directions, so it will always select some component.
// The default index is zero; the index has no effect for simple parts
// Arguments:
// name = name of part whose description is returned
// ind = index for use with compount parts. Default: 0
// Example(3D): This example defines an object with two parts and then uses `parent_part()` to create a {{prism_connector()}} between the two parts of the object. // Example(3D): This example defines an object with two parts and then uses `parent_part()` to create a {{prism_connector()}} between the two parts of the object.
// $fn=48; // $fn=48;
// module twocyl(d, sep, h, ang=20) // module twocyl(d, sep, h, ang=20)
@@ -5261,13 +5314,9 @@ function parent() =
// parent_part("right"), LEFT, // parent_part("right"), LEFT,
// fillet=1); // fillet=1);
function parent_part(name) = function parent_part(name,ind=0) =
assert(!is_undef($parent_parts), "\nParent does not exist or does not have any parts.") let(part = _get_part(name, ind))
let( [$transform * part[2], part[0]];
ind = search([name], $parent_parts, 1,0)[0]
)
assert(ind!=[], str("\nParent does not have a part named \"",name,"\"."))
[$transform * $parent_parts[ind][3], $parent_parts[ind][1]];
// Module: restore() // Module: restore()

View File

@@ -1540,7 +1540,9 @@ function textured_tile(
// . // .
// Attachment to the rectangular tube places objects on the **outside** of the tube. // Attachment to the rectangular tube places 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" // 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. // to switch goeomtry to the inside.
// Attachable Parts:
// "inside" = The inside of the tube
// Arguments: // Arguments:
// h/l/height/length = The height or length of the rectangular tube. Default: 1 // h/l/height/length = The height or length of the rectangular tube. Default: 1
// size = The outer [X,Y] size of the rectangular tube. // size = The outer [X,Y] size of the rectangular tube.
@@ -2907,6 +2909,8 @@ module zcyl(
// Usage: Rounded and chamfered tubes // Usage: Rounded and chamfered tubes
// tube(..., [rounding=], [irounding=], [orounding=], [rounding1=], [rounding2=], [irounding1=], [irounding2=], [orounding1=], [orounding2=], [teardrop=], [clip_angle=]); // tube(..., [rounding=], [irounding=], [orounding=], [rounding1=], [rounding2=], [irounding1=], [irounding2=], [orounding1=], [orounding2=], [teardrop=], [clip_angle=]);
// tube(..., [chamfer=], [ichamfer=], [ochamfer=], [chamfer1=], [chamfer2=], [ichamfer1=], [ichamfer2=], [ochamfer1=], [ochamfer2=]); // tube(..., [chamfer=], [ichamfer=], [ochamfer=], [chamfer1=], [chamfer2=], [ichamfer1=], [ichamfer2=], [ochamfer1=], [ochamfer2=]);
// Attachable Parts:
// "inside" = The inside of the tube
// Arguments: // Arguments:
// h / l / height / length = height of tube. Default: 1 // h / l / height / length = height of tube. Default: 1
// or = Outer radius of tube. Default: 1 // or = Outer radius of tube. Default: 1

View File

@@ -61,12 +61,14 @@ tag("keep")cube(100, center=true)
You can apply a tag that is not propagated to the children using You can apply a tag that is not propagated to the children using
`tag_this()`. The above example could then be redone: `tag_this()`. The above example could then be redone:
```openscad-3D
diff("hole", "keep") diff("hole", "keep")
tag_this("keep")cube(100, center=true) tag_this("keep")cube(100, center=true)
attach([RIGHT,TOP]) { attach([RIGHT,TOP]) {
cylinder(d=95, h=5); cylinder(d=95, h=5);
tag("hole") cylinder(d=50, h=11, anchor=CTR); tag("hole") cylinder(d=50, h=11, anchor=CTR);
} }
```
You can of course apply `tag()` to several children. You can of course apply `tag()` to several children.