mirror of
https://github.com/revarbat/BOSL2.git
synced 2025-01-16 13:50:23 +01:00
Merge pull request #1072 from adrianVmariano/master
add trapezoid anchor override and fix trapezoid and rect perimeter anchoring
This commit is contained in:
commit
f7beec7517
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
201
shapes2d.scad
201
shapes2d.scad
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user