mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2025-01-15 08:59:40 +00:00
Compare commits
34 commits
66f4b89731
...
9f64c66beb
Author | SHA1 | Date | |
---|---|---|---|
|
9f64c66beb | ||
|
a74802cc6c | ||
|
e11c702364 | ||
|
7266323b92 | ||
|
c83253c814 | ||
|
1114f3f802 | ||
|
9742177381 | ||
|
3f9fb3a5b4 | ||
|
4d95f81352 | ||
|
9b2a32e574 | ||
|
82a13f9e97 | ||
|
a2131ce23d | ||
|
15388a7c11 | ||
|
428649e97b | ||
|
5f6e067341 | ||
|
a36be7e748 | ||
|
a3d18152e9 | ||
|
2b55c2fd53 | ||
|
72d1729e4c | ||
|
1bce4d5249 | ||
|
cd5804bbc8 | ||
|
d050abe0e0 | ||
|
d2bfb25efd | ||
|
55c82687e6 | ||
|
129e1b60d8 | ||
|
2007154818 | ||
|
9d4821dac1 | ||
|
7e9616bc3f | ||
|
aed62329df | ||
|
ea6d1b1793 | ||
|
3314ab0fe0 | ||
|
fe5838e5f4 | ||
|
480249d494 | ||
|
26dca9fd04 |
9 changed files with 1011 additions and 129 deletions
|
@ -35,9 +35,14 @@ $parent_orient = UP;
|
||||||
$parent_size = undef;
|
$parent_size = undef;
|
||||||
$parent_geom = undef;
|
$parent_geom = undef;
|
||||||
|
|
||||||
|
$edge_angle = undef;
|
||||||
|
$edge_length = undef;
|
||||||
|
|
||||||
$tags_shown = "ALL";
|
$tags_shown = "ALL";
|
||||||
$tags_hidden = [];
|
$tags_hidden = [];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_ANCHOR_TYPES = ["intersect","hull"];
|
_ANCHOR_TYPES = ["intersect","hull"];
|
||||||
|
|
||||||
|
|
||||||
|
@ -486,6 +491,8 @@ _ANCHOR_TYPES = ["intersect","hull"];
|
||||||
// Side Effects:
|
// Side Effects:
|
||||||
// `$attach_anchor` for each `from=` anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
|
// `$attach_anchor` for each `from=` anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
|
||||||
// `$attach_to` is set to `undef`.
|
// `$attach_to` is set to `undef`.
|
||||||
|
// `$edge_angle` is set to the angle of the edge if the anchor is on an edge and the parent is a prismoid, or vnf with "hull" anchoring
|
||||||
|
// `$edge_length` is set to the length of the edge if the anchor is on an edge and the parent is a prismoid, or vnf with "hull" anchoring
|
||||||
// Example:
|
// Example:
|
||||||
// spheroid(d=20) {
|
// spheroid(d=20) {
|
||||||
// position(TOP) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
|
// position(TOP) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
|
||||||
|
@ -505,6 +512,8 @@ module position(at,from)
|
||||||
two_d = _attach_geom_2d($parent_geom);
|
two_d = _attach_geom_2d($parent_geom);
|
||||||
for (anchr = anchors) {
|
for (anchr = anchors) {
|
||||||
anch = _find_anchor(anchr, $parent_geom);
|
anch = _find_anchor(anchr, $parent_geom);
|
||||||
|
$edge_angle = len(anch)==5 ? struct_val(anch[4],"edge_angle") : undef;
|
||||||
|
$edge_length = len(anch)==5 ? struct_val(anch[4],"edge_length") : undef;
|
||||||
$attach_to = undef;
|
$attach_to = undef;
|
||||||
$attach_anchor = anch;
|
$attach_anchor = anch;
|
||||||
translate(anch[1]) children();
|
translate(anch[1]) children();
|
||||||
|
@ -899,7 +908,7 @@ function _make_anchor_legal(anchor,geom) =
|
||||||
// rmax = is_vector(r) ? r[1] : r;
|
// rmax = is_vector(r) ? r[1] : r;
|
||||||
// layers = [for(z=[0:steps])
|
// layers = [for(z=[0:steps])
|
||||||
// let(
|
// let(
|
||||||
// r=rmin+(rmax-rmin)/2*(cos(z*360*cycles/steps)+1),ff=echo(r=r)
|
// r=rmin+(rmax-rmin)/2*(cos(z*360*cycles/steps)+1)
|
||||||
// )
|
// )
|
||||||
// path3d( concat([[0,0]],
|
// path3d( concat([[0,0]],
|
||||||
// arc(corner=path2d([BACK,CTR,RIGHT]), n=n, r=r)),
|
// arc(corner=path2d([BACK,CTR,RIGHT]), n=n, r=r)),
|
||||||
|
@ -972,6 +981,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
|
||||||
anchor_data = _find_anchor(anchor, $parent_geom);
|
anchor_data = _find_anchor(anchor, $parent_geom);
|
||||||
$edge_angle = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_angle") : undef;
|
$edge_angle = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_angle") : undef;
|
||||||
$edge_length = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_length") : undef;
|
$edge_length = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_length") : undef;
|
||||||
|
$edge_end1 = len(anchor_data)==5 ? struct_val(anchor_data[4],"vec") : undef;
|
||||||
anchor_pos = anchor_data[1];
|
anchor_pos = anchor_data[1];
|
||||||
anchor_dir = factor*anchor_data[2];
|
anchor_dir = factor*anchor_data[2];
|
||||||
anchor_spin = two_d || !inside || anchor==TOP || anchor==BOT ? anchor_data[3]
|
anchor_spin = two_d || !inside || anchor==TOP || anchor==BOT ? anchor_data[3]
|
||||||
|
@ -2005,7 +2015,7 @@ module face_mask(faces=[LEFT,RIGHT,FRONT,BACK,BOT,TOP]) {
|
||||||
// Usage:
|
// Usage:
|
||||||
// PARENT() edge_mask([edges], [except]) CHILDREN;
|
// PARENT() edge_mask([edges], [except]) CHILDREN;
|
||||||
// Description:
|
// Description:
|
||||||
// Takes a 3D mask shape, and attaches it to the given edges, with the appropriate orientation to be
|
// Takes a 3D mask shape, and attaches it to the given edges of a cuboid parent, with the appropriate orientation to be
|
||||||
// differenced away. The mask shape should be vertically oriented (Z-aligned) with the back-right
|
// differenced away. The mask shape should be vertically oriented (Z-aligned) with the back-right
|
||||||
// quadrant (X+Y+) shaped to be diffed away from the edge of parent attachable shape. If no tag is set
|
// quadrant (X+Y+) shaped to be diffed away from the edge of parent attachable shape. If no tag is set
|
||||||
// then `edge_mask` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
|
// then `edge_mask` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
|
||||||
|
@ -2046,6 +2056,8 @@ module edge_mask(edges=EDGES_ALL, except=[]) {
|
||||||
vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
|
vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
|
||||||
dummy=assert(vcount == 2, "Not an edge vector!");
|
dummy=assert(vcount == 2, "Not an edge vector!");
|
||||||
anch = _find_anchor(vec, $parent_geom);
|
anch = _find_anchor(vec, $parent_geom);
|
||||||
|
$edge_angle = len(anch)==5 ? struct_val(anch[4],"edge_angle") : undef;
|
||||||
|
$edge_length = len(anch)==5 ? struct_val(anch[4],"edge_length") : undef;
|
||||||
$attach_to = undef;
|
$attach_to = undef;
|
||||||
$attach_anchor = anch;
|
$attach_anchor = anch;
|
||||||
rotang =
|
rotang =
|
||||||
|
@ -3229,12 +3241,13 @@ function reorient(
|
||||||
// orient = A vector pointing in the direction parts should project from the anchor position. Default: UP
|
// orient = A vector pointing in the direction parts should project from the anchor position. Default: UP
|
||||||
// spin = If needed, the angle to rotate the part around the direction vector. Default: 0
|
// spin = If needed, the angle to rotate the part around the direction vector. Default: 0
|
||||||
// ---
|
// ---
|
||||||
|
// info = structure listing info to be propagated to the attached child, e.g. "edge_anchor"
|
||||||
// rot = A 4x4 rotations matrix, which may include a translation
|
// rot = A 4x4 rotations matrix, which may include a translation
|
||||||
// flip = If true, flip the anchor the opposite direction. Default: false
|
// flip = If true, flip the anchor the opposite direction. Default: false
|
||||||
function named_anchor(name, pos, orient, spin, rot, flip) =
|
function named_anchor(name, pos, orient, spin, rot, flip, info) =
|
||||||
assert(num_defined([orient,spin])==0 || num_defined([rot,flip])==0, "Cannot mix orient or spin with rot or flip")
|
assert(num_defined([orient,spin])==0 || num_defined([rot,flip])==0, "Cannot mix orient or spin with rot or flip")
|
||||||
assert(num_defined([pos,rot])>0, "Must give pos or rot")
|
assert(num_defined([pos,rot])>0, "Must give pos or rot")
|
||||||
is_undef(rot) ? [name, pos, default(orient,UP), default(spin,0)]
|
is_undef(rot) ? [name, pos, default(orient,UP), default(spin,0), if (info) info]
|
||||||
:
|
:
|
||||||
let(
|
let(
|
||||||
flip = default(flip,false),
|
flip = default(flip,false),
|
||||||
|
@ -3248,7 +3261,7 @@ function named_anchor(name, pos, orient, spin, rot, flip) =
|
||||||
decode=rot_decode(rot(to=UP,from=dir)*_force_rot(rot)),
|
decode=rot_decode(rot(to=UP,from=dir)*_force_rot(rot)),
|
||||||
spin = decode[0]*sign(decode[1].z)
|
spin = decode[0]*sign(decode[1].z)
|
||||||
)
|
)
|
||||||
[name, pos, dir, spin];
|
[name, pos, dir, spin, if (info) info];
|
||||||
|
|
||||||
|
|
||||||
// Function: attach_geom()
|
// Function: attach_geom()
|
||||||
|
@ -3302,7 +3315,7 @@ function named_anchor(name, pos, orient, spin, rot, flip) =
|
||||||
// anchors = If given as a list of anchor points, allows named anchor points.
|
// 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)
|
// 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
|
// 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.
|
// override = Function that takes an anchor and returns a pair `[position,direction,spin]` 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,spin]]` entries. If the direction/position/spin that is returned is undef then the default will be used.
|
||||||
//
|
//
|
||||||
// Example(NORENDER): Null/Point Shape
|
// Example(NORENDER): Null/Point Shape
|
||||||
// geom = attach_geom();
|
// geom = attach_geom();
|
||||||
|
@ -3773,6 +3786,18 @@ function _get_cp(geom) =
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
/// anchor = Vector or named anchor string.
|
/// anchor = Vector or named anchor string.
|
||||||
/// geom = The geometry description of the shape.
|
/// geom = The geometry description of the shape.
|
||||||
|
|
||||||
|
function _three_edge_corner_dir(facevecs,edges) =
|
||||||
|
let(
|
||||||
|
v1 = vector_bisect(facevecs[0],facevecs[2]),
|
||||||
|
v2 = vector_bisect(facevecs[1],facevecs[2]),
|
||||||
|
p1 = plane_from_normal(rot(v=edges[0],a=90,p=v1)),
|
||||||
|
p2 = plane_from_normal(rot(v=edges[1],a=-90,p=v2)),
|
||||||
|
line = plane_intersection(p1,p2),
|
||||||
|
v3 = unit(line[1]-line[0],UP)
|
||||||
|
)
|
||||||
|
unit(v3,UP);
|
||||||
|
|
||||||
function _find_anchor(anchor, geom)=
|
function _find_anchor(anchor, geom)=
|
||||||
is_string(anchor)? (
|
is_string(anchor)? (
|
||||||
anchor=="origin"? [anchor, CENTER, UP, 0] // Ok that this returns 3d anchor in the 2d case?
|
anchor=="origin"? [anchor, CENTER, UP, 0] // Ok that this returns 3d anchor in the 2d case?
|
||||||
|
@ -3829,16 +3854,28 @@ function _find_anchor(anchor, geom)=
|
||||||
dir = anch==CENTER? UP
|
dir = anch==CENTER? UP
|
||||||
: len(facevecs)==1? unit(facevecs[0],UP)
|
: len(facevecs)==1? unit(facevecs[0],UP)
|
||||||
: len(facevecs)==2? vector_bisect(facevecs[0],facevecs[1])
|
: len(facevecs)==2? vector_bisect(facevecs[0],facevecs[1])
|
||||||
: let(
|
: _three_edge_corner_dir(facevecs,[FWD,LEFT])*anch.z,
|
||||||
v1 = vector_bisect(facevecs[0],facevecs[2]),
|
|
||||||
v2 = vector_bisect(facevecs[1],facevecs[2]),
|
|
||||||
p1 = plane_from_normal(yrot(90,p=v1)),
|
|
||||||
p2 = plane_from_normal(xrot(-90,p=v2)),
|
|
||||||
line = plane_intersection(p1,p2),
|
|
||||||
v3 = unit(line[1]-line[0],UP) * anch.z
|
|
||||||
)
|
|
||||||
unit(v3,UP),
|
|
||||||
edgeang = len(facevecs)==2 ? 180-vector_angle(facevecs[0], facevecs[1]) : undef,
|
edgeang = len(facevecs)==2 ? 180-vector_angle(facevecs[0], facevecs[1]) : undef,
|
||||||
|
edgelen = anch.z==0 ? norm(edge)
|
||||||
|
: anch.z>0 ? abs([size2.y,size2.x]*axy)
|
||||||
|
: abs([size.y,size.x]*axy),
|
||||||
|
endvecs = len(facevecs)!=2 ? undef
|
||||||
|
: anch.z==0 ? [DOWN, UP]
|
||||||
|
: let(
|
||||||
|
raxy = zrot(-90,axy),
|
||||||
|
bot1 = point3d(v_mul(point2d(size )/2, raxy), -h/2),
|
||||||
|
top1 = point3d(v_mul(point2d(size2)/2, raxy) + shift, h/2),
|
||||||
|
edge1 = top1-bot1,
|
||||||
|
vec1 = (raxy.x!=0) ? unit(rot(from=UP, to=[edge1.x,0,max(0.01,h)], p=[raxy.x,0,0]), UP)
|
||||||
|
: unit(rot(from=UP, to=[0,edge1.y,max(0.01,h)], p=[0,raxy.y,0]), UP),
|
||||||
|
raxy2 = zrot(90,axy),
|
||||||
|
bot2 = point3d(v_mul(point2d(size )/2, raxy2), -h/2),
|
||||||
|
top2 = point3d(v_mul(point2d(size2)/2, raxy2) + shift, h/2),
|
||||||
|
edge2 = top2-bot2,
|
||||||
|
vec2 = (raxy2.y!=0) ? unit(rot(from=UP, to=[edge.x,0,max(0.01,h)], p=[raxy2.x,0,0]), UP)
|
||||||
|
: unit(rot(from=UP, to=[0,edge.y,max(0.01,h)], p=[0,raxy2.y,0]), UP)
|
||||||
|
)
|
||||||
|
[vec1,vec2],
|
||||||
final_dir = default(override[1],anch==CENTER?UP:rot(from=UP, to=axis, p=dir)),
|
final_dir = default(override[1],anch==CENTER?UP:rot(from=UP, to=axis, p=dir)),
|
||||||
final_pos = default(override[0],rot(from=UP, to=axis, p=pos)),
|
final_pos = default(override[0],rot(from=UP, to=axis, p=pos)),
|
||||||
|
|
||||||
|
@ -3851,7 +3888,8 @@ function _find_anchor(anchor, geom)=
|
||||||
: anch.z!=0 && sum(v_abs(anch))==2 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=anch.z*[anch.y,-anch.x,0]))
|
: anch.z!=0 && sum(v_abs(anch))==2 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=anch.z*[anch.y,-anch.x,0]))
|
||||||
: norm(anch)==3 ? _compute_spin(final_dir, final_dir==DOWN || final_dir==UP ? BACK : UP)
|
: norm(anch)==3 ? _compute_spin(final_dir, final_dir==DOWN || final_dir==UP ? BACK : UP)
|
||||||
: oang // face anchors point UP/BACK
|
: oang // face anchors point UP/BACK
|
||||||
) [anchor, final_pos, final_dir, default(override[2],spin), if (is_def(edgeang)) [["edge_angle",edgeang],["edge_length",norm(edge)]]]
|
) [anchor, final_pos, final_dir, default(override[2],spin),
|
||||||
|
if (is_def(edgeang)) [["edge_angle",edgeang],["edge_length",edgelen], ["vec", endvecs]]]
|
||||||
) : type == "conoid"? ( //r1, r2, l, shift
|
) : type == "conoid"? ( //r1, r2, l, shift
|
||||||
let(
|
let(
|
||||||
rr1=geom[1],
|
rr1=geom[1],
|
||||||
|
@ -3991,16 +4029,18 @@ function _find_anchor(anchor, geom)=
|
||||||
len(matchind)!=1 ? []
|
len(matchind)!=1 ? []
|
||||||
: let( // After this runs we have two edges as index pairs, and their associated faces as index values
|
: let( // After this runs we have two edges as index pairs, and their associated faces as index values
|
||||||
match1 = select(idxs,[0,matchind[0]]),
|
match1 = select(idxs,[0,matchind[0]]),
|
||||||
match2 = list_remove_values(idxs,match1),
|
match2 = list_remove(idxs,[0,matchind[0]]),
|
||||||
face1 = _vnf_find_edge_faces(vnf,[match1[0],match2[0]]),
|
facelists = [for(i=[0:1], j=[0:1])
|
||||||
face2 = _vnf_find_edge_faces(vnf,[match1[0],match2[1]]),
|
let(
|
||||||
edge1 = [match1[0], face1==[] ? match2[1] : match2[0]],
|
ed = [match1[i],match2[j]],
|
||||||
edge2 = list_remove_values(idxs,edge1),
|
fl = _vnf_find_edge_faces(vnf,ed)
|
||||||
face3 = _vnf_find_edge_faces(vnf,edge2),
|
|
||||||
allfaces = concat(face1,face2,face3)
|
|
||||||
)
|
)
|
||||||
assert(len(allfaces)==2, "Invalid polyhedron encountered while computing VNF anchor")
|
if (fl!=[]) [ed,fl]
|
||||||
[[edge1,edge2], allfaces],
|
],
|
||||||
|
final = [column(facelists,0), flatten(column(facelists,1))]
|
||||||
|
)
|
||||||
|
assert(len(final[1])==2, "invalid!")
|
||||||
|
final,
|
||||||
dir = len(idxs)>2 && edges_faces==[] ? [anchor,oang]
|
dir = len(idxs)>2 && edges_faces==[] ? [anchor,oang]
|
||||||
: edges_faces!=[] ?
|
: edges_faces!=[] ?
|
||||||
let(
|
let(
|
||||||
|
|
|
@ -44,6 +44,8 @@ _UNDEF="LRG+HX7dy89RyHvDlAKvb9Y04OTuaikpx205CTh8BSI";
|
||||||
// The correct hole should hold the plug when the long block is turned upside-down.
|
// The correct hole should hold the plug when the long block is turned upside-down.
|
||||||
// The number in front of that hole will indicate the `$slop` value that is ideal for your printer.
|
// The number in front of that hole will indicate the `$slop` value that is ideal for your printer.
|
||||||
// Remember to set that slop value in your scripts after you include the BOSL2 library: ie: `$slop = 0.15;`
|
// Remember to set that slop value in your scripts after you include the BOSL2 library: ie: `$slop = 0.15;`
|
||||||
|
// .
|
||||||
|
// Note that the `$slop` value may be different using different materials even on the same printer.
|
||||||
// Example(3D,Med): Slop Calibration Part.
|
// Example(3D,Med): Slop Calibration Part.
|
||||||
// min_slop = 0.00;
|
// min_slop = 0.00;
|
||||||
// slop_step = 0.05;
|
// slop_step = 0.05;
|
||||||
|
@ -215,6 +217,36 @@ CENTER = [ 0, 0, 0]; // Centered zero vector.
|
||||||
CTR = CENTER;
|
CTR = CENTER;
|
||||||
CENTRE = CENTER;
|
CENTRE = CENTER;
|
||||||
|
|
||||||
|
// Function: EDGE()
|
||||||
|
// Synopsis: Named edge anchor constants
|
||||||
|
// Topics: Constants, Attachment
|
||||||
|
// Usage:
|
||||||
|
// EDGE(i)
|
||||||
|
// EDGE(direction,i)
|
||||||
|
// Description:
|
||||||
|
// A shorthand for the named anchors "edge0", "top_edge0", "bot_edge0", etc.
|
||||||
|
// Use `EDGE(i)` to get "edge<i>". Use `EDGE(TOP,i)` to get "top_edge<i>" and
|
||||||
|
// use `EDGE(BOT,i)` to get "bot_edge(i)". You can also use
|
||||||
|
// `EDGE(CTR,i)` to get "edge<i>" and you can replace TOP or BOT with simply 1 or -1.
|
||||||
|
|
||||||
|
function EDGE(a,b) =
|
||||||
|
is_undef(b) ? str("edge",a)
|
||||||
|
: assert(in_list(a,[TOP,BOT,CTR,1,0,-1]),str("Invalid direction: ",a))
|
||||||
|
let(
|
||||||
|
choices=["bot_","","top_"],
|
||||||
|
ind=is_vector(a) ? a.z : a
|
||||||
|
)
|
||||||
|
str(choices[ind+1],"edge",b);
|
||||||
|
|
||||||
|
// Function: FACE()
|
||||||
|
// Synopsis: Named face anchor constants
|
||||||
|
// Topics: Constants, Attachment
|
||||||
|
// Usage:
|
||||||
|
// FACE(i)
|
||||||
|
// Description:
|
||||||
|
// A shorthand for the named anchors "face0", "face1", etc.
|
||||||
|
|
||||||
|
function FACE(i) = str("face",i);
|
||||||
|
|
||||||
// Section: Line specifiers
|
// Section: Line specifiers
|
||||||
// Used by functions in geometry.scad for specifying whether two points
|
// Used by functions in geometry.scad for specifying whether two points
|
||||||
|
|
125
masks3d.scad
125
masks3d.scad
|
@ -25,7 +25,7 @@
|
||||||
// Difference it from the object to be chamfered. The center of
|
// Difference it from the object to be chamfered. The center of
|
||||||
// the mask object should align exactly with the edge to be chamfered.
|
// the mask object should align exactly with the edge to be chamfered.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// l/h/length/height = Length of mask.
|
// l/h/length/height = Length of mask. Default: $edge_length if defined
|
||||||
// chamfer = Size of chamfer.
|
// chamfer = Size of chamfer.
|
||||||
// excess = The extra amount to add to the length of the mask so that it differences away from other shapes cleanly. Default: `0.1`
|
// excess = The extra amount to add to the length of the mask so that it differences away from other shapes cleanly. Default: `0.1`
|
||||||
// ---
|
// ---
|
||||||
|
@ -49,7 +49,8 @@
|
||||||
// }
|
// }
|
||||||
function chamfer_edge_mask(l, chamfer=1, excess=0.1, h, length, height, anchor=CENTER, spin=0, orient=UP) = no_function("chamfer_edge_mask");
|
function chamfer_edge_mask(l, chamfer=1, excess=0.1, h, length, height, anchor=CENTER, spin=0, orient=UP) = no_function("chamfer_edge_mask");
|
||||||
module chamfer_edge_mask(l, chamfer=1, excess=0.1, h, length, height, anchor=CENTER, spin=0, orient=UP) {
|
module chamfer_edge_mask(l, chamfer=1, excess=0.1, h, length, height, anchor=CENTER, spin=0, orient=UP) {
|
||||||
l = one_defined([l, h, height, length], "l,h,height,length");
|
l = is_def($edge_length) && !any_defined([l,length,h,height]) ? $edge_length
|
||||||
|
: one_defined([l,length,h,height],"l,length,h,height");
|
||||||
default_tag("remove") {
|
default_tag("remove") {
|
||||||
attachable(anchor,spin,orient, size=[chamfer*2, chamfer*2, l]) {
|
attachable(anchor,spin,orient, size=[chamfer*2, chamfer*2, l]) {
|
||||||
cylinder(r=chamfer, h=l+excess, center=true, $fn=4);
|
cylinder(r=chamfer, h=l+excess, center=true, $fn=4);
|
||||||
|
@ -169,28 +170,35 @@ module chamfer_cylinder_mask(r, chamfer, d, ang=45, from_end=false, anchor=CENTE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Section: Rounding Masks
|
// Section: Rounding Masks
|
||||||
|
|
||||||
// Module: rounding_edge_mask()
|
// Module: rounding_edge_mask()
|
||||||
// Synopsis: Creates a shape to round a 90° edge.
|
// Synopsis: Creates a shape to round a 90° edge.
|
||||||
// SynTags: Geom
|
// SynTags: Geom
|
||||||
// Topics: Masks, Rounding, Shapes (3D)
|
// Topics: Masks, Rounding, Shapes (3D)
|
||||||
// See Also: rounding_corner_mask(), default_tag(), diff()
|
// See Also: edge_profile(), rounding_corner_mask(), default_tag(), diff()
|
||||||
// Usage:
|
// Usage:
|
||||||
// rounding_edge_mask(l|h=|length=|height=, r|d=, [ang], [excess=]) [ATTACHMENTS];
|
// rounding_edge_mask(l|h=|length=|height=, r|d=, [ang], [excess=], [rounding=|chamfer=], ) [ATTACHMENTS];
|
||||||
// rounding_edge_mask(l|h=|length=|height=, r1=|d1=, r2=|d2=, [ang=], [excess=]) [ATTACHMENTS];
|
// rounding_edge_mask(l|h=|length=|height=, r1=|d1=, r2=|d2=, [ang=], [excess=], [rounding=|chamfer=]) [ATTACHMENTS];
|
||||||
// Description:
|
// Description:
|
||||||
// Creates a shape that can be used to round a straight edge at any angle.
|
// Creates a mask shape that can be used to round a straight edge at any angle, with
|
||||||
// Difference it from the object to be rounded. The center of the mask
|
// different rounding radii at each end. The corner of the mask appears on the Z axis with one face on the XZ plane.
|
||||||
// object should align exactly with the edge to be rounded. You can use it with {{diff()}} and
|
// You must align the mask corner with the edge you want to round. If your parent object is a cuboid, the easiest way to
|
||||||
// {{edge_mask()}} to attach masks automatically to objects. The default "remove" tag is set
|
// do this is to use {{diff()}} and {{edge_mask()}}. However, this method is somewhat inflexible regarding orientation of a tapered
|
||||||
// automatically.
|
// mask, and it does not support other parent shapes. You can attach the mask to a larger range of shapes using
|
||||||
|
// {{attach()}} to anchor the `LEFT+FWD` anchor of the mask to a desired corner on the parent with `inside=true`.
|
||||||
|
// Many shapes propagate `$edge_angle` and `$edge_length` which can aid in configuring the mask, and you can adjust the
|
||||||
|
// mask as needed to align the taper as desired. The default "remove" tag is set so {{diff()}} will automatically difference
|
||||||
|
// away the mask. You can of course also position the mask manually and use `difference()`.
|
||||||
|
// .
|
||||||
|
// For mating with other roundings or chamfers on cuboids or regular prisms, you can choose end roundings and end chamfers. These affect
|
||||||
|
// only the curved edge of the mask ends and will only work if the terminating face is perpendicular to the masked edge. The `excess`
|
||||||
|
// parameter will add extra length to the mask when you use these settings.
|
||||||
//
|
//
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// l/h/length/height = Length of mask.
|
// l/h/length/height = Length of mask. Default: $edge_length if defined
|
||||||
// r = Radius of the rounding.
|
// r = Radius of the rounding.
|
||||||
// ang = Angle between faces for rounding. Default: 90
|
// ang = Angle between faces for rounding. Default: $edge_angle if defined, otherwise 90
|
||||||
// ---
|
// ---
|
||||||
// r1 = Bottom radius of rounding.
|
// r1 = Bottom radius of rounding.
|
||||||
// r2 = Top radius of rounding.
|
// r2 = Top radius of rounding.
|
||||||
|
@ -198,6 +206,12 @@ module chamfer_cylinder_mask(r, chamfer, d, ang=45, from_end=false, anchor=CENTE
|
||||||
// d1 = Bottom diameter of rounding.
|
// d1 = Bottom diameter of rounding.
|
||||||
// d2 = Top diameter of rounding.
|
// d2 = Top diameter of rounding.
|
||||||
// excess = Extra size for the mask. Defaults: 0.1
|
// excess = Extra size for the mask. Defaults: 0.1
|
||||||
|
// rounding = Radius of roundong along ends. Default: 0
|
||||||
|
// rounding1 = Radius of rounding along bottom end
|
||||||
|
// rounding2 = Radius of rounding along top end
|
||||||
|
// chamfer = Chamfer size of end chamfers. Default: 0
|
||||||
|
// chamfer1 = Chamfer size of chamfer at bottom end
|
||||||
|
// chamfer2 = Chamfer size of chamfer at top end
|
||||||
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
|
// 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`
|
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
|
||||||
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
|
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
|
||||||
|
@ -249,34 +263,85 @@ module chamfer_cylinder_mask(r, chamfer, d, ang=45, from_end=false, anchor=CENTE
|
||||||
// rounding_edge_mask(l=p.z, r=25);
|
// rounding_edge_mask(l=p.z, r=25);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
// Example(3D,VPT=[5.02872,6.37039,-0.503894],VPR=[75.3,0,107.4],VPD=74.4017): Mask shape with end rounding at the top, chamfer at the bottom, and a large excess value:
|
||||||
|
// rounding_edge_mask(r=10,h=20, chamfer1=3, rounding2=3, excess=1);
|
||||||
|
// Example(3D,VPT=[1.05892,1.10442,2.20513],VPR=[60.6,0,118.1],VPD=74.4017): Attaching masks using {{attach()}} with automatic angle and length from the parent. Note that sometimes the automatic length is too short because it is the length of the edge itself.
|
||||||
|
// diff()
|
||||||
|
// prismoid([20,30],[12,19], h=10,shift=[4,7])
|
||||||
|
// attach([TOP+RIGHT,RIGHT+FRONT],LEFT+FWD,inside=true)
|
||||||
|
// rounding_edge_mask(r1=2,r2=4);
|
||||||
|
// Example(3D): The mask does not need to be the full length of the edge
|
||||||
|
// diff()
|
||||||
|
// cuboid(20)
|
||||||
|
// attach(RIGHT+TOP,LEFT+FWD,inside=true,inset=-.1,align=FWD)
|
||||||
|
// rounding_edge_mask(r1=0,r2=10,length=10);
|
||||||
|
|
||||||
function rounding_edge_mask(l, r, ang=90, r1, r2, d, d1, d2, excess=0.1, anchor=CENTER, spin=0, orient=UP, h,height,length) = no_function("rounding_edge_mask");
|
function rounding_edge_mask(l, r, ang=90, r1, r2, d, d1, d2, excess=0.1, anchor=CENTER, spin=0, orient=UP, h,height,length) = no_function("rounding_edge_mask");
|
||||||
module rounding_edge_mask(l, r, ang=90, r1, r2, excess=0.01, d1, d2,d,r,length, h, height, anchor=CENTER, spin=0, orient=UP,
|
module rounding_edge_mask(l, r, ang, r1, r2, excess=0.01, d1, d2,d,r,length, h, height, anchor=CENTER, spin=0, orient=UP,
|
||||||
|
rounding,rounding1,rounding2,chamfer,chamfer1,chamfer2,
|
||||||
_remove_tag=true)
|
_remove_tag=true)
|
||||||
{
|
{
|
||||||
length = one_defined([l,length,h,height],"l,length,h,height");
|
ang = first_defined([ang,$edge_angle,90]);
|
||||||
|
length = is_def($edge_length) && !any_defined([l,length,h,height]) ? $edge_length
|
||||||
|
: one_defined([l,length,h,height],"l,length,h,height");
|
||||||
r1 = get_radius(r1=r1, d1=d1,d=d,r=r);
|
r1 = get_radius(r1=r1, d1=d1,d=d,r=r);
|
||||||
r2 = get_radius(r2=r2, d1=d2,d=d,r=r);
|
r2 = get_radius(r2=r2, d1=d2,d=d,r=r);
|
||||||
|
dummy1 = assert(num_defined([chamfer,rounding])<2, "Cannot give both rounding and chamfer")
|
||||||
|
assert(num_defined([chamfer1,rounding1])<2, "Cannot give both rounding1 and chamfer1")
|
||||||
|
assert(num_defined([chamfer2,rounding2])<2, "Cannot give both rounding2 and chamfer2");
|
||||||
|
rounding1 = first_defined([rounding1,rounding,0]);
|
||||||
|
rounding2 = first_defined([rounding2,rounding,0]);
|
||||||
|
chamfer1 = first_defined([chamfer1,chamfer,0]);
|
||||||
|
chamfer2 = first_defined([chamfer2,chamfer,0]);
|
||||||
dummy = assert(all_nonnegative([r1,r2]), "radius/diameter value(s) must be nonnegative")
|
dummy = assert(all_nonnegative([r1,r2]), "radius/diameter value(s) must be nonnegative")
|
||||||
assert(all_positive([length]), "length/l/h/height must be a positive value")
|
assert(all_positive([length]), "length/l/h/height must be a positive value")
|
||||||
assert(is_finite(ang) && ang>0 && ang<180, "ang must be a number between 0 and 180");
|
assert(is_finite(ang) && ang>0 && ang<180, "ang must be a number between 0 and 180")
|
||||||
|
assert(all_nonnegative([chamfer1,chamfer2,rounding1,rounding2]), "chamfers and roundings must be nonnegative");
|
||||||
steps = ceil(segs(max(r1,r2))*(180-ang)/360);
|
steps = ceil(segs(max(r1,r2))*(180-ang)/360);
|
||||||
function make_path(r) =
|
function make_path(r) =
|
||||||
let(
|
r==0 ? repeat([0,0],steps+1)
|
||||||
arc = r==0 ? repeat([0,0],steps+1)
|
: arc(n=steps+1, r=r, corner=[polar_to_xy(r,ang),[0,0],[r,0]]);
|
||||||
: arc(n=steps+1, r=r, corner=[polar_to_xy(r,ang),[0,0],[r,0]]),
|
|
||||||
maxx = last(arc).x,
|
|
||||||
maxy = arc[0].y,
|
|
||||||
cp = [-excess/tan(ang/2),-excess]
|
|
||||||
)
|
|
||||||
[
|
|
||||||
[maxx, -excess],
|
|
||||||
cp,
|
|
||||||
arc[0] + polar_to_xy(excess, 90+ang),
|
|
||||||
each arc
|
|
||||||
];
|
|
||||||
path1 = path3d(make_path(r1),-length/2);
|
path1 = path3d(make_path(r1),-length/2);
|
||||||
path2 = path3d(make_path(r2),length/2);
|
path2 = path3d(make_path(r2),length/2);
|
||||||
|
|
||||||
|
function getarc(bigr,r,chamfer,p1,p2,h,print=false) =
|
||||||
|
r==0 && chamfer==0? [p2]
|
||||||
|
:
|
||||||
|
let(
|
||||||
|
steps = ceil(segs(r)/4)+1,
|
||||||
|
center = [bigr/tan(ang/2), bigr,h],
|
||||||
|
refplane = plane_from_normal([-(p2-center).y, (p2-center).x, 0], p2),
|
||||||
|
refnormal = plane_normal(refplane),
|
||||||
|
mplane = plane3pt(p2,p1,center),
|
||||||
|
A = plane_normal(mplane),
|
||||||
|
basept = lerp(p2,p1,max(r,chamfer)/2/h),
|
||||||
|
corner = [basept+refnormal*(refplane[3]-basept*refnormal)/(refnormal*refnormal),
|
||||||
|
p2,
|
||||||
|
center],
|
||||||
|
bare_arc = chamfer ? [p2+chamfer*unit(corner[0]-corner[1]),p2+chamfer*unit(corner[2]-corner[1])]
|
||||||
|
: arc(r=r, corner = corner, n=steps),
|
||||||
|
arc_with_excess = [each bare_arc, up(excess, last(bare_arc))],
|
||||||
|
arc = [for(pt=arc_with_excess) pt+refnormal*(mplane[3]-pt*A)/(refnormal*A)]
|
||||||
|
)
|
||||||
|
arc;
|
||||||
|
cp = [-excess/tan(ang/2), -excess];
|
||||||
|
extra1 = rounding1 || chamfer1 ? [0,0,excess] : CTR;
|
||||||
|
extra2 = rounding2 || chamfer2 ? [0,0,excess] : CTR;
|
||||||
|
pathlist = [for(i=[0:len(path1)-1])
|
||||||
|
let(
|
||||||
|
path = [
|
||||||
|
if (i==0) move(polar_to_xy( excess, 90+ang),path1[i]-extra1)
|
||||||
|
else if (i==len(path1)-1) fwd(excess,last(path1)-extra1)
|
||||||
|
else point3d(cp,-length/2-extra1.z),
|
||||||
|
each reverse(zflip(getarc(r1,rounding1,chamfer1,zflip(path2[i]), zflip(path1[i]),length/2))),
|
||||||
|
each getarc(r2,rounding2,chamfer2,path1[i],path2[i],length/2,print=rounding2!=0&&!is_undef(rounding2)&&i==3),
|
||||||
|
if (i==0) move(polar_to_xy( excess, 90+ang),path2[i]+extra2)
|
||||||
|
else if (i==len(path2)-1) fwd(excess,last(path2)+extra2)
|
||||||
|
else point3d(cp, length/2+extra2.z),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
path];
|
||||||
|
|
||||||
left_normal = cylindrical_to_xyz(1,90+ang,0);
|
left_normal = cylindrical_to_xyz(1,90+ang,0);
|
||||||
left_dir = cylindrical_to_xyz(1,ang,0);
|
left_dir = cylindrical_to_xyz(1,ang,0);
|
||||||
zdir = unit([length, 0,-(r2-r1)/tan(ang/2)]);
|
zdir = unit([length, 0,-(r2-r1)/tan(ang/2)]);
|
||||||
|
@ -315,7 +380,7 @@ module rounding_edge_mask(l, r, ang=90, r1, r2, excess=0.01, d1, d2,d,r,length,
|
||||||
[BACK+RIGHT+TOP, [cylindrical_to_xyz(cutfact*r2,ang/2,length/2), zrot(ang/2,zdir)+UP,ang/2+90]],
|
[BACK+RIGHT+TOP, [cylindrical_to_xyz(cutfact*r2,ang/2,length/2), zrot(ang/2,zdir)+UP,ang/2+90]],
|
||||||
[BACK+RIGHT+BOT, [cylindrical_to_xyz(cutfact*r1,ang/2,-length/2), zrot(ang/2,zdir)+DOWN,ang/2+90]],
|
[BACK+RIGHT+BOT, [cylindrical_to_xyz(cutfact*r1,ang/2,-length/2), zrot(ang/2,zdir)+DOWN,ang/2+90]],
|
||||||
];
|
];
|
||||||
vnf = vnf_vertex_array([path1,path2],caps=true,col_wrap=true);
|
vnf = vnf_vertex_array(reverse(pathlist), col_wrap=true,caps=true);
|
||||||
default_tag("remove", _remove_tag)
|
default_tag("remove", _remove_tag)
|
||||||
attachable(anchor,spin,orient,size=[1,1,length],override=override){
|
attachable(anchor,spin,orient,size=[1,1,length],override=override){
|
||||||
vnf_polyhedron(vnf);
|
vnf_polyhedron(vnf);
|
||||||
|
|
|
@ -550,6 +550,8 @@ module partition_cut_mask(l=100, h=100, cutsize=10, cutpath="jigsaw", gap=0, anc
|
||||||
// partition(spread=12, gap=30, cutpath="dovetail") cylinder(h=50, d=80, center=false);
|
// partition(spread=12, gap=30, cutpath="dovetail") cylinder(h=50, d=80, center=false);
|
||||||
// partition(spread=20, gap=20, cutsize=15, cutpath="dovetail") cylinder(h=50, d=80, center=false);
|
// partition(spread=20, gap=20, cutsize=15, cutpath="dovetail") cylinder(h=50, d=80, center=false);
|
||||||
// partition(spread=25, gap=15, cutsize=[20,20], cutpath="dovetail") cylinder(h=50, d=80, center=false);
|
// partition(spread=25, gap=15, cutsize=[20,20], cutpath="dovetail") cylinder(h=50, d=80, center=false);
|
||||||
|
// Side Effects:
|
||||||
|
// `$idx` is set to 0 on the back part and 1 on the front part.
|
||||||
// Examples(2DMed):
|
// Examples(2DMed):
|
||||||
// partition(cutpath="sawtooth") cylinder(h=50, d=80, center=false);
|
// partition(cutpath="sawtooth") cylinder(h=50, d=80, center=false);
|
||||||
// partition(cutpath="sinewave") cylinder(h=50, d=80, center=false);
|
// partition(cutpath="sinewave") cylinder(h=50, d=80, center=false);
|
||||||
|
@ -566,12 +568,14 @@ module partition(size=100, spread=10, cutsize=10, cutpath="jigsaw", gap=0, spin=
|
||||||
rsize = v_abs(rot(spin,p=size));
|
rsize = v_abs(rot(spin,p=size));
|
||||||
vec = rot(spin,p=BACK)*spread/2;
|
vec = rot(spin,p=BACK)*spread/2;
|
||||||
move(vec) {
|
move(vec) {
|
||||||
|
$idx = 0;
|
||||||
intersection() {
|
intersection() {
|
||||||
children();
|
children();
|
||||||
partition_mask(l=rsize.x, w=rsize.y, h=rsize.z, cutsize=cutsize, cutpath=cutpath, gap=gap, spin=spin);
|
partition_mask(l=rsize.x, w=rsize.y, h=rsize.z, cutsize=cutsize, cutpath=cutpath, gap=gap, spin=spin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
move(-vec) {
|
move(-vec) {
|
||||||
|
$idx = 1;
|
||||||
intersection() {
|
intersection() {
|
||||||
children();
|
children();
|
||||||
partition_mask(l=rsize.x, w=rsize.y, h=rsize.z, cutsize=cutsize, cutpath=cutpath, gap=gap, inverse=true, spin=spin);
|
partition_mask(l=rsize.x, w=rsize.y, h=rsize.z, cutsize=cutsize, cutpath=cutpath, gap=gap, inverse=true, spin=spin);
|
||||||
|
|
|
@ -817,7 +817,7 @@ function _point_dist(path,pathseg_unit,pathseg_len,pt) =
|
||||||
// When `closed=true` (the default), the input is treated as a polygon. If the input is a region it is treated as a collection
|
// When `closed=true` (the default), the input is treated as a polygon. If the input is a region it is treated as a collection
|
||||||
// of polygons. In this case, positive offset values make the shape larger. If you set `closed=false` then the input is treated as a path
|
// of polygons. In this case, positive offset values make the shape larger. If you set `closed=false` then the input is treated as a path
|
||||||
// with distinct start and end points. For paths, positive offsets shifts the path to the left, relative to the direction of the path.
|
// with distinct start and end points. For paths, positive offsets shifts the path to the left, relative to the direction of the path.
|
||||||
// Note that a path that happens to end at its starting point is not the same as a polygon and the offset result may differ.
|
// Note that a path that happens to end at its starting point is not the same as a polygon and the offset result may differ and the ends.
|
||||||
// .
|
// .
|
||||||
// If you use `delta` without chamfers, the path must not include any 180 degree turns, where the path
|
// If you use `delta` without chamfers, the path must not include any 180 degree turns, where the path
|
||||||
// reverses direction. Such reversals result in an offset with two parallel segments, so they cannot be
|
// reverses direction. Such reversals result in an offset with two parallel segments, so they cannot be
|
||||||
|
|
152
rounding.scad
152
rounding.scad
|
@ -1348,7 +1348,10 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
|
||||||
// will get a similar or better looking model with fewer vertices using "round" instead of
|
// will get a similar or better looking model with fewer vertices using "round" instead of
|
||||||
// "chamfer". Use the "chamfer" style offset only in cases where the number of steps is very small or just one (such as when using
|
// "chamfer". Use the "chamfer" style offset only in cases where the number of steps is very small or just one (such as when using
|
||||||
// the `os_chamfer` profile type).
|
// the `os_chamfer` profile type).
|
||||||
//
|
// .
|
||||||
|
// This module offers four anchor types. The default is "hull" in which VNF anchors are placed on the VNF of the **unrounded** object. You
|
||||||
|
// can also use "intersect" to get the intersection anchors to the unrounded object. If you prefer anchors that respect the rounding
|
||||||
|
// then use "surf_hull" or "intersect_hull".
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// path = 2d path (list of points) to extrude
|
// path = 2d path (list of points) to extrude
|
||||||
// height / length / l / h = total height (including rounded portions, but not extra sections) of the output. Default: combined height of top and bottom end treatments.
|
// height / length / l / h = total height (including rounded portions, but not extra sections) of the output. Default: combined height of top and bottom end treatments.
|
||||||
|
@ -1375,7 +1378,7 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
|
||||||
// atype = Select "hull", "intersect", "surf_hull" or "surf_intersect" anchor types. Default: "hull"
|
// atype = Select "hull", "intersect", "surf_hull" or "surf_intersect" anchor types. Default: "hull"
|
||||||
// cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid"
|
// cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid"
|
||||||
// Anchor Types:
|
// Anchor Types:
|
||||||
// hull = Anchors to the convex hull of the linear sweep of the path, ignoring any end roundings.
|
// hull = Anchors to the convex hull of the linear sweep of the path, ignoring any end roundings. (default)
|
||||||
// intersect = Anchors to the surface of the linear sweep of the path, ignoring any end roundings.
|
// intersect = Anchors to the surface of the linear sweep of the path, ignoring any end roundings.
|
||||||
// surf_hull = Anchors to the convex hull of the offset_sweep shape, including end treatments.
|
// surf_hull = Anchors to the convex hull of the offset_sweep shape, including end treatments.
|
||||||
// surf_intersect = Anchors to the surface of the offset_sweep shape, including any end treatments.
|
// surf_intersect = Anchors to the surface of the offset_sweep shape, including any end treatments.
|
||||||
|
@ -2030,7 +2033,11 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
|
||||||
// vnf = rounded_prism(bottom, [top], [height=|h=|length=|l=], [joint_top=], [joint_bot=], [joint_sides=], [k=], [k_top=], [k_bot=], [k_sides=], [splinesteps=], [debug=]);
|
// vnf = rounded_prism(bottom, [top], [height=|h=|length=|l=], [joint_top=], [joint_bot=], [joint_sides=], [k=], [k_top=], [k_bot=], [k_sides=], [splinesteps=], [debug=]);
|
||||||
// Description:
|
// Description:
|
||||||
// Construct a generalized prism with continuous curvature rounding. You supply the polygons for the top and bottom of the prism. The only
|
// Construct a generalized prism with continuous curvature rounding. You supply the polygons for the top and bottom of the prism. The only
|
||||||
// limitation is that joining the edges must produce a valid polyhedron with coplanar side faces. You specify the rounding by giving
|
// limitation is that joining the edges must produce a valid polyhedron with coplanar side faces. The vertices of the top and bottom
|
||||||
|
// are joined in the order listed. The top should have the standard vertex order for a polyhedron: clockwise as seen when viewing the prism
|
||||||
|
// from the outside.
|
||||||
|
// .
|
||||||
|
// You specify the rounding by giving
|
||||||
// the joint distance away from the corner for the rounding curve. The k parameter ranges from 0 to 1 with a default of 0.5. Larger
|
// the joint distance away from the corner for the rounding curve. The k parameter ranges from 0 to 1 with a default of 0.5. Larger
|
||||||
// values give a more abrupt transition and smaller ones a more gradual transition. If you set the value much higher
|
// values give a more abrupt transition and smaller ones a more gradual transition. If you set the value much higher
|
||||||
// than 0.8 the curvature changes abruptly enough that though it is theoretically continuous, it may
|
// than 0.8 the curvature changes abruptly enough that though it is theoretically continuous, it may
|
||||||
|
@ -2059,22 +2066,38 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
|
||||||
// construct a top that is a single point or two points. This means you can completely round a cube by setting the joint to half of
|
// construct a top that is a single point or two points. This means you can completely round a cube by setting the joint to half of
|
||||||
// the cube's width.
|
// the cube's width.
|
||||||
// If you set `debug` to true the module version will display the polyhedron even when it is invalid and it will show the bezier patches at the corners.
|
// If you set `debug` to true the module version will display the polyhedron even when it is invalid and it will show the bezier patches at the corners.
|
||||||
// This can help troubleshoot problems with your parameters. With the function form setting debug to true causes it to return [patches,vnf] where
|
// This can help troubleshoot problems with your parameters. With the function form setting debug to true causes run even on invalid cases and to return [patches,vnf] where
|
||||||
// patches is a list of the bezier control points for the corner patches.
|
// patches is a list of the bezier control points for the corner patches.
|
||||||
// .
|
// .
|
||||||
|
// This module offers five anchor types. The default is "hull" in which VNF anchors are placed on the VNF of the **unrounded** object. You
|
||||||
|
// can also use "intersect" to get the intersection anchors to the unrounded object. If you prefer anchors that respect the rounding
|
||||||
|
// then use "surf_hull" or "intersect_hull". Lastly, in the special case of a prism with four sides, you can use "prismoid" anchoring
|
||||||
|
// which will attempt to assign standard prismoid anchors to the shape by assigning as RIGHT the face that is closest to the RIGHT direction,
|
||||||
|
// and defining the other anchors around the shape baesd on that choice.
|
||||||
|
// .
|
||||||
// Note that rounded_prism() is not well suited to rounding shapes that have already been rounded, or that have many points.
|
// Note that rounded_prism() is not well suited to rounding shapes that have already been rounded, or that have many points.
|
||||||
// It works best when the top and bottom are polygons with well-defined corners. When the polygons have been rounded already,
|
// It works best when the top and bottom are polygons with well-defined corners. When the polygons have been rounded already,
|
||||||
// further rounding generates tiny bezier patches patches that can more easily
|
// further rounding generates tiny bezier patches patches that can more easily
|
||||||
// interfere, giving rise to an invalid polyhedron. It's also slow because you get bezier patches for every corner in the model.
|
// interfere, giving rise to an invalid polyhedron. It's also slow because you get bezier patches for every corner in the model.
|
||||||
// .
|
// .
|
||||||
|
// Named Anchors:
|
||||||
|
// "origin" = The native position of the prism.
|
||||||
|
// "top" = Top face, with spin BACK if face is parallel to the XY plane, or with positive Z otherwise
|
||||||
|
// "bot" = Bottom face, with spin BACK if face is parallel to the XY plane, or with positive Z otherwise
|
||||||
|
// "edge0", "edge1", etc. = Center of each side edge, spin pointing up along the edge
|
||||||
|
// "face0", "face1", etc. = Center of each side face, spin pointing up
|
||||||
|
// "top_edge0", "top_edge1", etc = Center of each top edge, spin pointing clockwise (from top)
|
||||||
|
// "bot_edge0", "bot_edge1", etc = Center of each bottom edge, spin pointing clockwise (from bottom)
|
||||||
|
// "top_corner0", "top_corner1", etc = Top corner, pointing in direction of associated edge anchor, spin up along associated edge
|
||||||
|
// "bot_corner0", "bot_2corner1", etc = Bottom corner, pointing in direction of associated edge anchor, spin up along associated edge
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// bottom = 2d or 3d path describing bottom polygon
|
// bottom = 2d or 3d path describing bottom polygon
|
||||||
// top = 2d or 3d path describing top polygon (must be the same dimension as bottom)
|
// top = 2d or 3d path describing top polygon (must be the same dimension as bottom)
|
||||||
// ---
|
// ---
|
||||||
// height/length/h/l = height of the shape when you give 2d bottom
|
// height/length/h/l = height of the shape when you give 2d bottom
|
||||||
// joint_top = rounding length for top (number or 2-vector). Default: 0
|
// joint_top = joint distance or [joint,k] pair for top roundover (number or 2-vector). Default: 0
|
||||||
// joint_bot = rounding length for bottom (number or 2-vector). Default: 0
|
// joint_bot = joint distance or [joint,k] for bottom roundover (number or 2-vector). Default: 0
|
||||||
// joint_sides = rounding length for side edges, a number/2-vector or list of them. Default: 0
|
// joint_sides = joint distance or [joint,k] for rounding of side edges, a number/2-vector or list of them. Default: 0
|
||||||
// k = continuous curvature rounding parameter for all edges. Default: 0.5
|
// k = continuous curvature rounding parameter for all edges. Default: 0.5
|
||||||
// k_top = continuous curvature rounding parameter for top
|
// k_top = continuous curvature rounding parameter for top
|
||||||
// k_bot = continuous curvature rounding parameter for bottom
|
// k_bot = continuous curvature rounding parameter for bottom
|
||||||
|
@ -2085,11 +2108,23 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
|
||||||
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
|
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
|
||||||
// spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0
|
// spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0
|
||||||
// orient = Vector to rotate top towards after spin (module only)
|
// orient = Vector to rotate top towards after spin (module only)
|
||||||
// atype = Select "hull" or "intersect" anchor types. (module only) Default: "hull"
|
// atype = Select "prismoid", "hull", "intersect", "surf_hull" or "surf_intersect" anchor types. (module only) Default: "hull"
|
||||||
// cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. (module only) Default: "centroid"
|
// cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. (module only) Default: "centroid"
|
||||||
// Named Anchors:
|
// Named Anchors:
|
||||||
// "origin" = The native position of the prism.
|
// "top" = center of top face pointing normal to that face
|
||||||
|
// "bot" = center of bottom face pointing normal to that face
|
||||||
|
// "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
|
||||||
// Anchor Types:
|
// Anchor Types:
|
||||||
|
// hull = Anchors to the convex hull of the linear sweep of the path, ignoring any end roundings. (default)
|
||||||
|
// intersect = Anchors to the surface of the linear sweep of the path, ignoring any end roundings.
|
||||||
|
// surf_hull = Anchors to the convex hull of the offset_sweep shape, including end treatments.
|
||||||
|
// surf_intersect = Anchors to the surface of the offset_sweep shape, including any end treatments.
|
||||||
|
|
||||||
// "hull" = Anchors to the virtual convex hull of the prism.
|
// "hull" = Anchors to the virtual convex hull of the prism.
|
||||||
// "intersect" = Anchors to the surface of the prism.
|
// "intersect" = Anchors to the surface of the prism.
|
||||||
// Example: Uniformly rounded pentagonal prism
|
// Example: Uniformly rounded pentagonal prism
|
||||||
|
@ -2172,11 +2207,24 @@ module rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_bot
|
||||||
k=0.5, splinesteps=16, h, length, l, height, convexity=10, debug=false,
|
k=0.5, splinesteps=16, h, length, l, height, convexity=10, debug=false,
|
||||||
anchor="origin",cp="centroid",spin=0, orient=UP, atype="hull")
|
anchor="origin",cp="centroid",spin=0, orient=UP, atype="hull")
|
||||||
{
|
{
|
||||||
assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\"");
|
dummy1 = assert(in_list(atype, ["intersect","hull","surf_intersect","surf_hull","prismoid"]),
|
||||||
|
"Anchor type must be one of: \"hull\", \"intersect\", \"surf_hull\", \"surf_intersect\" or \"prismoid\"")
|
||||||
|
assert(atype!="prismoid" || len(bottom)==4, "Anchor type \"prismoid\" requires that len(bottom)=4");
|
||||||
result = rounded_prism(bottom=bottom, top=top, joint_bot=joint_bot, joint_top=joint_top, joint_sides=joint_sides,
|
result = rounded_prism(bottom=bottom, top=top, joint_bot=joint_bot, joint_top=joint_top, joint_sides=joint_sides,
|
||||||
k_bot=k_bot, k_top=k_top, k_sides=k_sides, k=k, splinesteps=splinesteps, h=h, length=length, height=height, l=l,debug=debug);
|
k_bot=k_bot, k_top=k_top, k_sides=k_sides, k=k, splinesteps=splinesteps, h=h, length=length, height=height, l=l,
|
||||||
vnf = debug ? result[1] : result;
|
debug=debug, _full_info=true);
|
||||||
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=atype=="hull", cp=cp)
|
|
||||||
|
top = is_undef(top) ? path3d(bottom,height/2) :
|
||||||
|
len(top[0])==2 ? path3d(top,height/2) :
|
||||||
|
top;
|
||||||
|
bottom = len(bottom[0])==2 ? path3d(bottom,-height/2) : bottom;
|
||||||
|
unrounded = vnf_vertex_array([top,bottom],caps=true, col_wrap=true,reverse=true);
|
||||||
|
|
||||||
|
vnf = result[1];
|
||||||
|
geom = atype=="prismoid" ? attach_geom(size=[1,1,1],anchors=result[2], override=result[3])
|
||||||
|
: in_list(atype,["hull","intersect"]) ? attach_geom(vnf=unrounded, extent=atype=="hull", cp=cp, anchors=result[2])
|
||||||
|
: attach_geom(vnf=vnf, extent=atype=="surf_hull", cp=cp, anchors=result[2]);
|
||||||
|
attachable(anchor=anchor, spin=spin, orient=orient, geom=geom)
|
||||||
{
|
{
|
||||||
if (debug){
|
if (debug){
|
||||||
vnf_polyhedron(vnf, convexity=convexity);
|
vnf_polyhedron(vnf, convexity=convexity);
|
||||||
|
@ -2189,7 +2237,7 @@ module rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_bot
|
||||||
|
|
||||||
|
|
||||||
function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_bot, k_top, k_sides, k=0.5, splinesteps=16,
|
function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_bot, k_top, k_sides, k=0.5, splinesteps=16,
|
||||||
h, length, l, height, debug=false) =
|
h, length, l, height, debug=false, _full_info=false) =
|
||||||
let(
|
let(
|
||||||
bottom = force_path(bottom,"bottom"),
|
bottom = force_path(bottom,"bottom"),
|
||||||
top = force_path(top,"top")
|
top = force_path(top,"top")
|
||||||
|
@ -2347,9 +2395,81 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
|
||||||
for(pts=edge_points) vnf_vertex_array(pts),
|
for(pts=edge_points) vnf_vertex_array(pts),
|
||||||
debug ? vnf_from_polygons(faces,fast=true)
|
debug ? vnf_from_polygons(faces,fast=true)
|
||||||
: vnf_triangulate(vnf_from_polygons(faces))
|
: vnf_triangulate(vnf_from_polygons(faces))
|
||||||
])
|
]),
|
||||||
|
topnormal = unit(cross(top[0]-top[1],top[2]-top[1])),
|
||||||
|
botnormal = -unit(cross(bottom[0]-bottom[1],bottom[2]-bottom[1])),
|
||||||
|
sidenormal = [for(i=idx(top))
|
||||||
|
unit(cross(select(top,i+1)-top[i], bottom[i]-top[i]))],
|
||||||
|
|
||||||
|
//pos, orient, spin, info=...
|
||||||
|
|
||||||
|
anchors = [
|
||||||
|
for(i=idx(top))
|
||||||
|
let(
|
||||||
|
face = concat(select(top,[i+1,i]), select(bottom,i,i+1)),
|
||||||
|
face_ctr = mean(concat(select(top,[i+1,i]), select(bottom,i,i+1))),
|
||||||
|
bot_edge = bottom[i]-select(bottom,i+1),
|
||||||
|
bot_edge_ctr = mean(select(bottom,i,i+1)),
|
||||||
|
bot_edge_normal = unit(mean([sidenormal[i],botnormal])),
|
||||||
|
top_edge_normal = unit(mean([sidenormal[i],topnormal])),
|
||||||
|
top_edge = select(top,i+1)-top[i],
|
||||||
|
top_edge_ctr = mean(select(top,i,i+1)),
|
||||||
|
top_edge_dir = select(top,i+1)-top[i],
|
||||||
|
edge = [top[i],bottom[i]],
|
||||||
|
edge_ctr = mean([top[i],bottom[i]]),
|
||||||
|
edge_normal = unit(mean(select(sidenormal,[i,i-1]))),
|
||||||
|
top_corner_dir = _three_edge_corner_dir([select(sidenormal,i-1),sidenormal[i],topnormal],
|
||||||
|
[top[i]-select(top,i-1), top_edge]),
|
||||||
|
bot_corner_dir = _three_edge_corner_dir([select(sidenormal,i-1),sidenormal[i],botnormal],
|
||||||
|
[bottom[i]-select(bottom,i-1), bot_edge])
|
||||||
)
|
)
|
||||||
debug ? [concat(top_patch, bot_patch), vnf] : vnf;
|
each[
|
||||||
|
named_anchor(EDGE(i),edge_ctr,edge_normal, _compute_spin(edge_normal,top[i]-bottom[i]),
|
||||||
|
info=[["edge_angle",180-vector_angle(sidenormal[i],select(sidenormal,i-1))], ["edge_length",norm(top[i]-bottom[i])]]),
|
||||||
|
named_anchor(EDGE(UP,i),top_edge_ctr, top_edge_normal, _compute_spin(top_edge_normal, top_edge),
|
||||||
|
info=[["edge_angle",180-vector_angle(topnormal,sidenormal[i])], ["edge_length",norm(top_edge)]]),
|
||||||
|
named_anchor(EDGE(DOWN,i),bot_edge_ctr, bot_edge_normal, _compute_spin(bot_edge_normal, bot_edge),
|
||||||
|
info=[["edge_angle",180-vector_angle(botnormal,sidenormal[i])], ["edge_length",norm(bot_edge)]]),
|
||||||
|
named_anchor(FACE(i),mean(face), sidenormal[i], _compute_spin(sidenormal[i],UP)),
|
||||||
|
named_anchor(str("top_corner",i),top[i], top_corner_dir, _compute_spin(top_corner_dir,UP)),
|
||||||
|
named_anchor(str("bot_corner",i),bottom[i], bot_corner_dir, _compute_spin(bot_corner_dir,UP))
|
||||||
|
],
|
||||||
|
named_anchor("top", mean(top), topnormal, _compute_spin(topnormal, approx(v_abs(topnormal),UP)?BACK:UP)),
|
||||||
|
named_anchor("bot", mean(bottom), botnormal, _compute_spin(botnormal, approx(v_abs(botnormal),UP)?BACK:UP)),
|
||||||
|
],
|
||||||
|
override = len(top)!=4 ? undef
|
||||||
|
:
|
||||||
|
let(
|
||||||
|
stddir = [RIGHT,FWD,LEFT,BACK],
|
||||||
|
getanch = function(name) search([name], anchors, num_returns_per_match=1)[0],
|
||||||
|
dir_angle = [for(i=[0:3]) vector_angle(anchors[6*i+3][2],RIGHT)],
|
||||||
|
ofs = search([min(dir_angle)], dir_angle, num_returns_per_match=1)[0]
|
||||||
|
)
|
||||||
|
[
|
||||||
|
[UP, select(anchors[24],1,3)],
|
||||||
|
[DOWN, select(anchors[25],1,3)],
|
||||||
|
for(i=[0:3])
|
||||||
|
let(
|
||||||
|
edgeanch=anchors[((i+ofs)%4)*6],
|
||||||
|
upedge=anchors[((i+ofs)%4)*6+1],
|
||||||
|
downedge=anchors[((i+ofs)%4)*6+2],
|
||||||
|
faceanch=anchors[((i+ofs)%4)*6+3],
|
||||||
|
upcorner=anchors[((i+ofs)%4)*6+4],
|
||||||
|
downcorner=anchors[((i+ofs)%4)*6+5]
|
||||||
|
)
|
||||||
|
each [
|
||||||
|
[stddir[i],select(faceanch,1,3)],
|
||||||
|
[stddir[i]+select(stddir,i-1), select(edgeanch,1,3)],
|
||||||
|
[stddir[i]+UP, select(upedge,1,3)],
|
||||||
|
[stddir[i]+DOWN, select(downedge,1,3)],
|
||||||
|
[stddir[i]+select(stddir,i-1)+UP, select(upcorner,1,3)],
|
||||||
|
[stddir[i]+select(stddir,i-1)+DOWN, select(downcorner,1,3)],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
!debug && !_full_info ? vnf
|
||||||
|
: _full_info ? [concat(top_patch, bot_patch), vnf, anchors, override]
|
||||||
|
: [concat(top_patch, bot_patch), vnf];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1472,7 +1472,7 @@ function egg(length, r1, r2, R, d1, d2, D, anchor=CENTER, spin=0) =
|
||||||
let(
|
let(
|
||||||
r1 = get_radius(r1=r1,d1=d1),
|
r1 = get_radius(r1=r1,d1=d1),
|
||||||
r2 = get_radius(r1=r2,d1=d2),
|
r2 = get_radius(r1=r2,d1=d2),
|
||||||
D = get_radius(r1=R, d1=D)
|
R = get_radius(r1=R, d1=D)
|
||||||
)
|
)
|
||||||
assert(length>0)
|
assert(length>0)
|
||||||
assert(R>length/2, "Side radius R must be larger than length/2")
|
assert(R>length/2, "Side radius R must be larger than length/2")
|
||||||
|
@ -1500,6 +1500,8 @@ function egg(length, r1, r2, R, d1, d2, D, anchor=CENTER, spin=0) =
|
||||||
module egg(length,r1,r2,R,d1,d2,D,anchor=CENTER, spin=0)
|
module egg(length,r1,r2,R,d1,d2,D,anchor=CENTER, spin=0)
|
||||||
{
|
{
|
||||||
path = egg(length,r1,r2,R,d1,d2,D);
|
path = egg(length,r1,r2,R,d1,d2,D);
|
||||||
|
r1 = get_radius(r1=r1,d1=d1);
|
||||||
|
r2 = get_radius(r1=r2,d1=d2);
|
||||||
anchors = [named_anchor("left", [-length/2+r1,0], BACK, 0),
|
anchors = [named_anchor("left", [-length/2+r1,0], BACK, 0),
|
||||||
named_anchor("right", [length/2-r2,0], BACK, 0)];
|
named_anchor("right", [length/2-r2,0], BACK, 0)];
|
||||||
attachable(anchor, spin, two_d=true, path=path, extent=true, anchors=anchors){
|
attachable(anchor, spin, two_d=true, path=path, extent=true, anchors=anchors){
|
||||||
|
|
137
shapes3d.scad
137
shapes3d.scad
|
@ -599,8 +599,8 @@ function cuboid(
|
||||||
// specifying `size2=[100,undef]` sets the size in the X direction but allows the size in the Y direction to be computed based on yang.
|
// specifying `size2=[100,undef]` sets the size in the X direction but allows the size in the Y direction to be computed based on yang.
|
||||||
// .
|
// .
|
||||||
// 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 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 up. The anchors on the side edges and the corners
|
// The anchors on the top and bottom edges also have anchors that point clockwise as viewed from outside the shapep.
|
||||||
// have spin with positive Z component, pointing along the edge where the anchor is located.
|
// The anchors on the side edges and the corners have spin with positive Z component, pointing along the edge where the anchor is located.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// size1 = [width, length] of the bottom end of the prism.
|
// size1 = [width, length] of the bottom end of the prism.
|
||||||
// size2 = [width, length] of the top end of the prism.
|
// size2 = [width, length] of the top end of the prism.
|
||||||
|
@ -835,7 +835,7 @@ function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
|
||||||
// Synopsis: Creates a regular prism with roundovers and chamfering
|
// Synopsis: Creates a regular prism with roundovers and chamfering
|
||||||
// SynTags: Geom, VNF
|
// SynTags: Geom, VNF
|
||||||
// Topics: Textures, Rounding, Chamfers
|
// Topics: Textures, Rounding, Chamfers
|
||||||
// See Also: cyl(), rounded_prism(), texture(), linear_sweep()
|
// See Also: cyl(), rounded_prism(), texture(), linear_sweep(), EDGE(), FACE()
|
||||||
// Usage: Normal prisms
|
// Usage: Normal prisms
|
||||||
// regular_prism(n, h|l=|height=|length=, r, [center=], [realign=]) [ATTACHMENTS];
|
// regular_prism(n, h|l=|height=|length=, r, [center=], [realign=]) [ATTACHMENTS];
|
||||||
// regular_prism(n, h|l=|height=|length=, d=|id=|od=|ir=|or=|side=, ...) [ATTACHMENTS];
|
// regular_prism(n, h|l=|height=|length=, d=|id=|od=|ir=|or=|side=, ...) [ATTACHMENTS];
|
||||||
|
@ -863,22 +863,24 @@ function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
|
||||||
// .
|
// .
|
||||||
// Anchors are based on the VNF of the prism. Especially for tapered or shifted prisms, this may give unexpected anchor positions, such as top side anchors
|
// Anchors are based on the VNF of the prism. Especially for tapered or shifted prisms, this may give unexpected anchor positions, such as top side anchors
|
||||||
// being located at the bottom of the shape, so confirm anchor positions before use.
|
// being located at the bottom of the shape, so confirm anchor positions before use.
|
||||||
// Additional face and edge anchors are located on the side faces and vertical edges of the prism.
|
// Additional named face and edge anchors are located on the side faces and vertical edges of the prism.
|
||||||
// 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.
|
// 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.
|
||||||
// Named anchors located along the top and bottom edges and corners are pointed in the direction of the associated face or edge to enable positioning
|
// When you use `shift`, which moves the top face of the prism, the spin for the side face and edges anchors will align
|
||||||
// in the direction of the side faces but positioned at the top/bottom, since {{align()}} cannot be used for this task. These edge and corners anchors do
|
// the child with the edge or face direction. The "edge0" anchor identifies an edge located along the X+ axis, and then edges
|
||||||
// not split the edge/corner angle like the standard anchors.
|
// 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.
|
||||||
// .
|
// .
|
||||||
// This module is very similar to {{cyl()}}. It differs in the following ways: you can specify side length or inner radius/diameter, you can apply roundings with
|
// This module is very similar to {{cyl()}}. It differs in the following ways: you can specify side length or inner radius/diameter, you can apply roundings with
|
||||||
// different `$fn` than the number of prism faces, you can apply texture to the flat faces without forcing a high facet count,
|
// different `$fn` than the number of prism faces, you can apply texture to the flat faces without forcing a high facet count,
|
||||||
// anchors are located on the true object instead of the ideal cylinder and you can anchor to the edges and faces.
|
// anchors are located on the true object instead of the ideal cylinder and you can anchor to the edges and faces.
|
||||||
// Named Anchors:
|
// Named Anchors:
|
||||||
// "edge0", "edge1", etc. = Center of each side edge, spin pointing up along the edge
|
// "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
|
// "face0", "face1", etc. = Center of each side face, spin pointing up. Can access with FACE(i)
|
||||||
// "topedge0", "topedge1", etc = Center of each top edge, pointing in direction of associated side face, spin up
|
// "top_edge0", "top_edge1", etc = Center of each top edge, spin pointing clockwise (from top). Can access with EDGE(TOP,i)
|
||||||
// "botedge0", "botedge1", etc = Center of each bottom edge, pointing in direction of associated side face, spin up
|
// "bot_edge0", "bot_edge1", etc = Center of each bottom edge, spin pointing clockwise (from bottom). Can access with EDGE(BOT,i)
|
||||||
// "topcorner0", "topcorner1", etc = Top corner, pointing in direction of associated edge anchor, spin up along associated edge
|
// "top_corner0", "top_corner1", etc = Top corner, pointing in direction of associated edge anchor, spin up along associated edge
|
||||||
// "botcorner0", "botcorner1", etc = Bottom 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
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// l / h / length / height = Length of prism
|
// l / h / length / height = Length of prism
|
||||||
// r = Outer radius of prism.
|
// r = Outer radius of prism.
|
||||||
|
@ -1131,33 +1133,18 @@ function regular_prism(n,
|
||||||
ovnf = apply(skmat, vnf),
|
ovnf = apply(skmat, vnf),
|
||||||
edge_face = [ [r2-r1,0,height],[(r2-r1)/sc,0,height]], // regular edge, then face edge, in xz plane
|
edge_face = [ [r2-r1,0,height],[(r2-r1)/sc,0,height]], // regular edge, then face edge, in xz plane
|
||||||
names = ["edge","face"],
|
names = ["edge","face"],
|
||||||
anchors = approx(shift,[0,0]) ?
|
anchors = let(
|
||||||
[for(i=[0:n-1], j=[0:1])
|
|
||||||
let(
|
|
||||||
M = zrot(-(i+j/2-(realign?1/2:0))*360/n),
|
|
||||||
edge = apply(M,edge_face[j]),
|
|
||||||
dir = apply(M,[height,0,-edge_face[j].x]),
|
|
||||||
spin = sign(dir.x)*vector_angle(edge - (edge*dir)*dir, rot(from=UP,to=dir,p=BACK))
|
|
||||||
)
|
|
||||||
each [
|
|
||||||
named_anchor(str(names[j],i), apply(M,[(r1+r2)/2/(j==0?1:sc),0,0]), dir, spin),
|
|
||||||
named_anchor(str(j==0?"top_corner":"top_edge",i), apply(M,[r2/(j==0?1:sc),0,height/2]), dir, spin),
|
|
||||||
named_anchor(str(j==0?"bot_corner":"bot_edge",i), apply(M,[r1/(j==0?1:sc),0,-height/2]), dir, spin),
|
|
||||||
]
|
|
||||||
]
|
|
||||||
:
|
|
||||||
let(
|
|
||||||
faces = [
|
faces = [
|
||||||
for(i=[0:n-1])
|
for(i=[0:n-1])
|
||||||
let(
|
let(
|
||||||
M1 = skmat*zrot(-i*360/n),
|
M1 = skmat*zrot(-i*360/n), // map to point i
|
||||||
M2 = skmat*zrot(-(i+1)*360/n),
|
M2 = skmat*zrot(-(i+1)*360/n), // map to point i+1
|
||||||
edge1 = apply(M1,[[r2,0,height/2], [r1,0,-height/2]]),
|
edge1 = apply(M1,[[r2,0,height/2], [r1,0,-height/2]]), // "vertical" edge at i
|
||||||
edge2 = apply(M2,[[r2,0,height/2], [r1,0,-height/2]]),
|
edge2 = apply(M2,[[r2,0,height/2], [r1,0,-height/2]]), // "vertical" edge at i+1
|
||||||
face_edge = (edge1+edge2)/2,
|
face_edge = (edge1+edge2)/2, // "vertical" edge across side face between i and i+1
|
||||||
facenormal = unit(cross(edge1[0]-edge1[1], edge2[1]-edge1[0]))
|
facenormal = unit(cross(edge1[0]-edge1[1], edge2[1]-edge1[0]))
|
||||||
)
|
) // [normal to face, edge through face center vector, actual edge vector, top edge vector]
|
||||||
[facenormal,face_edge[0]-face_edge[1],edge1[0]-edge1[1]] // [normal to face, edge through face center, actual edge]
|
[facenormal,face_edge[0]-face_edge[1],edge1[0]-edge1[1],edge2[0]-edge1[0]]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
[for(i=[0:n-1])
|
[for(i=[0:n-1])
|
||||||
|
@ -1165,20 +1152,34 @@ function regular_prism(n,
|
||||||
Mface = skmat*zrot(-(i+1/2)*360/n),
|
Mface = skmat*zrot(-(i+1/2)*360/n),
|
||||||
faceedge = faces[i][1],
|
faceedge = faces[i][1],
|
||||||
facenormal = faces[i][0],
|
facenormal = faces[i][0],
|
||||||
//facespin = _compute_spin(facenormal, faceedge), // spin along centerline of face instea of pointing up---seems to be wrong choice
|
//facespin = _compute_spin(facenormal, faceedge), // spin along centerline of face instead of pointing up---seems to be wrong choice
|
||||||
facespin = _compute_spin(facenormal, UP),
|
facespin = _compute_spin(facenormal, UP),
|
||||||
edgenormal = unit(vector_bisect(facenormal,select(faces,i-1)[0])),
|
edgenormal = unit(vector_bisect(facenormal,select(faces,i-1)[0])),
|
||||||
Medge = skmat*zrot(-i*360/n),
|
Medge = skmat*zrot(-i*360/n),
|
||||||
edge = faces[i][2],
|
edge = faces[i][2],
|
||||||
edgespin = _compute_spin(edgenormal, edge)
|
edgespin = _compute_spin(edgenormal, edge),
|
||||||
|
topedge = unit(faces[i][3]),
|
||||||
|
topnormal = unit(facenormal+UP),
|
||||||
|
botnormal = unit(facenormal+DOWN),
|
||||||
|
topedgespin = _compute_spin(topnormal, topedge),
|
||||||
|
botedgespin = _compute_spin(botnormal, -topedge),
|
||||||
|
topedgeangle = 180-vector_angle(UP,facenormal),
|
||||||
|
sideedgeangle = 180-vector_angle(facenormal, select(faces,i-1)[0]),
|
||||||
|
edgelen = norm(select(faces,i)[2])
|
||||||
)
|
)
|
||||||
each [
|
each [
|
||||||
named_anchor(str("face",i), apply(Mface,[(r1+r2)/2/sc,0,0]), facenormal, facespin),
|
named_anchor(str("face",i), apply(Mface,[(r1+r2)/2/sc,0,0]), facenormal, facespin),
|
||||||
named_anchor(str("edge",i), apply(Medge,[(r1+r2)/2,0,0]), edgenormal, edgespin),
|
named_anchor(str("edge",i), apply(Medge,[(r1+r2)/2,0,0]), edgenormal, edgespin,
|
||||||
named_anchor(str("top_edge",i), apply(Mface,[r2/sc,0,height/2]), facenormal, facespin),
|
info=[["edge_angle",sideedgeangle], ["edge_length",edgelen]]),
|
||||||
named_anchor(str("top_corner",i), apply(Medge,[r2,0,height/2]), edgenormal, edgespin),
|
named_anchor(str("top_edge",i), apply(Mface,[r2/sc,0,height/2]), topnormal, topedgespin,
|
||||||
named_anchor(str("bot_edge",i), apply(Mface,[r1/sc,0,-height/2]), facenormal, facespin),
|
info=[["edge_angle",topedgeangle],["edge_length",2*sin(180/n)*r2]]),
|
||||||
named_anchor(str("bot_corner",i), apply(Medge,[r1,0,-height/2]), edgenormal, edgespin)
|
named_anchor(str("bot_edge",i), apply(Mface,[r1/sc,0,-height/2]), botnormal, botedgespin,
|
||||||
|
info=[["edge_angle",180-topedgeangle],["edge_length",2*sin(180/n)*r1]]),
|
||||||
|
named_anchor(str("top_corner",i), apply(Medge,[r2,0,height/2]), unit(edgenormal+UP),
|
||||||
|
_compute_spin(unit(edgenormal+UP),edge)),
|
||||||
|
named_anchor(str("bot_corner",i), apply(Medge,[r1,0,-height/2]), unit(edgenormal+DOWN),
|
||||||
|
_compute_spin(unit(edgenormal+DOWN),edge))
|
||||||
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
override = approx(shift,[0,0]) ? undef : [[UP, [point3d(shift,height/2), UP]]],
|
override = approx(shift,[0,0]) ? undef : [[UP, [point3d(shift,height/2), UP]]],
|
||||||
|
@ -3666,20 +3667,34 @@ module path_text(path, text, font, size, thickness, lettersize, offset=0, revers
|
||||||
// Description:
|
// Description:
|
||||||
// Creates a shape that can be unioned into a concave joint between two faces, to fillet them.
|
// Creates a shape that can be unioned into a concave joint between two faces, to fillet them.
|
||||||
// Note that this module is the same as {{rounding_edge_mask()}}, except that it does not
|
// Note that this module is the same as {{rounding_edge_mask()}}, except that it does not
|
||||||
// apply the default "remove" tag.
|
// apply the default "remove" tag and has a different default angle.
|
||||||
//
|
// It can be convenient to {{attach()}} the fillet to the edge of a parent object.
|
||||||
|
// Many objects propagate the $edge_angle and $edge_length which are used as defaults for the fillet.
|
||||||
|
// If you attach the fillet to the edge, it will be hovering in space and you need to apply {{yrot()}}
|
||||||
|
// to place it on the parent object, generally either 90 degrees or -90 degrees dependong on which
|
||||||
|
// face you want the fillet.
|
||||||
// Usage:
|
// Usage:
|
||||||
// fillet(l|h=|length=|height=, r|d=, [ang=], [excess=]) [ATTACHMENTS];
|
// fillet(l|h=|length=|height=, r|d=, [ang=], [excess=], [rounding=|chamfer=]) [ATTACHMENTS];
|
||||||
// fillet(l|h=|length=|height=, r1=|d1=, r2=|d2=, [ang=], [excess=]) [ATTACHMENTS];
|
// fillet(l|h=|length=|height=, r1=|d1=, r2=|d2=, [ang=], [excess=], [rounding=|chamfer=]) [ATTACHMENTS];
|
||||||
//
|
//
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// l / length / h / height = Length of edge to fillet.
|
// l/h/length/height = Length of mask. Default: $edge_length if defined
|
||||||
// r = Radius of fillet.
|
// r = Radius of the rounding.
|
||||||
// ang = Angle between faces to fillet.
|
// ang = Angle between faces for rounding. Default: 180-$edge_angle if defined, otherwise 90
|
||||||
// excess = Overlap size for unioning with faces.
|
|
||||||
// ---
|
// ---
|
||||||
// d = Diameter of fillet.
|
// r1 = Bottom radius of fillet.
|
||||||
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `FRONT+LEFT`
|
// r2 = Top radius of fillet.
|
||||||
|
// d = Diameter of the fillet.
|
||||||
|
// d1 = Bottom diameter of fillet.
|
||||||
|
// d2 = Top diameter of fillet.
|
||||||
|
// excess = Extra size for the fillet. Defaults: .1
|
||||||
|
// rounding = Radius of roundong along ends. Default: 0
|
||||||
|
// rounding1 = Radius of rounding along bottom end
|
||||||
|
// rounding2 = Radius of rounding along top end
|
||||||
|
// chamfer = Chamfer size of end chamfers. Default: 0
|
||||||
|
// chamfer1 = Chamfer size of chamfer at bottom end
|
||||||
|
// chamfer2 = Chamfer size of chamfer at top end
|
||||||
|
// 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`
|
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
|
||||||
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
|
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
|
||||||
//
|
//
|
||||||
|
@ -3713,6 +3728,14 @@ module path_text(path, text, font, size, thickness, lettersize, offset=0, revers
|
||||||
// align(TOP,RIGHT,inset=10) fillet(l=50,r=10,orient=FWD);
|
// align(TOP,RIGHT,inset=10) fillet(l=50,r=10,orient=FWD);
|
||||||
// align(TOP,RIGHT,inset=20) cuboid([4,50,20],anchor=BOT);
|
// align(TOP,RIGHT,inset=20) cuboid([4,50,20],anchor=BOT);
|
||||||
// }
|
// }
|
||||||
|
// Example(3D,VPT=[3.03052,-2.34905,8.07573],VPR=[70.4,0,326.2],VPD=82.6686): Automatic positioning of the fillet at the odd angle of this shifted prismoid is simple using {{attach()}} with the inherited $edge_angle.
|
||||||
|
// $fn=64;
|
||||||
|
// prismoid([20,15],[12,17], h=10, shift=[3,5]){
|
||||||
|
// attach(TOP+RIGHT,FWD+LEFT,inside=false)
|
||||||
|
// yrot(90)fillet(r=4);
|
||||||
|
// attach(RIGHT,BOT)
|
||||||
|
// cuboid([22,22,2]);
|
||||||
|
// }
|
||||||
|
|
||||||
module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, length, h, height, anchor=CENTER, spin=0, orient=UP)
|
module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, length, h, height, anchor=CENTER, spin=0, orient=UP)
|
||||||
{
|
{
|
||||||
|
@ -3722,9 +3745,13 @@ module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, length, h, height, anc
|
||||||
|
|
||||||
|
|
||||||
function fillet(l, r, ang, r1, r2, d, d1, d2, excess=0.1, anchor=CENTER, spin=0, orient=UP, h,height,length) = no_function("fillet");
|
function fillet(l, r, ang, r1, r2, d, d1, d2, excess=0.1, anchor=CENTER, spin=0, orient=UP, h,height,length) = no_function("fillet");
|
||||||
module fillet(l, r, ang=90, r1, r2, excess=0.01, d1, d2,d,length, h, height, anchor=CENTER, spin=0, orient=UP)
|
module fillet(l, r, ang, r1, r2, excess=0.01, d1, d2,d,length, h, height, anchor=CENTER, spin=0, orient=UP,
|
||||||
|
rounding,rounding1,rounding2,chamfer,chamfer1,chamfer2)
|
||||||
{
|
{
|
||||||
|
ang = first_defined([ang, u_add(u_mul($edge_angle,-1),180), 90]);
|
||||||
|
//echo(ang,180-$edge_angle);
|
||||||
rounding_edge_mask(l=l, r1=r1, r2=r2, ang=ang, excess=excess, d1=d1, d2=d2,d=d,r=r,length=length, h=h, height=height,
|
rounding_edge_mask(l=l, r1=r1, r2=r2, ang=ang, excess=excess, d1=d1, d2=d2,d=d,r=r,length=length, h=h, height=height,
|
||||||
|
chamfer1=chamfer1, chamfer2=chamfer2, chamfer=chamfer, rounding1=rounding1, rounding2=rounding2, rounding=rounding,
|
||||||
anchor=anchor, spin=spin, orient=orient, _remove_tag=false)
|
anchor=anchor, spin=spin, orient=orient, _remove_tag=false)
|
||||||
children();
|
children();
|
||||||
}
|
}
|
||||||
|
|
592
tutorials/Beziers_for_Beginners.md
Executable file
592
tutorials/Beziers_for_Beginners.md
Executable file
|
@ -0,0 +1,592 @@
|
||||||
|
# Béziers for Beginners
|
||||||
|
|
||||||
|
Bézier curves are parametric curves defined by polynomial equations. To work with Béziers in OpenSCAD we need to load the Bézier extension BOSL2/beziers.scad in addition to BOSL2/std.scad.
|
||||||
|
|
||||||
|
Bézier curves vary by the degree of the polynomial that defines the curve.
|
||||||
|
|
||||||
|
Quadratic Béziers, i.e. Bezier's of degree 2, are defined by [quadratic polynomials](https://en.wikipedia.org/wiki/Quadratic_polynomial). A quadratic Bézier has a starting control point, an ending control point, and, one intermediate control point that most often does not lie on the curve. The curve starts toward the intermediate control point and then turns so that it arrives at the endpoint from the direction of the intermediate control point.
|
||||||
|
|
||||||
|
![Image courtesy Wikipedia](images/bezier_2_big.gif "Quadratic Bézier Animation courtesy Wikipedia")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To visualize a Bézier curve we can use the module [debug_bezier()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#module-debug_bezier). The argument N tells debug_bezier the degree of the Bézier curve.
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = [[0,0], [30,60], [0,100]];
|
||||||
|
debug_bezier(bez, N = 2);
|
||||||
|
```
|
||||||
|
|
||||||
|
If we move any of the control points, we change the shape of the curve.
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = [[0,0], [100,50], [0,100]];
|
||||||
|
debug_bezier(bez, N = 2);
|
||||||
|
```
|
||||||
|
|
||||||
|
Cubic Bézier curves (degree 3) are defined by cubic polynomials. A cubic Bézier has four control points. The first and last control points are the endpoints of the curve. The curve starts toward the second control point and then turns so that it arrives at the endpoint from the direction of the third control point.
|
||||||
|
|
||||||
|
![Image courtesy Wikipedia](images/bezier_3_big.gif "Cubic Bézier Animation courtesy Wikipedia")
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = [[20,0], [100,40], [50,90], [25,80]];
|
||||||
|
debug_bezier(bez, N = 3);
|
||||||
|
```
|
||||||
|
|
||||||
|
By moving the second and third points on the list we change the shape of the curve.
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = [[20,0], [60,40], [-20,50], [25,80]];
|
||||||
|
debug_bezier(bez, N = 3);
|
||||||
|
```
|
||||||
|
|
||||||
|
For a live example of cubic Béziers see the [Desmos Graphing Calculator](https://www.desmos.com/calculator/cahqdxeshd).
|
||||||
|
|
||||||
|
Higher order Béziers such as Quartic (degree 4) and Quintic (degree 5) Béziers exist as well. Degree 4 Béziers are used by [round_corners()](https://github.com/BelfrySCAD/BOSL2/wiki/rounding.scad#function-round_corners) and in the continuous rounding operations of [rounded_prism()](https://github.com/BelfrySCAD/BOSL2/wiki/rounding.scad#functionmodule-rounded_prism).
|
||||||
|
|
||||||
|
|
||||||
|
![Image courtesy Wikipedia](images/bezier_4_big.gif "Quartic Bézier Animation courtesy Wikipedia")
|
||||||
|
|
||||||
|
Beziers with higher degree, and hence more control points, offer more control over the shape of the bezier curve. You can add more control points to a bezier if you need to refine the shape of the curve.
|
||||||
|
|
||||||
|
```openscad-2D;Anim;FrameMS=2000;Frames=4;VPT=[50,50,40];ImgOnly
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = [
|
||||||
|
[[0,0], [100,100], [0,80]],
|
||||||
|
[[0,0], [10,30], [100,100], [0,80]],
|
||||||
|
[[0,0], [10,30], [40,30], [100,100], [0,80]],
|
||||||
|
[[0,0], [10,30], [40,30], [100,100], [30,100], [0,80]]
|
||||||
|
];
|
||||||
|
debug_bezier(bez[$t*4], N=$t*4+2);
|
||||||
|
move([60,30]) color("blue") text(str("N = ",($t*4+2)));
|
||||||
|
```
|
||||||
|
### 3d Bézier Curves
|
||||||
|
|
||||||
|
Bézier curves are not restricted to the XY plane. We can define a 3d Bézier as easily as a 2d Bézier.
|
||||||
|
|
||||||
|
```openscad-2D;FlatSpin,VPR=[80,0,360*$t],VPT=[0,0,20],VPD=175
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = [[10,0,10], [30,30,-10], [-30,30,40], [-10,0,30]];
|
||||||
|
debug_bezier(bez, N = 3);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bézier Paths
|
||||||
|
|
||||||
|
A Bézier path is when we string together a sequence of Béiers with coincident endpoints.
|
||||||
|
|
||||||
|
The point counts arise as a natural consequence of what a bezier path is. If you have k beziers of order N then that's k(N+1) points, except we have k-1 overlaps, so instead it's
|
||||||
|
|
||||||
|
```math
|
||||||
|
k(N+1)-(k-1) = kN +k -k+1 = kN+1.
|
||||||
|
```
|
||||||
|
|
||||||
|
The list of control points for a Bézier is not an OpenSCAD path. If we treat the list bez[] as a path we would get a very different shape. Here the Bézier is in green and the OpenSCAd path through the control points is in red.
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = [[0,0], [30,30], [0,50], {70,30] [0,100]];
|
||||||
|
debug_bezier(bez, N = 2);
|
||||||
|
|
||||||
|
|
||||||
|
While the bez variable in these examples is a list of points, it is not the same as an OpenScad path, which is also a list of points. Here we have the Bézier curve shown in green and an OpenSCAD path through the same point-list in red.
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = [[20,0], [60,40], [-20,50], [25,80]];
|
||||||
|
debug_bezier(bez, N = 3);
|
||||||
|
color("red") stroke(bez);
|
||||||
|
```
|
||||||
|
|
||||||
|
To convert the Bézier curve to an OpenSCAD path, use the [bezpath_curve()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bezpath_curve) function.
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = [[20,0], [60,40], [-20,50], [25,80]];
|
||||||
|
path = bezpath_curve(bez, N = 3);
|
||||||
|
stroke(path);
|
||||||
|
```
|
||||||
|
|
||||||
|
Bézier paths can be made up of more than one Bézier curve. Quadratic Bezier paths have a multiple of 2 points plus 1, and cubic Bézier paths have a multiple of 3 points plus 1
|
||||||
|
|
||||||
|
This means that a series of 7 control points can be grouped into three (overlapping) sets of 3 and treated as a sequence of 3 quadratic beziers. The same 7 points can be grouped into two overlapping sets of 4 and treated as a sequence of two cubic beziers. The two paths have significantly different shapes.
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = [[0,0], [10,30], [20,0], [30,-30], [40,0], [50,30],[60,0]];
|
||||||
|
path = bezpath_curve(bez, N = 2); //make a quadratic Bézier path
|
||||||
|
stroke(path);
|
||||||
|
```
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = [[0,0], [10,30], [20,0], [30,-30], [40,0], [50,30],[60,0]];
|
||||||
|
path = bezpath_curve(bez, N=3); //make a cubic Bézier path
|
||||||
|
stroke(path);
|
||||||
|
```
|
||||||
|
|
||||||
|
By default [bezpath_curve()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bezpath_curve) takes a Bézier path and converts it to an OpenSCAD path by splitting each Bézier curve into 16 straight-line segments. The segments are not necessarily of equal length. Note that the special variable $fn has no effect on the number of steps. You can control this number using the **splinesteps** argument.
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = [[20,0], [60,40], [-20,50], [25,80]];
|
||||||
|
path = bezpath_curve(bez, splinesteps = 6);
|
||||||
|
stroke(path);
|
||||||
|
```
|
||||||
|
|
||||||
|
To close the path to the y-axis we can use the [bezpath\_close\_to\_axis()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bezpath_close_to_axis) function.
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = [[20,0], [60,40], [-20,50], [25,80]];
|
||||||
|
closed = bezpath_close_to_axis(bez, axis = "Y");
|
||||||
|
path = bezpath_curve(closed);
|
||||||
|
stroke(path, width = 2);
|
||||||
|
```
|
||||||
|
|
||||||
|
If we use [rotate_sweep()](https://github.com/BelfrySCAD/BOSL2/wiki/skin.scad#functionmodule-rotate_sweep) to sweep that path around the y-axis we have a solid vase-shaped object. Here we're using both $fn and the splinesteps argument to produce a smoother object.
|
||||||
|
|
||||||
|
```openscad-3D VPR = [80,0,20]
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
$fn = 72;
|
||||||
|
|
||||||
|
bez = [[20,0], [60,40], [-20,50], [25,80]];
|
||||||
|
closed = bezpath_close_to_axis(bez, axis = "Y");
|
||||||
|
path = bezpath_curve(closed, splinesteps = 32);
|
||||||
|
rotate_sweep(path,360);
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of closing the path all the way to the y-axis, we can use [bezpath_offset()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bezpath_offset) to duplicate the path 5 units to the left, and close the two paths at the top and bottom. Note that bezpath_offset takes an x,y pair as an offset value.
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
$fn = 72;
|
||||||
|
|
||||||
|
bez = [[20,0], [60,40], [-20,50], [25,80]];
|
||||||
|
closed = bezpath_offset([-5,0], bez);
|
||||||
|
debug_bezier(closed);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that [bezpath_offset()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bezpath_offset) does not ensure a uniform wall thickness. For a constant-width wall we need to offset the path along the normals. This we can do using [offset()](https://github.com/BelfrySCAD/BOSL2/wiki/regions.scad#function-offset), but we must first convert the Bézier to an OpenSCAD path, then reverse the offset path to create a closed path.
|
||||||
|
|
||||||
|
We could also do this using [offset_stroke()](https://github.com/BelfrySCAD/BOSL2/wiki/rounding.scad#functionmodule-offset_stroke) as a function. The [offset_stroke()](https://github.com/BelfrySCAD/BOSL2/wiki/rounding.scad#functionmodule-offset_stroke) function automates offsetting the path, reversing it and closing the path all in one step. To use [offset_stroke()](https://github.com/BelfrySCAD/BOSL2/wiki/rounding.scad#functionmodule-offset_stroke), we must also include the file [rounding.scad](https://github.com/BelfrySCAD/BOSL2/wiki/rounding.scad).
|
||||||
|
|
||||||
|
You can see the differences between the three methods here, with [bezpath_offset()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bezpath_offset) in blue, [offset()](https://github.com/BelfrySCAD/BOSL2/wiki/regions.scad#function-offset) in red, and [offset_stroke()]() in green.
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
include<BOSL2/rounding.scad>
|
||||||
|
$fn = 72;
|
||||||
|
|
||||||
|
bez = [[40,0], [110,40], [-60,50], [45,80]];
|
||||||
|
|
||||||
|
bez2 = bezpath_offset([5,0], bez);
|
||||||
|
path= bezpath_curve(bez2, splinesteps = 32);
|
||||||
|
color("blue") stroke(path);
|
||||||
|
|
||||||
|
path2 = bezier_curve(bez, splinesteps = 32);
|
||||||
|
closed2 = concat(path2,reverse(offset(path2,delta=5)),[bez[0]]);
|
||||||
|
right(30) color("red") stroke(closed2);
|
||||||
|
|
||||||
|
path3 = offset_stroke(bezier_curve(bez, splinesteps = 32), [5,0]);
|
||||||
|
right(60) color("green") stroke(path3, closed= true);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Sweeping a Bézier path offset using any of the three methods around the y-axis gives us a shape with an open interior. However, as this cross section shows, our new path does not close the bottom of the vase.
|
||||||
|
|
||||||
|
```openscad-3D, VPT=[0,60,40], VPR=[90,0,0], VPD=250
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
include<BOSL2/rounding.scad>
|
||||||
|
|
||||||
|
$fn = 72;
|
||||||
|
|
||||||
|
bez = [[15,0], [60,40], [-25,50], [25,80]];
|
||||||
|
path = offset_stroke(bezier_curve(bez, splinesteps = 32), [2,0]);
|
||||||
|
back_half(s = 200) rotate_sweep(path,360);
|
||||||
|
```
|
||||||
|
|
||||||
|
We'll use a cylinder with a height of 2 for the floor of our vase. At the bottom of the vase the radius of the hole is bez[0].x but we need to find the radius at y = 2. The function [bezier_line_intersection()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bezier_line_intersection) will return a list of u-values where a given line intersects our Bézier curve.
|
||||||
|
|
||||||
|
The u-value is a number between 0 and 1 that designates how far along the curve the intersections occur. In our case the line only crosses the Bézier at one point so we get the single-element list [0.0168783].
|
||||||
|
|
||||||
|
The function [bezier_points()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bezpath_points) will convert that list of u-values to a list of x,y coordinates. Drawing a line at y = 2 gives us the single-element list [[17.1687, 2]].
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
|
||||||
|
bez = [[15,0], [60,40], [-25,50], [25,80]];
|
||||||
|
debug_bezier(bez, N = 3);
|
||||||
|
line = [[0,2], [30,2]];
|
||||||
|
color("red") stroke(line);
|
||||||
|
u = bezier_line_intersection(bez,line);
|
||||||
|
echo(bezier_points(bez,u)); // [[17.1687, 2]]
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
That means a cyl() with a height of 2, a bottom radius of bez[0].x and a top radius of 17.1687 will fit our vase.
|
||||||
|
|
||||||
|
```openscad-3D, VPT=[0,60,12], VPR=[90,0,0], VPD=150
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
include<BOSL2/rounding.scad>
|
||||||
|
|
||||||
|
$fn = 72;
|
||||||
|
|
||||||
|
bez = [[15,0], [60,40], [-25,50], [25,80]];
|
||||||
|
path = offset_stroke(bezier_curve(bez, splinesteps = 32), [0,2]);
|
||||||
|
back_half(s = 200) rotate_sweep(path,360);
|
||||||
|
line = [[0,2], [30,2]];
|
||||||
|
u = bezier_line_intersection(bez,line).x;
|
||||||
|
r2 = bezier_points(bez,u).x;
|
||||||
|
color("red") cyl(h = 2, r1 = bez[0].x, r2 = r2, anchor = BOT);
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep in mind the fact that **$fn** controls the smoothness of the [rotate_sweep()](https://github.com/BelfrySCAD/BOSL2/wiki/skin.scad#functionmodule-rotate_sweep) operation while the smoothness of the Bézier is controlled by the **splinesteps** argument.
|
||||||
|
|
||||||
|
```openscad-3D NoAxes VPD=400 VPT=[45,45,10] Big
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
$fn = 72;
|
||||||
|
|
||||||
|
bez = [[15,0], [40,40], [-20,50], [20,80]];
|
||||||
|
closed = bezpath_offset([2,0], bez);
|
||||||
|
path = bezpath_curve(closed, splinesteps = 64);
|
||||||
|
|
||||||
|
rotate_sweep(path,360, $fn = 72);
|
||||||
|
right(60) rotate_sweep(path,360, $fn = 6);
|
||||||
|
right(120) rotate_sweep(path,360, $fn = 4);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2D Cubic Bézier Path Construction
|
||||||
|
|
||||||
|
Paths constructed as a series of cubic Bézier curves are familiar to users of Inkscape, Adobe Illustrator, and Affinity Designer. [The Bézier Game](https://bezier.method.ac) illustrates how these drawing programs work.
|
||||||
|
|
||||||
|
BOSL2 includes four functions for constructing Cubic Bézier paths:
|
||||||
|
|
||||||
|
[bez_begin()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bez_begin) and [bez_end()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bez_end) define the endpoints of a simple cubic Bézier curve.
|
||||||
|
|
||||||
|
Because each constructor function produces a list of points , we'll use the [flatten()](https://github.com/BelfrySCAD/BOSL2/wiki/lists.scad#function-flatten) function to consolidate them into a single list.
|
||||||
|
|
||||||
|
There are three different ways to specify the location of the endpoints and control points.
|
||||||
|
|
||||||
|
First, you can specify the endpoints by vectors and the control points by angle, measured from X+ in the XY plane, and distance:
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
bez = flatten([
|
||||||
|
bez_begin([0,0], 45, 42.43),
|
||||||
|
bez_end([100,0], 90, 30),
|
||||||
|
]);
|
||||||
|
debug_bezier(bez,N=3);
|
||||||
|
```
|
||||||
|
|
||||||
|
Second, can specify the XY location of the endpoint and that end's control point as a vector from the control point:
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
bez = flatten([
|
||||||
|
bez_begin([0,0], [30,30]),
|
||||||
|
bez_end([100,0], [0,30]),
|
||||||
|
]);
|
||||||
|
debug_bezier(bez,N=3);
|
||||||
|
```
|
||||||
|
|
||||||
|
Third, you can specify the endpoints by vectors, and the control points by a direction vector and a distance:
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
bez = flatten([
|
||||||
|
bez_begin([0,0], BACK+RIGHT, 42.43),
|
||||||
|
bez_end([100,0], [0,1], 30),
|
||||||
|
]);
|
||||||
|
debug_bezier(bez,N=3);
|
||||||
|
```
|
||||||
|
|
||||||
|
BOSL2 includes the [bez_joint()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bez_joint) constructor for adding corners to a Bézier path. A corner point has three control points. These are the point on the path where we want the corner, and the approaching and departing control points. We can specify these control points in any of the three ways shown above.
|
||||||
|
|
||||||
|
Here's an example using angle and distance to specify a corner. Note that the angles are specified first, and then the distances:
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
bez = flatten([
|
||||||
|
bez_begin([0,0], 45, 42.43),
|
||||||
|
bez_joint([40,20], 90,0, 30,30),
|
||||||
|
bez_end([100,0], 90, 30),
|
||||||
|
]);
|
||||||
|
debug_bezier(bez,N=3);
|
||||||
|
```
|
||||||
|
|
||||||
|
The fourth cubic Bézier path constructor is [bez_tang()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bez_tang). This constructor makes smooth joint. It also has three control points, one on the path and the approaching and departing control points. Because all three points lie on a single line, we need only specify the angle of the departing control point. As in this example you can specify different distances for the approaching and departing controls points. If you specify only a single distance, it will be used for both.
|
||||||
|
|
||||||
|
We can add a smooth joint to the last example:
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
bez = flatten([
|
||||||
|
bez_begin([0,0], 45, 42.43),
|
||||||
|
bez_joint([40,20], 90,0, 30,30),
|
||||||
|
bez_tang([80,50], 0, 20,40),
|
||||||
|
bez_end([100,0], 90, 30),
|
||||||
|
]);
|
||||||
|
debug_bezier(bez,N=3);
|
||||||
|
```
|
||||||
|
|
||||||
|
It is not necessary to use the same notation to describe the entire Bézier path. We can mix the Angle, Vector and Vector with Distance notations within a single path:
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
bez = flatten([
|
||||||
|
bez_begin([0,0], [30,30]),
|
||||||
|
bez_joint([40,20], BACK,RIGHT, 30,30),
|
||||||
|
bez_tang([80,50], 0, 20,40),
|
||||||
|
bez_end([100,0], BACK, 30),
|
||||||
|
]);
|
||||||
|
debug_bezier(bez,N=3);
|
||||||
|
```
|
||||||
|
|
||||||
|
When using the cubic Bézier constructors our Bézier path must always begin with the [bez_begin()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bez_begin) and [bez_end()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bez_end) constructors.
|
||||||
|
|
||||||
|
This might make some of the examples in [The Bézier Game](https://bezier.method.ac) appear puzzling.
|
||||||
|
Take for example the circle. We can duplicate those results by replacing their starting tangential control point with our beginning and end points.
|
||||||
|
|
||||||
|
The correct distance to place the approaching and departing control points to closely approximate a circle is
|
||||||
|
|
||||||
|
```math
|
||||||
|
r * (4/3) * tan(180/2*n)
|
||||||
|
```
|
||||||
|
|
||||||
|
where r is the radius of the circle and n is the number of bez_tang() segments required to make a full circle. Remember that our bez_begin() and bez_end() segments taken together simulate a bez_tang() segment. For our case, where we're closing the circle in 4 segments, the formula evaluates to r * 0.552284.
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
r = 50; // radius of the circle
|
||||||
|
n = 4; //bezier segments to complete circle
|
||||||
|
d = r * (4/3) * tan(180/(2*n)); //control point distance
|
||||||
|
|
||||||
|
bez = flatten([
|
||||||
|
bez_begin([-r,0], 90, d),
|
||||||
|
bez_tang ([0,r], 0, d),
|
||||||
|
bez_tang ([r,0], -90, d),
|
||||||
|
bez_tang ([0,-r], 180, d),
|
||||||
|
bez_end ([-r,0], -90, d)
|
||||||
|
]);
|
||||||
|
|
||||||
|
debug_bezier(bez, N=3);
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly, for the heart-shaped path we'll replace a corner point with the start and end points:
|
||||||
|
|
||||||
|
```openscad-2D
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = flatten([
|
||||||
|
bez_begin([0,25], 40, 40),
|
||||||
|
bez_joint([0,-25], 30, 150, 60, 60),
|
||||||
|
bez_end ([0,25], 140, 40)
|
||||||
|
]);
|
||||||
|
debug_bezier(bez, N=3);
|
||||||
|
```
|
||||||
|
|
||||||
|
The first shape in [The Bézier Game](https://bezier.method.ac) past the stages with hints is the outline of the automobile. Here's how we can duplicate that with our cubic Bézier constructors:
|
||||||
|
|
||||||
|
```openscad-3D,Big,NoScales,VPR=[0,0,0],VPT=[100,25,0],VPF=22
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = flatten([
|
||||||
|
bez_begin([0,0], BACK, 15),
|
||||||
|
bez_joint([0,9], FWD, RIGHT, 10,10),
|
||||||
|
bez_joint([5,9], LEFT, 70, 9,20),
|
||||||
|
bez_tang([80,65], 3, 35, 20),
|
||||||
|
bez_joint([130,60], 160, -60, 10, 30),
|
||||||
|
bez_joint([140,42], 120, 0, 20,55),
|
||||||
|
bez_joint([208,9], BACK, RIGHT, 10,6),
|
||||||
|
bez_joint([214,9], LEFT, FWD, 10,10),
|
||||||
|
bez_joint([214,0], BACK, LEFT, 10,10),
|
||||||
|
bez_joint([189,0], RIGHT, -95, 10,10),
|
||||||
|
bez_tang([170,-17], LEFT, 10),
|
||||||
|
bez_joint([152,0], -85, LEFT, 10,10),
|
||||||
|
bez_joint([52,0], RIGHT, -95, 10,10),
|
||||||
|
bez_tang([33,-17], LEFT, 10),
|
||||||
|
bez_joint([16,0], -85,LEFT, 10,10),
|
||||||
|
bez_end ([0,0], RIGHT,10)
|
||||||
|
]);
|
||||||
|
|
||||||
|
debug_bezier(bez, N = 3);
|
||||||
|
```
|
||||||
|
|
||||||
|
### A Bézier Dish
|
||||||
|
|
||||||
|
We can make a heart shaped dish using a 2D Bézier path to define the shape. When we convert the Bézier curve to a Bézier path with [bezpath_curve()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bezpath_curve) we can smooth the resulting path by increasing *splinesteps* to 64.
|
||||||
|
|
||||||
|
```openscad-3d
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
include<BOSL2/rounding.scad>
|
||||||
|
|
||||||
|
bez = flatten([
|
||||||
|
bez_begin([0,50], 40, 100),
|
||||||
|
bez_joint([0,-50], 30, 150, 120, 120),
|
||||||
|
bez_end ([0,50], 140, 100)
|
||||||
|
]);
|
||||||
|
|
||||||
|
path = bezpath_curve(bez, splinesteps = 64);
|
||||||
|
linear_sweep(h = 2, path);
|
||||||
|
|
||||||
|
region = offset_stroke(path, -3, closed = true);
|
||||||
|
linear_sweep(h = 20, region);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3D Cubic Bézier Path Construction
|
||||||
|
|
||||||
|
BOSL2 includes a set of constructor functions for creating cubic Bézier paths. They can create 2d or 3d Béziers. There are constructors for beginning and end points as well as connectors for corner and tangential connections between Bézier curves. Each function gives you the choice of specifying the curve using Angle Notation, Vector Notation, or by Direction Vector and Distance.
|
||||||
|
|
||||||
|
### 3D Path by Angle Notation
|
||||||
|
|
||||||
|
The path by angle constructors can be used to create 3D Bézier paths by specifying a 3D point on the curve and listing the angle (from the X axis) and distance to the departing and/or departing control point, then adding a p argument that is the angle away from the Z axis for that control point.
|
||||||
|
|
||||||
|
```openscad-3D,FlatSpin,NoScales,VPR=[85,0,360*$t],VPT=[0,0,20]
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = flatten([
|
||||||
|
bez_begin ([-50,0,0], 90, 25, p=90),
|
||||||
|
bez_joint ([0,50,50], 180,0 , 50,50, p1=45, p2=45),
|
||||||
|
bez_tang ([50,0,0], -90, 25, p=90),
|
||||||
|
bez_joint ([0,-50,50], 0,180 , 25,25, p1=135,p2=135),
|
||||||
|
bez_end ([-50,0,0], -90, 25, p=90)
|
||||||
|
]);
|
||||||
|
|
||||||
|
debug_bezier(bez, N=3);
|
||||||
|
```
|
||||||
|
## 3D Path by Vector Notation
|
||||||
|
|
||||||
|
The cubic Bézier path constructors can also be used to create 3D Bézier paths by specifying the control points using vectors. The first vector is the location of the control point that lies on the Bézier path, followed by vectors pointing from that control point to the approaching and/or departing control points.
|
||||||
|
|
||||||
|
```openscad-3D,FlatSpin,NoScales,VPR=[80,0,360*$t],,VPT=[0,0,20]
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = flatten([
|
||||||
|
bez_begin([-50,0,0], [0,25,0]),
|
||||||
|
bez_joint([0,50,50], [-35,0,35], [35,0,35]),
|
||||||
|
bez_tang ([50,0,0], [0,-25,0]),
|
||||||
|
bez_joint([0,-50,50], [18,0,-18], [-18,0,-18]),
|
||||||
|
bez_end ([-50,0,0], [0,-25,0])
|
||||||
|
]);
|
||||||
|
|
||||||
|
debug_bezier(bez, N=3);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3D Path by Direction Vector and Distance
|
||||||
|
|
||||||
|
The third method for specifying 3D cubic Bézier Paths is by Direction Vector and distance. For [bez_tang()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bez_tang) and [bez_joint()](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad#function-bez_joint), if r1 is given and r2 is not, the function uses the value of r1 for r2.
|
||||||
|
|
||||||
|
```openscad-3D,FlatSpin,NoScales,VPR=[80,0,360*$t],,VPT=[0,0,20]
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
bez = flatten([
|
||||||
|
bez_begin([-50,0,0], BACK, 25),
|
||||||
|
bez_joint([0,50,50], LEFT+UP, RIGHT+UP, 50,50),
|
||||||
|
bez_tang ([50,0,0], FWD, 25),
|
||||||
|
bez_joint([0,-50,50], RIGHT+DOWN, LEFT+DOWN, 25,25),
|
||||||
|
bez_end ([-50,0,0], FWD, 25)
|
||||||
|
]);
|
||||||
|
|
||||||
|
debug_bezier(bez, N=3);
|
||||||
|
```
|
||||||
|
|
||||||
|
### A Bud Vase Design using both 2D and 3D Bézier Paths
|
||||||
|
|
||||||
|
We can use a 2D Bézier path to define the shape of our bud vase as we did in the examples above. Instead of using a [rotate_sweep()](https://github.com/BelfrySCAD/BOSL2/wiki/skin.scad#functionmodule-rotate_sweep) to make a vase with a circular cross section we'll use a 3D Bèzier path that both defines the cross section and makes the top more interesting. This design uses the [skin()](https://github.com/BelfrySCAD/BOSL2/wiki/skin.scad#functionmodule-skin) module to create the final geometry.
|
||||||
|
|
||||||
|
```openscad-3d,Big
|
||||||
|
include<BOSL2/std.scad>
|
||||||
|
include<BOSL2/beziers.scad>
|
||||||
|
|
||||||
|
//Side Bézier Path
|
||||||
|
side_bez = [[20,0], [40,40], [-10,70], [20,100]];
|
||||||
|
side = bezpath_curve(side_bez, splinesteps = 32);
|
||||||
|
h = last(side).y;
|
||||||
|
steps = len(side)-1;
|
||||||
|
step = h/steps;
|
||||||
|
wall = 2;
|
||||||
|
|
||||||
|
//Layer Bézier Path
|
||||||
|
size = side_bez[0].x; // size of the base
|
||||||
|
d = size * 0.8; // intermediate control point distance
|
||||||
|
theta = 65; // adjusts layer "wavyness".
|
||||||
|
bz = 5 * cos(theta); // offset to raise layer curve minima above z = 0;
|
||||||
|
|
||||||
|
layer_bez = flatten([
|
||||||
|
bez_begin ([-size,0,bz], 90, d, p=theta),
|
||||||
|
bez_tang ([0, size,bz], 0, d, p=theta),
|
||||||
|
bez_tang ([size, 0,bz], -90, d, p=theta),
|
||||||
|
bez_tang ([0,-size,bz], 180, d, p=theta),
|
||||||
|
bez_end ([-size,0,bz], -90, d, p=180 - theta)
|
||||||
|
]);
|
||||||
|
|
||||||
|
layer = bezpath_curve(layer_bez);
|
||||||
|
|
||||||
|
function layer_xy_scale(z) =
|
||||||
|
let (sample_z = side_bez[0].y + z * step) // the sampling height
|
||||||
|
let (u = bezier_line_intersection(side_bez, [[0, sample_z],[1, sample_z]]))
|
||||||
|
flatten(bezier_points(side_bez,u)).x / side_bez[0].x;
|
||||||
|
|
||||||
|
outside =[for(i=[0:steps]) scale([layer_xy_scale(i),layer_xy_scale(i),1],up(i*step, layer))];
|
||||||
|
inside = [for (curve = outside) hstack(offset(path2d(curve), delta = -2, same_length = true), column(curve,2))];
|
||||||
|
|
||||||
|
base = path3d(path2d(outside[0])); //flatten the base but keep as a 3d path
|
||||||
|
floor = up(wall, path3d(offset(path2d(outside[0]), -wall)));
|
||||||
|
|
||||||
|
skin([ base, each outside, each reverse(inside), floor ], slices=0, refine=1, method="fast_distance");
|
||||||
|
|
||||||
|
```
|
Loading…
Reference in a new issue