From b17672b71e1bac1696097b6c02aa0b04f0d9c9a3 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Thu, 19 Jun 2025 22:21:11 -0400 Subject: [PATCH 01/10] Add top_edge anchor to wedge and make anchors to degenerate prismoids provide useful edge data. Fix mirror() to always return a 3d transformation --- attachments.scad | 14 ++++++++++---- partitions.scad | 4 ++-- shapes3d.scad | 26 ++++++++++++++++++++++++-- transforms.scad | 2 +- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/attachments.scad b/attachments.scad index d395d903..244d6651 100644 --- a/attachments.scad +++ b/attachments.scad @@ -3985,14 +3985,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 = + 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 +4037,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) diff --git a/partitions.scad b/partitions.scad index 838b616e..5e64edc6 100644 --- a/partitions.scad +++ b/partitions.scad @@ -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); } } diff --git a/shapes3d.scad b/shapes3d.scad index f0d76bc5..52263a51 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -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], @@ -1753,6 +1763,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 +1777,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 +1794,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 +1830,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); diff --git a/transforms.scad b/transforms.scad index c195c84b..e91b4e55 100644 --- a/transforms.scad +++ b/transforms.scad @@ -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); From 77b87040dc4048b129cb3960a66dc286e6070097 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Fri, 20 Jun 2025 18:45:18 -0400 Subject: [PATCH 02/10] add named anchors to linear_sweep --- rounding.scad | 6 ++- shapes3d.scad | 3 +- skin.scad | 112 ++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 95 insertions(+), 26 deletions(-) diff --git a/rounding.scad b/rounding.scad index b01b0380..56e873ec 100644 --- a/rounding.scad +++ b/rounding.scad @@ -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.")) diff --git a/shapes3d.scad b/shapes3d.scad index 52263a51..12b8cb48 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -851,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. diff --git a/skin.scad b/skin.scad index 4886971c..1a95b3d3 100644 --- a/skin.scad +++ b/skin.scad @@ -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.") @@ -858,9 +915,20 @@ function linear_sweep( 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) ]), + 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() From cc169e0e23775783857cb65547b53a6e0fafa1d6 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 21 Jun 2025 00:28:40 -0400 Subject: [PATCH 03/10] fix anchoring for textured case --- skin.scad | 106 +++++++++++++++++++++++++++--------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/skin.scad b/skin.scad index 1a95b3d3..3ffa8ba3 100644 --- a/skin.scad +++ b/skin.scad @@ -861,60 +861,60 @@ 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( - 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( - caps = is_bool(caps) ? [caps,caps] : caps, - anchor = center==true? "origin" : - center == false? "original_base" : - default(anchor, "original_base"), + h = one_defined([h, height,l,length],"h,height,l,length",dflt=1), 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), - trgns = [ - for (rgn = regions) [ - for (path = rgn) let( - p = list_unwrap(path), - path = is_undef(maxseg)? p : [ - for (seg = pair(p,true)) each - let( steps = ceil(norm(seg.y - seg.x) / maxseg) ) - lerpn(seg.x, seg.y, steps, false) - ] - ) apply(topmat, path) - ] - ], - vnf = vnf_join([ - for (rgn = regions) - for (pathnum = idx(rgn)) let( - p = list_unwrap(rgn[pathnum]), - path = is_undef(maxseg)? p : [ - for (seg=pair(p,true)) each - let(steps=ceil(norm(seg.y-seg.x)/maxseg)) - lerpn(seg.x, seg.y, steps, false) - ], - verts = [ - for (i=[0:1:slices]) let( - u = i / slices, - scl = lerp([1,1], scale, u), - ang = lerp(0, -twist, u), - off = lerp([0,0,-h/2], point3d(shift,h/2), u), - m = move(off) * scale(scl) * rot(ang) - ) apply(m, path3d(path)) - ] - ) 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 = !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) + : let( + caps = is_bool(caps) ? [caps,caps] : caps, + anchor = center==true? "origin" : + center == false? "original_base" : + default(anchor, "original_base"), + slices = default(slices, max(1,ceil(abs(twist)/5))), + scale = is_num(scale)? [scale,scale] : point2d(scale), + topmat = move(shift) * scale(scale) * rot(-twist), + trgns = [ + for (rgn = regions) [ + for (path = rgn) let( + p = list_unwrap(path), + path = is_undef(maxseg)? p : [ + for (seg = pair(p,true)) each + let( steps = ceil(norm(seg.y - seg.x) / maxseg) ) + lerpn(seg.x, seg.y, steps, false) + ] + ) apply(topmat, path) + ] + ], + vnf = vnf_join([ + for (rgn = regions) + for (pathnum = idx(rgn)) let( + p = list_unwrap(rgn[pathnum]), + path = is_undef(maxseg)? p : [ + for (seg=pair(p,true)) each + let(steps=ceil(norm(seg.y-seg.x)/maxseg)) + lerpn(seg.x, seg.y, steps, false) + ], + verts = [ + for (i=[0:1:slices]) let( + u = i / slices, + scl = lerp([1,1], scale, u), + ang = lerp(0, -twist, u), + off = lerp([0,0,-h/2], point3d(shift,h/2), u), + m = move(off) * scale(scl) * rot(ang) + ) apply(m, path3d(path)) + ] + ) 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 = [ From 7449ba6b1052491e2bd3f06233edb617b080ba9c Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 21 Jun 2025 10:51:09 -0400 Subject: [PATCH 04/10] add attachable parts section and links between sections --- attachments.scad | 10 +- tutorials/Attachment-Align.md | 4 + tutorials/Attachment-Attach.md | 4 + tutorials/Attachment-Basic-Positioning.md | 4 + tutorials/Attachment-Color.md | 4 + tutorials/Attachment-Edge-Profiling.md | 4 +- tutorials/Attachment-Making.md | 139 +++++++++++++++++++ tutorials/Attachment-Overview.md | 2 + tutorials/Attachment-Position.md | 4 + tutorials/Attachment-Relative-Positioning.md | 4 + tutorials/Attachment-Tags.md | 4 + 11 files changed, 178 insertions(+), 5 deletions(-) diff --git a/attachments.scad b/attachments.scad index 244d6651..247804a5 100644 --- a/attachments.scad +++ b/attachments.scad @@ -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 @@ -4168,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] : @@ -4448,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 { diff --git a/tutorials/Attachment-Align.md b/tutorials/Attachment-Align.md index c49aa2f5..2430258d 100644 --- a/tutorials/Attachment-Align.md +++ b/tutorials/Attachment-Align.md @@ -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) diff --git a/tutorials/Attachment-Attach.md b/tutorials/Attachment-Attach.md index 40a3c682..a968cc06 100644 --- a/tutorials/Attachment-Attach.md +++ b/tutorials/Attachment-Attach.md @@ -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) diff --git a/tutorials/Attachment-Basic-Positioning.md b/tutorials/Attachment-Basic-Positioning.md index e800b581..d98f7829 100644 --- a/tutorials/Attachment-Basic-Positioning.md +++ b/tutorials/Attachment-Basic-Positioning.md @@ -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 square([40,30], anchor=BACK+LEFT, spin=30); ``` + +[Next: Relative Positioning of Children](Tutorial-Attachment-Relative-Positioning) diff --git a/tutorials/Attachment-Color.md b/tutorials/Attachment-Color.md index ce5c5e82..71c0bce6 100644 --- a/tutorials/Attachment-Color.md +++ b/tutorials/Attachment-Color.md @@ -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) diff --git a/tutorials/Attachment-Edge-Profiling.md b/tutorials/Attachment-Edge-Profiling.md index d3af758f..a8f03388 100644 --- a/tutorials/Attachment-Edge-Profiling.md +++ b/tutorials/Attachment-Edge-Profiling.md @@ -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) diff --git a/tutorials/Attachment-Making.md b/tutorials/Attachment-Making.md index a1576872..d7944e97 100644 --- a/tutorials/Attachment-Making.md +++ b/tutorials/Attachment-Making.md @@ -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 +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 half +related to a cuboid, so maybe sometimes you prefer to use anchors +based on the bounding box. You could create a module with bounding +box anchors like this, where we have had to explicitly center the VNF +to make it work with the prismoid type anchoring: + +```openscad-3D +include +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 +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 +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 set `inside=true` +when creating the part with `define_part()`. This cause `align()` and +`attach()` to invert the meaning of the `inside` parameter so that +objects will attach on the inside by default. diff --git a/tutorials/Attachment-Overview.md b/tutorials/Attachment-Overview.md index d82ff46b..285fd4a0 100644 --- a/tutorials/Attachment-Overview.md +++ b/tutorials/Attachment-Overview.md @@ -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) + diff --git a/tutorials/Attachment-Position.md b/tutorials/Attachment-Position.md index 740d9a98..58c23cae 100644 --- a/tutorials/Attachment-Position.md +++ b/tutorials/Attachment-Position.md @@ -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) diff --git a/tutorials/Attachment-Relative-Positioning.md b/tutorials/Attachment-Relative-Positioning.md index 6e08f625..04e0c6bb 100644 --- a/tutorials/Attachment-Relative-Positioning.md +++ b/tutorials/Attachment-Relative-Positioning.md @@ -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) diff --git a/tutorials/Attachment-Tags.md b/tutorials/Attachment-Tags.md index 5fa0a745..b326f512 100644 --- a/tutorials/Attachment-Tags.md +++ b/tutorials/Attachment-Tags.md @@ -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) From fd0f38a32bc3593422a6e4622084145b66ca04ec Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 21 Jun 2025 10:59:08 -0400 Subject: [PATCH 05/10] link correction --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db8fc62c..cf2ba905 100644 --- a/README.md +++ b/README.md @@ -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. From 48343f06d2198ff115080d5d41c6d4224b04b2d0 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 21 Jun 2025 12:40:32 -0400 Subject: [PATCH 06/10] actually add parts section --- tutorials/Attachment-Parts.md | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tutorials/Attachment-Parts.md diff --git a/tutorials/Attachment-Parts.md b/tutorials/Attachment-Parts.md new file mode 100644 index 00000000..954ced3b --- /dev/null +++ b/tutorials/Attachment-Parts.md @@ -0,0 +1,50 @@ +# 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 +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 +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 +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); + } +} +``` + + + From cf2f82a56f9ceb2b8c337f7e1210bbf7ff3e9b8d Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 21 Jun 2025 12:44:57 -0400 Subject: [PATCH 07/10] minor edits --- tutorials/Attachment-Making.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tutorials/Attachment-Making.md b/tutorials/Attachment-Making.md index d7944e97..023ceedb 100644 --- a/tutorials/Attachment-Making.md +++ b/tutorials/Attachment-Making.md @@ -558,10 +558,10 @@ module tetrahedron(base, height, spin=0, anchor=FWD+LEFT+BOT, orient=UP) tetrahedron(20,18) show_anchors(); ``` -For this module we have used VNF anchors, but this tetrahedron is half -related to a cuboid, so maybe sometimes you prefer to use anchors -based on the bounding box. You could create a module with bounding -box anchors like this, where we have had to explicitly center the VNF +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 From df4f3acca821659c9aec3df26f91063fa6ff4aff Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 21 Jun 2025 12:46:11 -0400 Subject: [PATCH 08/10] more edits --- tutorials/Attachment-Making.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorials/Attachment-Making.md b/tutorials/Attachment-Making.md index 023ceedb..9ed70fbf 100644 --- a/tutorials/Attachment-Making.md +++ b/tutorials/Attachment-Making.md @@ -625,8 +625,8 @@ 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 +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 From 23b8239e8c9185f14712ed56d62d98c4cb21d910 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 21 Jun 2025 12:47:49 -0400 Subject: [PATCH 09/10] even more --- tutorials/Attachment-Making.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorials/Attachment-Making.md b/tutorials/Attachment-Making.md index 9ed70fbf..22c2b2f0 100644 --- a/tutorials/Attachment-Making.md +++ b/tutorials/Attachment-Making.md @@ -666,7 +666,7 @@ 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 set `inside=true` -when creating the part with `define_part()`. This cause `align()` and +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. From f67955b3222987f207be670e69626a0dccdb053d Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 21 Jun 2025 13:41:55 -0400 Subject: [PATCH 10/10] tutorial fixes --- tutorials/Attachment-Parts.md | 10 +++++----- tutorials/Attachment-Tags.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tutorials/Attachment-Parts.md b/tutorials/Attachment-Parts.md index 954ced3b..f128c736 100644 --- a/tutorials/Attachment-Parts.md +++ b/tutorials/Attachment-Parts.md @@ -1,3 +1,5 @@ +[Prev: Using attach()](Tutorial-Attachment-Attach) + # Attachment Parts Some objects provide named attachable parts that you can select @@ -10,21 +12,20 @@ Here is a tube with its anchors shown: include 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 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 @@ -46,5 +47,4 @@ tube(id=20,h=15,wall=3){ } ``` - - +[Next: Using Color with Attachments](Tutorial-Attachment-Color) diff --git a/tutorials/Attachment-Tags.md b/tutorials/Attachment-Tags.md index b326f512..07bb65dd 100644 --- a/tutorials/Attachment-Tags.md +++ b/tutorials/Attachment-Tags.md @@ -1,4 +1,4 @@ -[Prev: Using color with attachments](Tutorial-Attachment-Color) +[Prev: Using Color with Attachments](Tutorial-Attachment-Color) # Tagged Operations