Merge pull request #1072 from adrianVmariano/master

add trapezoid anchor override and fix trapezoid and rect perimeter anchoring
This commit is contained in:
Revar Desmera 2023-03-10 16:51:08 -08:00 committed by GitHub
commit f7beec7517
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 265 additions and 145 deletions

View File

@ -1628,7 +1628,7 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
// Module: attachable()
//
// Usage: Square/Trapezoid Geometry
// attachable(anchor, spin, two_d=true, size=, [size2=], [shift=], ...) {OBJECT; children();}
// attachable(anchor, spin, two_d=true, size=, [size2=], [shift=], [override=], ...) {OBJECT; children();}
// Usage: Circle/Oval Geometry
// attachable(anchor, spin, two_d=true, r=|d=, ...) {OBJECT; children();}
// Usage: 2D Path/Polygon Geometry
@ -1708,6 +1708,7 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
// anchors = If given as a list of anchor points, allows named anchor points.
// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D)
// axis = The vector pointing along the axis of a geometry. Default: UP
// override = Function that takes an anchor and returns a pair `[position,direction]` to use for that anchor to override the normal one. You can also supply a lookup table that is a list of `[anchor, [position, direction]]` entries. If the direction/position that is returned is undef then the default will be used.
// geom = If given, uses the pre-defined (via {{attach_geom()}} geometry.
//
// Side Effects:
@ -1892,7 +1893,7 @@ module attachable(
offset=[0,0,0],
anchors=[],
two_d=false,
axis=UP,
axis=UP,override,
geom
) {
dummy1 =
@ -1913,7 +1914,7 @@ module attachable(
d=d, d1=d1, d2=d2, l=l,
vnf=vnf, region=region, extent=extent,
cp=cp, offset=offset, anchors=anchors,
two_d=two_d, axis=axis
two_d=two_d, axis=axis, override=override
);
m = _attach_transform(anchor,spin,orient,geom);
multmatrix(m) {
@ -2032,7 +2033,7 @@ function reorient(
cp=[0,0,0],
anchors=[],
two_d=false,
axis=UP,
axis=UP, override,
geom,
p=undef
) =
@ -2056,7 +2057,7 @@ function reorient(
d=d, d1=d1, d2=d2, l=l,
vnf=vnf, region=region, extent=extent,
cp=cp, offset=offset, anchors=anchors,
two_d=two_d, axis=axis
two_d=two_d, axis=axis, override=override
),
$attach_to = undef
) _attach_transform(anchor,spin,orient,geom,p);
@ -2130,6 +2131,7 @@ function named_anchor(name, pos, orient=UP, spin=0) = [name, pos, orient, spin];
// anchors = If given as a list of anchor points, allows named anchor points.
// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D)
// axis = The vector pointing along the axis of a geometry. Default: UP
// override = Function that takes an anchor and returns a pair `[position,direction]` to use for that anchor to override the normal one. You can also supply a lookup table that is a list of `[anchor, [position, direction]]` entries. If the direction/position that is returned is undef then the default will be used.
//
// Example(NORENDER): Null/Point Shape
// geom = attach_geom();
@ -2177,7 +2179,7 @@ function named_anchor(name, pos, orient=UP, spin=0) = [name, pos, orient, spin];
// geom = attach_geom(two_d=true, size=size);
//
// Example(NORENDER): 2D Trapezoidal Shape
// geom = attach_geom(two_d=true, size=[x1,y], size2=x2, shift=shift);
// geom = attach_geom(two_d=true, size=[x1,y], size2=x2, shift=shift, override=override);
//
// Example(NORENDER): 2D Circular Shape
// geom = attach_geom(two_d=true, r=r);
@ -2197,6 +2199,13 @@ function named_anchor(name, pos, orient=UP, spin=0) = [name, pos, orient, spin];
// Example(NORENDER): Extruded Region, Anchored by Intersection
// geom = attach_geom(region=region, l=length, extent=false);
//
function _local_struct_val(struct, key)=
assert(is_def(key),"key is missing")
let(ind = search([key],struct)[0])
ind == [] ? undef : struct[ind][1];
function attach_geom(
size, size2,
shift, scale, twist,
@ -2207,7 +2216,7 @@ function attach_geom(
offset=[0,0,0],
anchors=[],
two_d=false,
axis=UP
axis=UP, override
) =
assert(is_bool(extent))
assert(is_vector(cp) || is_string(cp))
@ -2219,12 +2228,15 @@ function attach_geom(
two_d? (
let(
size2 = default(size2, size.x),
shift = default(shift, 0)
shift = default(shift, 0),
over_f = is_undef(override) ? function(anchor) [undef,undef]
: is_func(override) ? override
: function(anchor) _local_struct_val(override,anchor)
)
assert(is_vector(size,2))
assert(is_num(size2))
assert(is_num(shift))
["trapezoid", point2d(size), size2, shift, cp, offset, anchors]
["trapezoid", point2d(size), size2, shift, over_f, cp, offset, anchors]
) : (
let(
size2 = default(size2, point2d(size)),
@ -2637,7 +2649,7 @@ function _find_anchor(anchor, geom) =
mpt = approx(point2d(anchor),[0,0])? [maxx,0,0] : avep,
pos = point3d(cp) + rot(from=RIGHT, to=anchor, p=mpt)
) [anchor, pos, anchor, oang]
) : type == "trapezoid"? ( //size, size2, shift
) : type == "trapezoid"? ( //size, size2, shift, override
let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
assert(all_comps_good, "All components of an anchor for a rectangle/trapezoid must be -1, 0, or 1")
let(
@ -2646,9 +2658,12 @@ function _find_anchor(anchor, geom) =
u = (anchor.y+1)/2, // 0<=u<=1
frpt = [size.x/2*anchor.x, -size.y/2],
bkpt = [size2/2*anchor.x+shift, size.y/2],
pos = point2d(cp) + lerp(frpt, bkpt, u) + point2d(offset),
override = geom[4](anchor),
pos = default(override[0],point2d(cp) + lerp(frpt, bkpt, u) + point2d(offset)),
svec = point3d(line_normal(bkpt,frpt)*anchor.x),
vec = anchor.y < 0? (
vec = is_def(override[1]) ? override[1]
:
anchor.y < 0? (
anchor.x == 0? FWD :
size.x == 0? unit(-[shift,size.y], FWD) :
unit((point3d(svec) + FWD) / 2, FWD)
@ -2658,6 +2673,7 @@ function _find_anchor(anchor, geom) =
anchor.x == 0? BACK :
size2 == 0? unit([shift,size.y], BACK) :
unit((point3d(svec) + BACK) / 2, BACK)
)
) [anchor, pos, vec, 0]
) : type == "ellipse"? ( //r

View File

@ -2296,24 +2296,27 @@ module hull_points(points, fast=false) {
no_children($children);
check = assert(is_path(points))
assert(len(points)>=3, "Point list must contain 3 points");
if (len(points[0])==2)
hull() polygon(points=points);
else {
if (fast) {
extra = len(points)%3;
faces = [
[for(i=[0:1:extra+2])i], // If vertex count not divisible by 3, combine extras with first 3
for(i=[extra+3:3:len(points)-3])[i,i+1,i+2]
];
hull() polyhedron(points=points, faces=faces);
} else {
faces = hull(points);
if (is_num(faces[0])){
if (len(faces)<=2) echo("Hull contains only two points");
else polyhedron(points=points, faces=[faces]);
attachable(){
if (len(points[0])==2)
hull() polygon(points=points);
else {
if (fast) {
extra = len(points)%3;
faces = [
[for(i=[0:1:extra+2])i], // If vertex count not divisible by 3, combine extras with first 3
for(i=[extra+3:3:len(points)-3])[i,i+1,i+2]
];
hull() polyhedron(points=points, faces=faces);
} else {
faces = hull(points);
if (is_num(faces[0])){
if (len(faces)<=2) echo("Hull contains only two points");
else polyhedron(points=points, faces=[faces]);
}
else polyhedron(points=points, faces=faces);
}
else polyhedron(points=points, faces=faces);
}
union();
}
}

View File

@ -112,6 +112,9 @@ module square(size=1, center, anchor, spin) {
// Example(2D): "perim" Anchors
// rect([40,30], rounding=10, atype="perim")
// show_anchors();
// Example(2D): "perim" Anchors
// rect([40,30], rounding=[-10,-8,-3,-7], atype="perim")
// show_anchors();
// Example(2D): Mixed Chamferring and Rounding
// rect([40,30],rounding=[5,0,10,0],chamfer=[0,8,0,15],$fa=1,$fs=1);
// Example(2D): Called as Function
@ -120,52 +123,49 @@ module square(size=1, center, anchor, spin) {
// move_copies(path) color("blue") circle(d=2,$fn=8);
module rect(size=1, rounding=0, atype="box", chamfer=0, anchor=CENTER, spin=0) {
errchk = assert(in_list(atype, ["box", "perim"]));
size = is_num(size)? [size,size] : point2d(size);
size = force_list(size,2);
if (rounding==0 && chamfer==0) {
attachable(anchor, spin, two_d=true, size=size) {
square(size, center=true);
children();
}
} else {
pts = rect(size=size, rounding=rounding, chamfer=chamfer);
if (atype == "perim") {
attachable(anchor, spin, two_d=true, path=pts) {
pts_over = rect(size=size, rounding=rounding, chamfer=chamfer, atype=atype, _return_override=true);
pts = pts_over[0];
override = pts_over[1];
attachable(anchor, spin, two_d=true, size=size,override=override) {
polygon(pts);
children();
}
} else {
attachable(anchor, spin, two_d=true, size=size) {
polygon(pts);
children();
}
}
}
}
function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0) =
assert(is_num(size) || is_vector(size))
assert(is_num(chamfer) || len(chamfer)==4)
assert(is_num(rounding) || len(rounding)==4)
function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0, _return_override) =
assert(is_num(size) || is_vector(size,2))
assert(is_num(chamfer) || is_vector(chamfer,4))
assert(is_num(rounding) || is_vector(rounding,4))
assert(in_list(atype, ["box", "perim"]))
let(
anchor=point2d(anchor),
size = is_num(size)? [size,size] : point2d(size),
complex = rounding!=0 || chamfer!=0
size = force_list(size,2),
chamfer = force_list(chamfer,4),
rounding = force_list(rounding,4)
)
(rounding==0 && chamfer==0)? let(
path = [
[ size.x/2, -size.y/2],
[-size.x/2, -size.y/2],
[-size.x/2, size.y/2],
[ size.x/2, size.y/2]
]
)
rot(spin, p=move(-v_mul(anchor,size/2), p=path)) :
all_zero(concat(chamfer,rounding),0) ?
let(
path = [
[ size.x/2, -size.y/2],
[-size.x/2, -size.y/2],
[-size.x/2, size.y/2],
[ size.x/2, size.y/2]
]
)
rot(spin, p=move(-v_mul(anchor,size/2), p=path))
:
assert(all_zero(v_mul(chamfer,rounding),0), "Cannot specify chamfer and rounding at the same corner")
let(
chamfer = is_list(chamfer)? chamfer : [for (i=[0:3]) chamfer],
rounding = is_list(rounding)? rounding : [for (i=[0:3]) rounding],
quadorder = [3,2,1,0],
quadpos = [[1,1],[-1,1],[-1,-1],[1,-1]],
eps = 1e-9,
@ -176,7 +176,7 @@ function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0)
assert(insets_x <= size.x, "Requested roundings and/or chamfers exceed the rect width.")
assert(insets_y <= size.y, "Requested roundings and/or chamfers exceed the rect height.")
let(
path = [
corners = [
for(i = [0:3])
let(
quad = quadorder[i],
@ -191,13 +191,20 @@ function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0)
abs(qround) >= eps? [for (j=[0:1:cverts]) let(a=90-j*step) v_mul(polar_to_xy(abs(qinset),a),[sign(qinset),1])] :
[[0,0]],
qfpts = [for (p=qpts) v_mul(p,qpos)],
qrpts = qpos.x*qpos.y < 0? reverse(qfpts) : qfpts
)
each move(cp, p=qrpts)
]
) complex && atype=="perim"?
reorient(anchor,spin, two_d=true, path=path, p=path) :
reorient(anchor,spin, two_d=true, size=size, p=path);
qrpts = qpos.x*qpos.y < 0? reverse(qfpts) : qfpts,
cornerpt = atype=="box" || (qround==0 && qchamf==0) ? undef
: qround<0 || qchamf<0 ? [[0,-qpos.y*min(qround,qchamf)]]
: [for(seg=pair(qrpts)) let(isect=line_intersection(seg, [[0,0],qpos],SEGMENT,LINE)) if (is_def(isect) && isect!=seg[0]) isect]
)
assert(is_undef(cornerpt) || len(cornerpt)==1,"Cannot find corner point to anchor")
[move(cp, p=qrpts), is_undef(cornerpt)? undef : move(cp,p=cornerpt[0])]
],
path = flatten(column(corners,0)),
override = [for(i=[0:3])
let(quad=quadorder[i])
if (is_def(corners[i][1])) [quadpos[quad], [corners[i][1], min(chamfer[quad],rounding[quad])<0 ? [quadpos[quad].x,0] : undef]]]
) _return_override ? [reorient(anchor,spin, two_d=true, size=size, p=path, override=override), override]
: reorient(anchor,spin, two_d=true, size=size, p=path, override=override);
// Function&Module: circle()
@ -868,8 +875,12 @@ module right_triangle(size=[1,1], center, anchor, spin=0) {
// rounding = The rounding radius for the corners. If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding)
// chamfer = The Length of the chamfer faces at the corners. If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no chamfer)
// flip = If true, negative roundings and chamfers will point forward and back instead of left and right. Default: `false`.
// atype = The type of anchoring to use with `anchor=`. Valid opptions are "box" and "perim". This lets you choose between putting anchors on the rounded or chamfered perimeter, or on the square bounding box of the shape. Default: "box"
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// Anchor Types:
// box = Anchor is with respect to the rectangular bounding box of the shape.
// perim = Anchors are placed along the rounded or chamfered perimeter of the shape.
// Examples(2D):
// trapezoid(h=30, w1=40, w2=20);
// trapezoid(h=25, w1=20, w2=35);
@ -893,9 +904,17 @@ module right_triangle(size=[1,1], center, anchor, spin=0) {
// trapezoid(h=30, w1=60, w2=40, rounding=-5, flip=true);
// Example(2D): Mixed Chamfering and Rounding
// trapezoid(h=30, w1=60, w2=40, rounding=[5,0,-10,0],chamfer=[0,8,0,-15],$fa=1,$fs=1);
// Example(2D): default anchors for roundings
// trapezoid(h=30, w1=100, ang=[66,44],rounding=5) show_anchors();
// Example(2D): default anchors for negative roundings are still at the trapezoid corners
// trapezoid(h=30, w1=100, ang=[66,44],rounding=-5) show_anchors();
// Example(2D): "perim" anchors are at the tips of negative roundings
// trapezoid(h=30, w1=100, ang=[66,44],rounding=-5, atype="perim") show_anchors();
// Example(2D): They point the other direction if you flip them
// trapezoid(h=30, w1=100, ang=[66,44],rounding=-5, atype="perim",flip=true) show_anchors();
// Example(2D): Called as Function
// stroke(closed=true, trapezoid(h=30, w1=40, w2=20));
function trapezoid(h, w1, w2, ang, shift, chamfer=0, rounding=0, flip=false, anchor=CENTER, spin=0, angle) =
function trapezoid(h, w1, w2, ang, shift, chamfer=0, rounding=0, flip=false, anchor=CENTER, spin=0, ,atype="box", _return_override, angle) =
assert(is_undef(angle), "The angle parameter has been replaced by ang, which specifies trapezoid interior angle")
assert(is_undef(h) || is_finite(h))
assert(is_undef(w1) || is_finite(w1))
@ -919,11 +938,12 @@ function trapezoid(h, w1, w2, ang, shift, chamfer=0, rounding=0, flip=false, anc
w1 = is_def(w1)? w1 : w2 + x1 + x2,
w2 = is_def(w2)? w2 : w1 - x1 - x2,
shift = first_defined([shift,(x1-x2)/2]),
chamfs = is_num(chamfer)? [for (i=[0:3]) chamfer] :
assert(len(chamfer)==4) chamfer,
rounds = is_num(rounding)? [for (i=[0:3]) rounding] :
assert(len(rounding)==4) rounding,
srads = [for (i=[0:3]) rounds[i]? rounds[i] : chamfs[i]],
chamfer = force_list(chamfer,4),
rounding = force_list(rounding,4)
)
assert(all_zero(v_mul(chamfer,rounding),0), "Cannot specify chamfer and rounding at the same corner")
let(
srads = chamfer+rounding,
rads = v_abs(srads)
)
assert(w1>=0 && w2>=0 && h>0, "Degenerate trapezoid geometry.")
@ -947,65 +967,70 @@ function trapezoid(h, w1, w2, ang, shift, chamfer=0, rounding=0, flip=false, anc
b = a + [hyps[i] * qdirs[i].x * (srads[i]<0 && !flip? 1 : -1), 0]
) b
],
cpath = [
each (
corners = [
(
let(i = 0)
rads[i] == 0? [base[i]] :
srads[i] > 0? arc(n=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[angs[i], 90], r=rads[i]) :
flip? arc(n=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[angs[i],-90], r=rads[i]) :
arc(n=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[180+angs[i],90], r=rads[i])
rads[i] == 0? [base[i]]
: srads[i] > 0? arc(n=rounding[i]?undef:2, cp=base[i]+offs[i], angle=[angs[i], 90], r=rads[i])
: flip? arc(n=rounding[i]?undef:2, cp=base[i]+offs[i], angle=[angs[i],-90], r=rads[i])
: arc(n=rounding[i]?undef:2, cp=base[i]+offs[i], angle=[180+angs[i],90], r=rads[i])
),
each (
(
let(i = 1)
rads[i] == 0? [base[i]] :
srads[i] > 0? arc(n=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[90,180+angs[i]], r=rads[i]) :
flip? arc(n=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[270,180+angs[i]], r=rads[i]) :
arc(n=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[90,angs[i]], r=rads[i])
rads[i] == 0? [base[i]]
: srads[i] > 0? arc(n=rounding[i]?undef:2, cp=base[i]+offs[i], angle=[90,180+angs[i]], r=rads[i])
: flip? arc(n=rounding[i]?undef:2, cp=base[i]+offs[i], angle=[270,180+angs[i]], r=rads[i])
: arc(n=rounding[i]?undef:2, cp=base[i]+offs[i], angle=[90,angs[i]], r=rads[i])
),
each (
(
let(i = 2)
rads[i] == 0? [base[i]] :
srads[i] > 0? arc(n=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[180+angs[i],270], r=rads[i]) :
flip? arc(n=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[180+angs[i],90], r=rads[i]) :
arc(n=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[angs[i],-90], r=rads[i])
rads[i] == 0? [base[i]]
: srads[i] > 0? arc(n=rounding[i]?undef:2, cp=base[i]+offs[i], angle=[180+angs[i],270], r=rads[i])
: flip? arc(n=rounding[i]?undef:2, cp=base[i]+offs[i], angle=[180+angs[i],90], r=rads[i])
: arc(n=rounding[i]?undef:2, cp=base[i]+offs[i], angle=[angs[i],-90], r=rads[i])
),
each (
(
let(i = 3)
rads[i] == 0? [base[i]] :
srads[i] > 0? arc(n=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[-90,angs[i]], r=rads[i]) :
flip? arc(n=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[90,angs[i]], r=rads[i]) :
arc(n=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[270,180+angs[i]], r=rads[i])
rads[i] == 0? [base[i]]
: srads[i] > 0? arc(n=rounding[i]?undef:2, cp=base[i]+offs[i], angle=[-90,angs[i]], r=rads[i])
: flip? arc(n=rounding[i]?undef:2, cp=base[i]+offs[i], angle=[90,angs[i]], r=rads[i])
: arc(n=rounding[i]?undef:2, cp=base[i]+offs[i], angle=[270,180+angs[i]], r=rads[i])
),
],
path = reverse(cpath)
) true //simple // force regular anchoring
? reorient(anchor,spin, two_d=true, size=[w1,h], size2=w2, shift=shift, p=path)
: reorient(anchor,spin, two_d=true, path=path, p=path);
path = reverse(flatten(corners)),
override = [for(i=[0:3])
if (atype!="box" && srads[i]!=0)
srads[i]>0?
let(dir = unit(base[i]-select(base,i-1)) + unit(base[i]-select(base,i+1)),
pt=[for(seg=pair(corners[i])) let(isect=line_intersection(seg, [base[i],base[i]+dir],SEGMENT,LINE))
if (is_def(isect) && isect!=seg[0]) isect]
)
[qdirs[i], [pt[0], undef]]
: flip?
let( dir=unit(base[i] - select(base,i+(i%2==0?-1:1))))
[qdirs[i], [select(corners[i],i%2==0?0:-1), dir]]
: let( dir = [qdirs[i].x,0])
[qdirs[i], [select(corners[i],i%2==0?-1:0), dir]]]
) _return_override ? [reorient(anchor,spin, two_d=true, size=[w1,h], size2=w2, shift=shift, p=path, override=override),override]
: reorient(anchor,spin, two_d=true, size=[w1,h], size2=w2, shift=shift, p=path, override=override);
module trapezoid(h, w1, w2, ang, shift, chamfer=0, rounding=0, flip=false, anchor=CENTER, spin=0, angle) {
path = trapezoid(h=h, w1=w1, w2=w2, ang=ang, shift=shift, chamfer=chamfer, rounding=rounding, flip=flip, angle=angle);
union() {
simple = true; //chamfer==0 && rounding==0; // force "normal" anchoring for now
ang = force_list(ang,2);
h = is_def(h)? h : (w1-w2) * sin(ang[0]) * sin(ang[1]) / sin(ang[0]+ang[1]);
x1 = is_undef(ang[0]) || ang[0]==90 ? 0 : h/tan(ang[0]);
x2 = is_undef(ang[1]) || ang[1]==90 ? 0 : h/tan(ang[1]);
w1 = is_def(w1)? w1 : w2 + x1 + x2;
w2 = is_def(w2)? w2 : w1 - x1 - x2;
shift = first_defined([shift,(x1-x2)/2]);
if (simple) {
attachable(anchor,spin, two_d=true, size=[w1,h], size2=w2, shift=shift) {
polygon(path);
children();
}
} else {
attachable(anchor,spin, two_d=true, path=path) {
polygon(path);
children();
}
}
module trapezoid(h, w1, w2, ang, shift, chamfer=0, rounding=0, flip=false, anchor=CENTER, spin=0, atype="box", angle) {
path_over = trapezoid(h=h, w1=w1, w2=w2, ang=ang, shift=shift, chamfer=chamfer, rounding=rounding, flip=flip, angle=angle,atype=atype,_return_override=true);
path=path_over[0];
override = path_over[1];
ang = force_list(ang,2);
h = is_def(h)? h : (w1-w2) * sin(ang[0]) * sin(ang[1]) / sin(ang[0]+ang[1]);
x1 = is_undef(ang[0]) || ang[0]==90 ? 0 : h/tan(ang[0]);
x2 = is_undef(ang[1]) || ang[1]==90 ? 0 : h/tan(ang[1]);
w1 = is_def(w1)? w1 : w2 + x1 + x2;
w2 = is_def(w2)? w2 : w1 - x1 - x2;
shift = first_defined([shift,(x1-x2)/2]);
attachable(anchor,spin, two_d=true, size=[w1,h], size2=w2, shift=shift, override=override) {
polygon(path);
children();
}
}

View File

@ -838,6 +838,11 @@ function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
// outside, and an inside rounding is calculated that will maintain constant width
// if your wall thickness is uniform. If the wall thickness is not uniform, the default
// inside rounding is calculated based on the smaller of the two wall thicknesses.
// Note that the values of the more specific chamfers and roundings inherit from the
// 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.
// 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.
@ -907,8 +912,8 @@ function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
// Example: Mixing Chamfer and Rounding
// rect_tube(
// size=100, wall=10, h=30,
// chamfer=[0,5,0,10], ichamfer=0,
// rounding=[5,0,10,0], irounding=0
// chamfer=[0,10,0,20],
// rounding=[10,0,20,0]
// );
// Example: Really Mixing It Up
// rect_tube(
@ -919,23 +924,29 @@ function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
// rounding1=[5,0,10,0], irounding1=[3,0,8,0],
// rounding2=[0,5,0,10], irounding2=[0,3,0,8]
// );
// Example: Some interiors chamfered, others with default rounding
// rect_tube(
// size=100, wall=10, h=30,
// rounding=[0,10,20,30], ichamfer=[8,8,undef,undef]
// );
function _rect_tube_rounding(factor,ir,r,size,isize) =
let(wall = min(size-isize)/2*factor
)
is_def(ir) ? ir
: is_undef(r) ? undef
: is_num(r) ? max(0,r-wall)
: [for(val=r) max(0,val-wall)];
function _rect_tube_rounding(factor,ir,r,alternative,size,isize) =
let(wall = min(size-isize)/2*factor)
[for(i=[0:3])
is_def(ir[i]) ? ir[i]
: is_undef(alternative[i]) ? max(0,r[i]-wall)
: 0
];
module rect_tube(
h, size, isize, center, shift=[0,0],
wall, size1, size2, isize1, isize2,
rounding=0, rounding1, rounding2,
irounding, irounding1, irounding2,
irounding=undef, irounding1=undef, irounding2=undef,
chamfer=0, chamfer1, chamfer2,
ichamfer, ichamfer1, ichamfer2,
ichamfer=undef, ichamfer1=undef, ichamfer2=undef,
anchor, spin=0, orient=UP,
l, length, height
) {
@ -976,12 +987,6 @@ module rect_tube(
(is_def(wall) && is_def(s2))? (s2-2*[wall,wall]) :
undef;
checks2 =
assert(is_num(rounding) || is_vector(rounding,4), "rounding must be a number or 4-vector")
assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "rounding1 must be a number or 4-vector")
assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "rounding2 must be a number or 4-vector")
assert(is_undef(irounding) || is_num(irounding) || is_vector(irounding,4), "irounding must be a number or 4-vector")
assert(is_undef(irounding1) || is_num(irounding1) || is_vector(irounding1,4), "irounding1 must be a number or 4-vector")
assert(is_undef(irounding2) || is_num(irounding2) || is_vector(irounding2,4), "irounding2 must be a number or 4-vector")
assert(wall==undef || is_num(wall))
assert(size1!=undef, "Bad size/size1 argument.")
assert(size2!=undef, "Bad size/size2 argument.")
@ -990,19 +995,59 @@ module rect_tube(
assert(isize1.x < size1.x, "Inner size is larger than outer size.")
assert(isize1.y < size1.y, "Inner size is larger than outer size.")
assert(isize2.x < size2.x, "Inner size is larger than outer size.")
assert(isize2.y < size2.y, "Inner size is larger than outer size.");
irounding1 = _rect_tube_rounding(1,default(irounding1, irounding), default(rounding1, rounding) , size1, isize1);
irounding2 = _rect_tube_rounding(1,default(irounding2, irounding), default(rounding2, rounding) , size2, isize2);
ichamfer1 = _rect_tube_rounding(1/sqrt(2),default(ichamfer1, ichamfer), default(chamfer1, chamfer) , size1, isize1);
ichamfer2 = _rect_tube_rounding(1/sqrt(2),default(ichamfer2, ichamfer), default(chamfer2, chamfer) , size2, isize2);
assert(isize2.y < size2.y, "Inner size is larger than outer size.")
assert(is_num(rounding) || is_vector(rounding,4), "rounding must be a number or 4-vector")
assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "rounding1 must be a number or 4-vector")
assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "rounding2 must be a number or 4-vector")
assert(is_num(chamfer) || is_vector(chamfer,4), "chamfer must be a number or 4-vector")
assert(is_undef(chamfer1) || is_num(chamfer1) || is_vector(chamfer1,4), "chamfer1 must be a number or 4-vector")
assert(is_undef(chamfer2) || is_num(chamfer2) || is_vector(chamfer2,4), "chamfer2 must be a number or 4-vector")
assert(is_undef(irounding) || is_num(irounding) || (is_list(irounding) && len(irounding)==4), "irounding must be a number or 4-vector")
assert(is_undef(irounding1) || is_num(irounding1) || (is_list(irounding1) && len(irounding1)==4), "irounding1 must be a number or 4-vector")
assert(is_undef(irounding2) || is_num(irounding2) || (is_list(irounding2) && len(irounding2)==4), "irounding2 must be a number or 4-vector")
assert(is_undef(ichamfer) || is_num(ichamfer) || (is_list(ichamfer) && len(ichamfer)==4), "ichamfer must be a number or 4-vector")
assert(is_undef(ichamfer1) || is_num(ichamfer1) || (is_list(ichamfer1) && len(ichamfer1)==4), "ichamfer1 must be a number or 4-vector")
assert(is_undef(ichamfer2) || is_num(ichamfer2) || (is_list(ichamfer2) && len(ichamfer2)==4), "ichamfer2 must be a number or 4-vector");
chamfer1=force_list( is_def(chamfer1)?chamfer1 : default(chamfer1,chamfer),4);
chamfer2=force_list(default(chamfer2,chamfer),4);
rounding1=force_list(default(rounding1,rounding),4);
rounding2=force_list(default(rounding2,rounding),4);
checks3 =
assert(all_nonnegative(chamfer1), "chamfer/chamfer1 must be non-negative")
assert(all_nonnegative(chamfer2), "chamfer/chamfer2 must be non-negative")
assert(all_nonnegative(rounding1), "rounding/rounding1 must be non-negative")
assert(all_nonnegative(rounding2), "rounding/rounding2 must be non-negative")
assert(all_zero(v_mul(rounding1,chamfer1),0), "rounding1 and chamfer1 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner")
assert(all_zero(v_mul(rounding2,chamfer2),0), "rounding2 and chamfer2 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner");
irounding1_temp = force_list(default(irounding1,irounding),4);
irounding2_temp = force_list(default(irounding2,irounding),4);
ichamfer1_temp = force_list(default(ichamfer1,ichamfer),4);
ichamfer2_temp = force_list(default(ichamfer2,ichamfer),4);
checksignr1 = [for(entry=irounding1_temp) if (is_def(entry) && entry<0) 1]==[];
checksignr2 = [for(entry=irounding2_temp) if (is_def(entry) && entry<0) 1]==[];
checksignc1 = [for(entry=ichamfer1_temp) if (is_def(entry) && entry<0) 1]==[];
checksignc2 = [for(entry=ichamfer2_temp) if (is_def(entry) && entry<0) 1]==[];
checkconflict1 = [for(i=[0:3]) if (is_def(irounding1_temp[i]) && is_def(ichamfer1_temp[i]) && irounding1_temp[i]!=0 && ichamfer1_temp[i]!=0) 1]==[];
checkconflict2 = [for(i=[0:3]) if (is_def(irounding2_temp[i]) && is_def(ichamfer2_temp[i]) && irounding2_temp[i]!=0 && ichamfer2_temp[i]!=0) 1]==[];
checks4 =
assert(checksignr1, "irounding/irounding1 must be non-negative")
assert(checksignr2, "irounding/irounding2 must be non-negative")
assert(checksignc1, "ichamfer/ichamfer1 must be non-negative")
assert(checksignc2, "ichamfer/ichamfer2 must be non-negative")
assert(checkconflict1, "irounding1 and ichamfer1 (possibly inherited from irounding and ichamfer) cannot both be nonzero at the swame corner")
assert(checkconflict2, "irounding2 and ichamfer2 (possibly inherited from irounding and ichamfer) cannot both be nonzero at the swame corner");
irounding1 = _rect_tube_rounding(1,irounding1_temp, rounding1, ichamfer1_temp, size1, isize1);
irounding2 = _rect_tube_rounding(1,irounding2_temp, rounding2, ichamfer2_temp, size2, isize2);
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) {
down(h/2) {
difference() {
prismoid(
size1, size2, h=h, shift=shift,
rounding=rounding, rounding1=rounding1, rounding2=rounding2,
chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
rounding1=rounding1, rounding2=rounding2,
chamfer1=chamfer1, chamfer2=chamfer2,
anchor=BOT
);
down(0.01) prismoid(

View File

@ -8,7 +8,8 @@ things with attachable shapes:
* Control where the shape appears and how it is oriented by anchoring and specifying orientation and spin
* Position or attach shapes relative to parent objects
* Tag objects and then color them or control boolean operations based on their tags.
* Tag objects and then control boolean operations based on their tags.
* Change the color of objects so that child objects are different colors than their parents
The various attachment features may seem complex at first, but
attachability is one of the most important features of the BOSL2
@ -18,8 +19,13 @@ It makes models simpler, more intuitive, and easier to maintain.
Almost all objects defined by BOSL2 are attachable. In addition,
BOSL2 overrides the built-in definitions for `cube()`, `cylinder()`,
`sphere()`, `square()`, and `circle()` and makes them attachable as
well.
`sphere()`, `square()`, `circle()` and `text()` and makes them attachable as
well. However, some basic OpenSCAD built-in definitions are not
attachable and will not work with the features described in this
tutorial. The non-attachables are `polyhedron()`, `linear_extrude()`,
`rotate_extrude()`, `surface()`, `projection()` and `polygon()`.
Some of these have attachable alternatives: `vnf_polyhedron()`,
`linear_sweep()`, `rotate_sweep()`, and `region()`.
## Anchoring
@ -1101,8 +1107,8 @@ color("red") spheroid(d=3) {
}
```
If you use the `recolor()` module, however, the child's color overrides the color of the parent.
This is probably easier to understand by example:
If you use the `recolor()` module, however, the child's color
overrides the color of the parent. This is probably easier to understand by example:
```openscad-3D
include <BOSL2/std.scad>
@ -1114,6 +1120,31 @@ recolor("red") spheroid(d=3) {
}
```
Be aware that `recolor()` will only work if you avoid using the native
`color()` module. Also note that `recolor()` still affects all its
children. If you want to color an object without affecting the
children you can use `color_this()`. See the difference below:
```openscad-3D
include <BOSL2/std.scad>
$fn = 24;
recolor("red") spheroid(d=3) {
attach(CENTER,BOT) recolor("white") cyl(h=10, d=1) {
attach(TOP,BOT) cyl(h=5, d1=3, d2=0);
}
}
right(5)
recolor("red") spheroid(d=3) {
attach(CENTER,BOT) color_this("white") cyl(h=10, d=1) {
attach(TOP,BOT) cyl(h=5, d1=3, d2=0);
}
}
```
As with all of the attachable features, these color modules only work
on attachable objects, so they will have no effect on objects you
create using `linear_extrude()` or `rotate_extrude()`.
## Making Attachables
To make a shape attachable, you just need to wrap it with an `attachable()` module with a