Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Richard Milewski 2024-09-20 13:52:48 -07:00
commit d2bfb25efd
9 changed files with 611 additions and 134 deletions

View file

@ -909,6 +909,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
dummy2=assert(align_list==[undef] || is_def(child), "Cannot use 'align' without 'child'")
assert(!inside || is_def(child), "Cannot use 'inside' without 'child'")
assert(inset==0 || is_def(child), "Cannot specify 'inset' without 'child'")
assert(inset==0 || is_def(align), "Cannot specify 'inset' without 'align'")
assert(shiftout==0 || is_def(child), "Cannot specify 'shiftout' without 'child'");
factor = inside?-1:1;
$attach_to = child;
@ -920,11 +921,13 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
anchor = is_string(anchors[anch_ind])? anchors[anch_ind]
: two_d?_force_anchor_2d(anchors[anch_ind])
: point3d(anchors[anch_ind]);
$anchor=anchor;
anchor_data = _find_anchor(anchor, $parent_geom);
anchor_pos = anchor_data[1];
anchor_dir = factor*anchor_data[2];
anchor_spin = !inside || anchor==TOP || anchor==BOT ? anchor_data[3] : 180+anchor_data[3];
$anchor=anchor;
anchor_spin = two_d || !inside || anchor==TOP || anchor==BOT ? anchor_data[3]
: let(spin_dir = rot(anchor_data[3],from=UP, to=-anchor_dir, p=BACK))
_compute_spin(anchor_dir,spin_dir);
for(align_ind = idx(align_list)){
align = is_undef(align_list[align_ind]) ? undef
: assert(is_vector(align_list[align_ind],2) || is_vector(align_list[align_ind],3), "align direction must be a 2-vector or 3-vector")
@ -934,34 +937,40 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
$align=align;
dummy=assert(is_undef(align) || all_zero(v_mul(anchor,align)),
str("Invalid alignment: align value (",align,") includes component parallel to parent anchor (",anchor,")"));
// Now compute position on the parent (including alignment but not inset) where the child will be anchored
pos = is_undef(align) ? anchor_data[1] : _find_anchor(anchor+align, $parent_geom)[1];
$attach_anchor = list_set(anchor_data, 1, pos); ///
$attach_anchor = list_set(anchor_data, 1, pos); // Never used; For user informational use? Should this be set at all?
startdir = two_d || is_undef(align)? undef
: anchor==UP || anchor==DOWN ? BACK
: UP - (anchor*UP)*anchor/(anchor*anchor);
: UP - (anchor*UP)*anchor/(anchor*anchor); // Component of UP perpendicular to anchor
enddir = is_undef(child) || child.z==0 ? UP : BACK;
// Compute adjustment to the child anchor for position purposes. This adjustment
// accounts for the change in the anchor needed to to alignment.
child_adjustment = is_undef(align)? CTR
: two_d ? rot(to=child,from=-factor*anchor,p=align)
: apply( frame_map(x=child, z=enddir)
*frame_map(x=-factor*anchor, z=startdir, reverse=true)
*frame_map(x=-factor*anchor, z=startdir, reverse=true)
*rot(v=anchor,-spin), align);
// The $anchor_override anchor value forces an override of the *position* only for the anchor
// used when attachable() places the child
$anchor_override = all_zero(child_adjustment)? inside?child:undef
: child+child_adjustment;
reference = two_d? BACK : UP;
// inset_dir is the direction for insetting when alignment is in effect
inset_dir = is_undef(align) ? CTR
: two_d ? rot(to=reference, from=anchor,p=align)
: apply(zrot(-factor*spin)*frame_map(x=reference, z=BACK)*frame_map(x=factor*anchor, z=startdir, reverse=true),
align);
spinaxis = two_d? UP : anchor_dir;
olap = - overlap * reference - inset*inset_dir + shiftout * (inset_dir + factor*reference);
if (norot || (approx(anchor_dir,reference) && anchor_spin==0)) {
if (norot || (approx(anchor_dir,reference) && anchor_spin==0))
translate(pos) rot(v=spinaxis,a=factor*spin) translate(olap) default_tag("remove",inside) children();
} else {
else
translate(pos)
rot(v=spinaxis,a=factor*spin)
rot(anchor_spin,from=reference,to=anchor_dir){
rot(anchor_spin,from=reference,to=anchor_dir)
translate(olap)
default_tag("remove",inside) children();}}
default_tag("remove",inside) children();
}
}
}
@ -2044,7 +2053,7 @@ module edge_profile(edges=EDGES_ALL, except=[], excess=0.01, convexity=10) {
// Topics: Attachments, Masking
// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_profile(), edge_mask(), face_mask(), corner_mask()
// Usage:
// PARENT() edge_profile([edges], [except=], [convexity=], [flip=], [corner_type=]) CHILDREN;
// PARENT() edge_profile([edges], [except], [convexity=], [flip=], [corner_type=]) CHILDREN;
// Description:
// Takes an asymmetric 2D mask shape and attaches it to the selected edges and corners, with the appropriate
// orientation and extruded length to be `diff()`ed away, to give the edges and corners a matching profile.
@ -2064,6 +2073,7 @@ module edge_profile(edges=EDGES_ALL, except=[], excess=0.01, convexity=10) {
// Arguments:
// edges = Edges to mask. See [Specifying Edges](attachments.scad#subsection-specifying-edges). Default: All edges.
// except = Edges to explicitly NOT mask. See [Specifying Edges](attachments.scad#subsection-specifying-edges). Default: No edges.
// ---
// excess = Excess length to extrude the profile to make edge masks. Default: 0.01
// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10
// flip = If true, reverses the orientation of any external profile parts at each edge. Default false
@ -2818,9 +2828,9 @@ module attachable(
) {
dummy1 =
assert($children==2, "attachable() expects exactly two children; the shape to manage, and the union of all attachment candidates.")
assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
assert(is_undef(spin) || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient));
assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor))
assert(is_undef(spin) || is_vector(spin,3) || is_num(spin), str("Invalid spin: ",spin))
assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient));
anchor = first_defined([anchor, CENTER]);
spin = default(spin, 0);
orient = is_def($anchor_override)? UP : default(orient, UP);
@ -3031,12 +3041,6 @@ function named_anchor(name, pos, orient, spin, rot, flip) =
[name, pos, dir, spin];
function _force_rot(T) =
[for(i=[0:3])
[for(j=[0:3]) j<3 ? T[i][j] :
i==3 ? 1
: 0]];
// Function: attach_geom()
// Synopsis: Returns the internal geometry description of an attachable object.
// Topics: Attachments
@ -3157,11 +3161,6 @@ function _force_rot(T) =
// geom = attach_geom(region=region, l=length, extent=false);
//
function _local_struct_val(struct, key)=
assert(is_def(key),"key is missing")
let(ind = search([key],struct)[0])
ind == [] ? undef : struct[ind][1];
function attach_geom(
size, size2,
@ -3181,12 +3180,12 @@ function attach_geom(
assert(is_list(anchors))
assert(is_bool(two_d))
assert(is_vector(axis))
let(
over_f = is_undef(override) ? function(anchor) [undef,undef,undef]
: is_func(override) ? override
: function(anchor) _local_struct_val(override,anchor)
)
!is_undef(size)? (
let(
over_f = is_undef(override) ? function(anchor) [undef,undef,undef]
: is_func(override) ? override
: function(anchor) _local_struct_val(override,anchor)
)
two_d? (
let(
size2 = default(size2, size.x),
@ -3209,8 +3208,8 @@ function attach_geom(
) : !is_undef(vnf)? (
assert(is_vnf(vnf))
assert(two_d == false)
extent? ["vnf_extent", vnf, cp, offset, anchors] :
["vnf_isect", vnf, cp, offset, anchors]
extent? ["vnf_extent", vnf, over_f, cp, offset, anchors]
: ["vnf_isect", vnf, over_f, cp, offset, anchors]
) : !is_undef(region)? (
assert(is_region(region),2)
let( l = default(l, h) )
@ -3460,39 +3459,40 @@ function _attach_geom_edge_path(geom, edge) =
/// p = If given as a VNF, path, or point, applies the affine3d transformation matrix to it and returns the result.
function _attach_transform(anchor, spin, orient, geom, p) =
assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
assert(is_undef(spin) || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient))
assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor))
assert(is_undef(spin) || is_vector(spin,3) || is_num(spin), str("Invalid spin: ",spin))
assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient))
let(
anchor = default(anchor, CENTER),
spin = default(spin, 0),
orient = default(orient, UP),
two_d = _attach_geom_2d(geom),
m = ($attach_to != undef)? (
let(
anch = _find_anchor($attach_to, geom),
pos = is_undef($anchor_override) ? anch[1]
: _find_anchor(_make_anchor_legal($anchor_override,geom),geom)[1]
)
two_d?
assert(is_num(spin))
/*affine3d_zrot(spin) * */
rot(to=FWD, from=point3d(anch[2]))
* affine3d_translate(point3d(-pos))
:
assert(is_num(spin) || is_vector(spin,3))
let(
ang = vector_angle(anch[2], DOWN),
axis = vector_axis(anch[2], DOWN),
ang2 = (anch[2]==UP || anch[2]==DOWN)? 0 : 180-anch[3],
axis2 = rot(p=axis,[0,0,ang2])
)
affine3d_rot_by_axis(axis2,ang)
* (is_num(spin)? affine3d_zrot(ang2+spin)
: affine3d_zrot(spin.z) * affine3d_yrot(spin.y) * affine3d_xrot(spin.x)
* affine3d_zrot(ang2))
* affine3d_translate(point3d(-pos))
) : (
m = ($attach_to != undef) ? // $attach_to is the attachment point on this object
( // which will attach to the parent
let(
anch = _find_anchor($attach_to, geom),
// if $anchor_override is set it defines the object position anchor (but note not direction or spin).
// Otherwise we use the provided anchor for the object.
pos = is_undef($anchor_override) ? anch[1]
: _find_anchor(_make_anchor_legal($anchor_override,geom),geom)[1]
)
two_d?
assert(is_num(spin))
affine3d_zrot(spin)
* rot(to=FWD, from=point3d(anch[2]))
* affine3d_translate(point3d(-pos))
:
let(
spinT = is_num(spin) ? affine3d_zrot(-anch[3]-spin)
: affine3d_zrot(-spin.z) * affine3d_yrot(-spin.y) * affine3d_xrot(-spin.x)
* affine3d_zrot(-anch[3])
)
affine3d_yrot(180)
* spinT
* rot(from=anch[2],to=UP)
* affine3d_translate(point3d(-pos))
)
:
let(
anchor = is_undef($attach_alignment) ? anchor
: two_d? _make_anchor_legal(zrot(-spin,$attach_alignment),geom)
@ -3503,16 +3503,14 @@ function _attach_transform(anchor, spin, orient, geom, p) =
assert(is_num(spin))
affine3d_zrot(spin) * affine3d_translate(point3d(-pos))
:
assert(is_num(spin) || is_vector(spin,3))
let(
axis = vector_axis(UP,orient),
axis = vector_axis(UP,orient), // Returns BACK if orient is UP
ang = vector_angle(UP,orient)
)
affine3d_rot_by_axis(axis,ang)
* ( is_num(spin)? affine3d_zrot(spin)
: affine3d_zrot(spin.z) * affine3d_yrot(spin.y) * affine3d_xrot(spin.x))
* affine3d_translate(point3d(-pos))
)
)
is_undef(p)? m
: is_vnf(p) && p==EMPTY_VNF? p
@ -3557,12 +3555,6 @@ function _get_cp(geom) =
function _force_anchor_2d(anchor) =
is_undef(anchor) || len(anchor)==2 ? anchor :
assert(anchor.y==0 || anchor.z==0, "Anchor for a 2D shape cannot be fully 3D. It must have either Y or Z component equal to zero.")
anchor.y==0 ? [anchor.x,anchor.z] : point2d(anchor);
/// Internal Function: _find_anchor()
/// Usage:
/// anchorinfo = _find_anchor(anchor, geom);
@ -3618,28 +3610,37 @@ function _find_anchor(anchor, geom) =
axy = point2d(anch),
bot = point3d(v_mul(point2d(size )/2, axy), -h/2),
top = point3d(v_mul(point2d(size2)/2, axy) + shift, h/2),
edge = top-bot,
pos = point3d(cp) + lerp(bot,top,u) + offset,
vecs = anchor==CENTER? [UP]
: [
if (anch.x!=0) unit(rot(from=UP, to=[(top-bot).x,0,max(0.01,h)], p=[axy.x,0,0]), UP),
if (anch.y!=0) unit(rot(from=UP, to=[0,(top-bot).y,max(0.01,h)], p=[0,axy.y,0]), UP),
// Find vectors of the faces involved in the anchor
facevecs =
[
if (anch.x!=0) unit(rot(from=UP, to=[edge.x,0,max(0.01,h)], p=[axy.x,0,0]), UP),
if (anch.y!=0) unit(rot(from=UP, to=[0,edge.y,max(0.01,h)], p=[0,axy.y,0]), UP),
if (anch.z!=0) unit([0,0,anch.z],UP)
],
vec2 = anchor==CENTER? UP
: len(vecs)==1? unit(vecs[0],UP)
: len(vecs)==2? vector_bisect(vecs[0],vecs[1])
: let(
v1 = vector_bisect(vecs[0],vecs[2]),
v2 = vector_bisect(vecs[1],vecs[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),
vec = default(override[1],rot(from=UP, to=axis, p=vec2)),
pos2 = default(override[0],rot(from=UP, to=axis, p=pos))
) [anchor, pos2, vec, default(override[2],oang)]
dir = anchor==CENTER? UP
: len(facevecs)==1? unit(facevecs[0],UP)
: len(facevecs)==2? vector_bisect(facevecs[0],facevecs[1])
: let(
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),
final_dir = default(override[1],rot(from=UP, to=axis, p=dir)),
final_pos = default(override[0],rot(from=UP, to=axis, p=pos)),
// If the anchor is on a face or horizontal edge we take the oang value for spin
// If the anchor is on a vertical or sloped edge or corner we want to align the spin to point upward along the edge
// The "native" spin direction is the rotation of UP to the anchor direction
// The desired spin direction is the edge vector
// The axis of rotation is the direction vector, so we need component of edge perpendicular to dir
spin = anchor.x!=0 && anchor.y!=0 ? _compute_spin(dir, edge) //sign(anchor.x)*vector_angle(edge - (edge*dir)*dir/(dir*dir), rot(from=UP,to=dir,p=BACK))
: oang
) [anchor, final_pos, final_dir, default(override[2],spin)]
) : type == "conoid"? ( //r1, r2, l, shift
assert(anchor.z == sign(anchor.z), "The Z component of an anchor for a cylinder/cone must be -1, 0, or 1")
let(
@ -3678,8 +3679,11 @@ function _find_anchor(anchor, geom) =
vec = unit(v_mul(r,anchor),UP)
) [anchor, pos, vec, oang]
) : type == "vnf_isect"? ( //vnf
let( vnf=geom[1] )
approx(anchor,CTR)? [anchor, cp, UP, 0] : // CENTER anchors anchor on cp, "origin" anchors on [0,0]
let(
vnf=geom[1],
override = geom[2](anchor)
) // CENTER anchors anchor on cp, "origin" anchors on [0,0]
approx(anchor,CTR)? [anchor, default(override[0],cp),default(override[1],UP),default(override[2], 0)] :
vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor), 0] :
let(
eps = 1/2048,
@ -3726,19 +3730,54 @@ function _find_anchor(anchor, geom) =
n = unit(sum(unorms)),
oang = approx(point2d(n), [0,0])? 0 : atan2(n.y, n.x) + 90
)
[anchor, pos, n, oang]
[anchor, default(override[0],pos),default(override[1], n),default(override[2], oang)]
) : type == "vnf_extent"? ( //vnf
let( vnf=geom[1] )
approx(anchor,CTR)? [anchor, cp, UP, 0] : // CENTER anchors anchor on cp, "origin" anchors on [0,0]
let(
vnf=geom[1],
override = geom[2](anchor)
) // CENTER anchors anchor on cp, "origin" anchors on [0,0]
approx(anchor,CTR)? [anchor, default(override[0],cp),default(override[1],UP),default(override[2], 0)] :
vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor,UP), 0] :
let(
rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]),
maxx = max(column(rpts,0)),
idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i],
dir = len(idxs)>2 ? [anchor,oang]
: len(idxs)==2 ?
let(
edgefaces = _vnf_find_edge_faces(vnf,idxs),
edge = select(vnf[0],idxs)
)
len(edgefaces)==0 ? [anchor,oang]
: assert(len(edgefaces)==2, "Invalid polyhedron encountered while computing VNF anchor")
edge[0]==edge[1] ? [anchor,oang] // two "edge" points are the same, so give up
: let(
direction= unit(mean([for(face=edgefaces) polygon_normal(select(vnf[0],vnf[1][face]))])),
edgedir = edge[1]-edge[0],
nz = [for(i=[0:2]) if (!approx(edgedir[i],0)) i],
flip = edgedir[last(nz)] < 0 ? -1 : 1,
spin = _compute_spin(direction, flip*edgedir)
)
[direction,spin]
: let(
vertices = vnf[0],
faces = vnf[1],
cornerfaces = _vnf_find_corner_faces(vnf,idxs[0]), // faces = [3,9,12] indicating which faces
normals = [for(faceind=cornerfaces) polygon_normal(select(vnf[0], faces[faceind]))],
angles = [for(faceind=cornerfaces)
let(
thisface = faces[faceind],
vind = search(idxs[0],thisface)[0]
)
vector_angle(select(vertices, select(thisface,vind-1,vind+1)))
],
direc = unit(angles*normals)
)
[direc, atan2(direc.y,direc.x)+90],
avep = sum(select(rpts,idxs))/len(idxs),
mpt = approx(point2d(anchor),[0,0])? [maxx,0,0] : avep,
pos = point3d(cp) + rot(from=RIGHT, to=anchor, p=mpt)
) [anchor, pos, anchor, oang]
) [anchor, default(override[0],pos),default(override[1],dir[0]),default(override[2],dir[1])]
) : type == "trapezoid"? ( //size, size2, shift, override
let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
assert(all_comps_good, "All components of an anchor for a rectangle/trapezoid must be -1, 0, or 1")
@ -4568,4 +4607,39 @@ module _show_cube_faces(faces, size=20, toplabel,botlabel) {
color("yellow",0.7) cuboid(size=size);
}
/// Internal utility function
function _force_rot(T) =
[for(i=[0:3])
[for(j=[0:3]) j<3 ? T[i][j] :
i==3 ? 1
: 0]];
function _local_struct_val(struct, key)=
assert(is_def(key),"key is missing")
let(ind = search([key],struct)[0])
ind == [] ? undef : struct[ind][1];
function _force_anchor_2d(anchor) =
is_undef(anchor) || len(anchor)==2 ? anchor :
assert(anchor.y==0 || anchor.z==0, "Anchor for a 2D shape cannot be fully 3D. It must have either Y or Z component equal to zero.")
anchor.y==0 ? [anchor.x,anchor.z] : point2d(anchor);
// Compute spin angle based on a anchor direction and desired spin direction
// anchor_dir assumed to be a unit vector; no assumption on spin_dir
// Takes the component of the spin direction perpendicular to the anchor
// direction and gives the spin angle that achieves it.
function _compute_spin(anchor_dir, spin_dir) =
let(
native_dir = rot(from=UP, to=anchor_dir, p=BACK),
spin_dir = spin_dir - (spin_dir*anchor_dir)*anchor_dir, // component of spin_dir perpendicular to anchor_dir
angle = vector_angle(native_dir,spin_dir),
sign = cross(native_dir,spin_dir)*anchor_dir<0 ? -1 : 1
)
sign*angle;
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -3523,12 +3523,12 @@ function _gear_tooth_profile(
// mod = The module of the gear, pitch diameter divided by tooth count.
// diam_pitch = The diametral pitch, or number of teeth per inch of pitch diameter. Note that the diametral pitch is a completely different thing than the pitch diameter.
// circ_pitch = distance between teeth centers around the pitch circle.
// ring_carrier = set ring/carrier ratio to this value in a ring driven system, must be between 1 and 2
// carrier_ring = set carrier/ring ratio to this value in a carrier driven system, must be between 1/2 and 1
// sun_carrier = set sun/carrier ratio to this value in a sun driven system, must be larger than 2
// carrier_sun = set carrier/sun ratio to this value in a carrier driven system, must be smaller than 1/2
// ring_sun = set ring/sun ratio to this value in a ring driven system, must have absolute value larger than 1
// sun_ring = set sun/ring ratio to this value in a sun driven system, must have absolute value smaller than 1
// ring_carrier = set ring/carrier transmission ratio to this value in a ring driven system, must be between 1 and 2
// carrier_ring = set carrier/ring transmission ratio to this value in a carrier driven system, must be between 1/2 and 1
// sun_carrier = set sun/carrier transmission ratio to this value in a sun driven system, must be larger than 2
// carrier_sun = set carrier/sun transmission ratio to this value in a carrier driven system, must be smaller than 1/2
// ring_sun = set ring/sun transmission ratio to this value in a ring driven system, must have absolute value smaller than 1
// sun_ring = set sun/ring transmission ratio to this value in a sun driven system, must have absolute value larger than 1
// helical = create gears with specified helical angle. Default: 0
// gear_spin = rotate the driven gear by this number of degrees. Default:0
// Example(2D,NoAxes,Anim,Frames=90,FrameMS=30,VPT=[-0.875705,-0.110537,-66.3877],VPR=[0,0,0],VPD=102,Med): In this example we request a ring/carrier ratio of 1.341 and the system produced has a ratio of 4/3. The sun is fixed, the input is carried by the ring, and the carrier, shown as the blue triangle, is the output, rotating approximately in accordance with the requested ratio.

View file

@ -1585,8 +1585,8 @@ function polygon_normal(poly) =
// b=30;
// ofs = 17;
// curve = [for(theta=[0:10:140]) [a * theta/360*2*PI - b*sin(theta), a-b*cos(theta)-20]];
// path = deduplicate(concat( reverse(offset(curve,r=ofs)),
// xflip(offset(curve,r=ofs)),
// path = deduplicate(concat( reverse(offset(curve,r=ofs,closed=false)),
// xflip(offset(curve,r=ofs,closed=false)),
// xflip(reverse(curve)),
// curve
// ));
@ -1612,8 +1612,8 @@ function polygon_normal(poly) =
// b=30*2/3;
// ofs = 17*2/3;
// curve = [for(theta=[0:10:140]) [a * theta/360*2*PI - b*sin(theta), a-b*cos(theta)]];
// path = deduplicate(concat( reverse(offset(curve,r=ofs)),
// xflip(offset(curve,r=ofs)),
// path = deduplicate(concat( reverse(offset(curve,r=ofs,closed=false)),
// xflip(offset(curve,r=ofs,closed=false)),
// xflip(reverse(curve)),
// curve
// ));
@ -1628,8 +1628,8 @@ function polygon_normal(poly) =
// b=30*2/3;
// ofs = 17*2/3;
// curve = [for(theta=[0:10:140]) [a * theta/360*2*PI - b*sin(theta), a-b*cos(theta)]];
// path = deduplicate(concat( reverse(offset(curve,r=ofs)),
// xflip(offset(curve,r=ofs)),
// path = deduplicate(concat( reverse(offset(curve,r=ofs,closed=false)),
// xflip(offset(curve,r=ofs,closed=false)),
// xflip(reverse(curve)),
// curve
// ));

View file

@ -1193,7 +1193,7 @@ module rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1
bounds = pointlist_bounds(rounded);
extrapt = is_pin ? [] : [rounded[0] - [0,extra]];
finalpath = is_pin ? rounded
: let(withclearance=offset(rounded, r=-clearance))
: let(withclearance=offset(rounded, r=-clearance, closed=false))
concat( [[withclearance[0].x,-extra]],
withclearance,
[[-withclearance[0].x,-extra]]);

View file

@ -437,7 +437,7 @@ function _partition_cutpath(l, h, cutsize, cutpath, gap) =
// Creates a mask that you can use to difference or intersect with an object to remove half of it,
// leaving behind a side designed to allow assembly of the sub-parts.
// Arguments:
// l = The length of the cut axis.
// l = The length of the cut axis.
// w = The width of the part to be masked, back from the cut plane.
// h = The height of the part to be masked.
// cutsize = The width of the cut pattern to be used.
@ -487,16 +487,15 @@ module partition_mask(l=100, w=100, h=100, cutsize=10, cutpath="jigsaw", gap=0,
// Topics: Partitions, Masking, Paths
// See Also: partition_mask(), partition()
// Usage:
// partition_cut_mask(l, w, h, [cutsize], [cutpath], [gap], [inverse], [$slop=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
// partition_cut_mask(l, [cutsize], [cutpath], [gap], [inverse], [$slop=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
// Description:
// Creates a mask that you can use to difference with an object to cut it into two sub-parts that can be assembled.
// The `$slop` value is important to get the proper fit and should probably be smaller than 0.2. The examples below
// use larger values to make the mask easier to see.
// Arguments:
// l = The length of the cut axis.
// w = The width of the part to be masked, back from the cut plane.
// h = The height of the part to be masked.
// cutsize = The width of the cut pattern to be used.
// cutsize = The width of the cut pattern to be used. Default: 10
// cutpath = The cutpath to use. Standard named paths are "flat", "sawtooth", "sinewave", "comb", "finger", "dovetail", "hammerhead", and "jigsaw". Alternatively, you can give a cutpath as a 2D path, where X is between 0 and 1, and Y is between -0.5 and 0.5. Default: "jigsaw"
// gap = Empty gaps between cutpath iterations. Default: 0
// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#subsection-spin). Default: `0`

View file

@ -814,8 +814,10 @@ function _point_dist(path,pathseg_unit,pathseg_len,pt) =
// Takes a 2D input path, polygon or region and returns a path offset by the specified amount. As with the built-in
// offset() module, you can use `r` to specify rounded offset and `delta` to specify offset with
// corners. If you used `delta` you can set `chamfer` to true to get chamfers.
// For paths and polygons positive offsets make the polygons larger. For paths,
// positive offsets shift the path to the left, relative to the direction of the path.
// 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
// 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.
// .
// 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
@ -863,7 +865,7 @@ function _point_dist(path,pathseg_unit,pathseg_len,pt) =
// r = offset radius. Distance to offset. Will round over corners.
// delta = offset distance. Distance to offset with pointed corners.
// chamfer = chamfer corners when you specify `delta`. Default: false
// closed = if true path is treate as a polygon. Default: False.
// closed = if true path is treated as a polygon. Default: True.
// check_valid = perform segment validity check. Default: True.
// quality = validity check quality parameter, a small integer. Default: 1.
// same_length = return a path with the same length as the input. Only compatible with `delta=`. Default: false
@ -902,7 +904,7 @@ function _point_dist(path,pathseg_unit,pathseg_len,pt) =
// Example(2D): Open path. The red path moves from left to right as shown by the arrow and the positive offset shifts to the left of the initial red path.
// sinpath = 2*[for(theta=[-180:5:180]) [theta/4,45*sin(theta)]];
// stroke(sinpath, width=2, color="red", endcap2="arrow2");
// stroke(offset(sinpath, r=17.5),width=2);
// stroke(offset(sinpath, r=17.5,closed=false),width=2);
// Example(2D,NoAxes): An open path in red with with its positive offset in yellow and its negative offset in blue.
// seg = [[0,0],[0,50]];
// stroke(seg,color="red",endcap2="arrow2");
@ -985,12 +987,13 @@ function _point_dist(path,pathseg_unit,pathseg_len,pt) =
function offset(
path, r=undef, delta=undef, chamfer=false,
closed=false, check_valid=true,
closed=true, check_valid=true,
quality=1, return_faces=false, firstface_index=0,
flip_faces=false, same_length=false
) =
assert(!(same_length && return_faces), "Cannot combine return_faces with same_length")
is_region(path)?
assert(closed, "cannot set closed=false for a region")
assert(!return_faces, "return_faces not supported for regions.")
let(
ofsregs = [for(R=region_parts(path))
@ -1035,7 +1038,7 @@ function offset(
cornercheck = [for(i=idx(goodsegs)) (!closed && (i==0 || i==len(goodsegs)-1))
|| is_def(sharpcorners[i])
|| approx(unit(deltas(select(goodsegs,i-1))[0]) * unit(deltas(goodsegs[i])[0]),-1)],
dummyA = assert(len(sharpcorners)==2 || all(cornercheck),"Two consecutive valid offset segments are parallel but do not meet at their ends, maybe because path contains very short segments that were mistakenly flagged as invalid; unable to compute offset"),
dummyA = assert(len(sharpcorners)==2 || all(cornercheck),"Two consecutive valid offset segments are parallel but do not meet at their ends, maybe because path contains very short segments that were mistakenly flagged as invalid; unable to compute offset. If you get this error from offset_sweep() try setting ofset=\"delta\""),
reversecheck =
!same_length
|| !(is_def(delta) && !chamfer) // Reversals only a problem in delta mode without chamfers

View file

@ -1406,7 +1406,7 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
// star = star(5, r=22, ir=13);
// rounded_star = round_corners(star, cut=flatten(repeat([.5,0],5)), $fn=24);
// offset_sweep(rounded_star, height=20, bottom=os_teardrop(r=4), top=os_chamfer(width=4),$fn=64);
// Example: We round a cube using the continous curvature rounding profile. But note that the corners are not smooth because the curved square collapses into a square with corners. When a collapse like this occurs, we cannot turn `check_valid` off. For a better result use `rounded_prism()` instead.
// Example: We round a cube using the continous curvature rounding profile. But note that the corners are not smooth because the curved square collapses into a square with corners. When a collapse like this occurs, we cannot turn `check_valid` off. For a better result use {{rounded_prism()}} instead.
// square = square(1);
// rsquare = round_corners(square, method="smooth", cut=0.1, k=0.7, $fn=36);
// end_spec = os_smooth(cut=0.1, k=0.7, steps=22);
@ -2619,8 +2619,8 @@ Access to the derivative smoothing parameter?
// When joining between planes this function produces similar results to {{rounded_prism()}}. This function works best when the prism
// cross section is a continuous shape with a high sampling rate and without sharp corners. If you have sharp corners you should consider
// giving them a small rounding first. When the prism cross section has concavities the fillet size will be limited by the curvature of those concavities.
// In contrast, {{rounded_prism()}} works best on a prism that has fewer points. A high sampling rate can lead to problems, and rounding
// over sharp corners leads to poor results.
// In contrast, {{rounded_prism()}} works best on a prism that has fewer points and does well with sharp corners, but may encounter problems
// with a high sampling rate.
// .
// You specify the prism by giving its cross section as a 2D path. The cross section will always be the orthogonal cross
// section of the prism. Depending on end conditions, the ends may not be perpendicular to the
@ -2707,7 +2707,7 @@ Access to the derivative smoothing parameter?
// For joins to convex objects you can choose a small value, but when joining to a concave object the overlap may need to be
// very large to ensure that the base of the joiner prism is well-behaved. In such cases you may need to use an intersection
// remove excess base.
// Figure(2D,Med,NoAxes): Uniform fillet method. This image shows how the fillet we construct a uniform fillet. The pictures shows the cross section that is perpendicular to the prism. The blue curve represents the base object surface. The vertical line is the side of the prism. To construct a fillet we travel along the surface of the base, following the curve, until we have moved the fillet length, `a`. This defines the point `u`. We then construct a tangent line to the base and find its intersection, `v`, with the prism. Note that if the base is steeply curved, this tangent may fail to intersect, and the algorithm will fail with an error because `v` does not exist. Finally we locate `w` to be distance `a` above the point where the prism intersects the base object. The fillet is defined by the `[u,v,w]` triple and is shown in red. Note that with this method, the fillet is always height `a` above the base, so it makes a uniform curve parallel to the base object. However, when the base curvature is more extreme, point `v` may end up above point `w`, resulting in an invalid configuration. It also happens that point `v`, while below `w`, is very close to `w`, so the resulting fillet has an abrupt angle near `w` instead of a smooth transition.
// Figure(2D,Med,NoAxes): Uniform fillet method. This image shows how we construct a uniform fillet. The pictures shows the cross section that is perpendicular to the prism. The blue curve represents the base object surface. The vertical line is the side of the prism. To construct a fillet we travel along the surface of the base, following the curve, until we have moved the fillet length, `a`. This defines the point `u`. We then construct a tangent line to the base and find its intersection, `v`, with the prism. Note that if the base is steeply curved, this tangent may fail to intersect, and the algorithm will fail with an error because `v` does not exist. Finally we locate `w` to be distance `a` above the point where the prism intersects the base object. The fillet is defined by the `[u,v,w]` triple and is shown in red. Note that with this method, the fillet is always height `a` above the base, so it makes a uniform curve parallel to the base object. However, when the base curvature is more extreme, point `v` may end up above point `w`, resulting in an invalid configuration. It also happens that point `v`, while below `w`, is very close to `w`, so the resulting fillet has an abrupt angle near `w` instead of a smooth transition.
// R=60;
// base = R*[cos(70),sin(70)];
// end = R*[cos(45),sin(45)];

View file

@ -590,13 +590,17 @@ function cuboid(
// Creates a rectangular prismoid shape with optional roundovers and chamfering.
// You can only round or chamfer the vertical(ish) edges. For those edges, you can
// specify rounding and/or chamferring per-edge, and for top and bottom separately.
// If you want to round the bottom or top edges see {{rounded_prism()}}.
// If you want to round the bottom or top edges see {{rounded_prism()}} or {{edge_profile()}}
// .
// Specification of the prismoid is similar to specification for {{trapezoid()}}. You can specify the dimensions of the
// bottom and top and its height to get a symmetric prismoid. You can use the shift argument to shift the top face around.
// You can also specify base angles either in the X direction, Y direction or both. In order to avoid overspecification,
// you may need to specify a parameter such as size2 as a list of two values, one of which is undef. For example,
// 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 edges also have anchors that point up. 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:
// size1 = [width, length] of the bottom end of the prism.
// size2 = [width, length] of the top end of the prism.
@ -827,6 +831,362 @@ function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
]
) reorient(anchor,spin,orient, vnf=vnf, extent=true, p=vnf);
// Function&Module: regular_prism()
// Synopsis: Creates a regular prism with roundovers and chamfering
// SynTags: Geom, VNF
// Topics: Textures, Rounding, Chamfers
// See Also: cyl(), rounded_prism(), texture(), linear_sweep()
// Usage: Normal prisms
// 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=, r1=|d1=|id1=|od1=|ir1=|or1=|side1=,r2=|d2=|id2=|od2=|ir2=|or2=|side2=, ...) [ATTACHMENTS];
// Usage: Chamferred end prisms
// regular_prism(n, h, r, chamfer=, [chamfang=], [from_end=], ...);
// regular_prism(n, h, r, chamfer1=, [chamfang1=], [from_end=], ...);
// regular_prism(n, h, r, chamfer2=, [chamfang2=], [from_end=], ...);
// regular_prism(n, h, r, chamfer1=, chamfer2=, [chamfang1=], [chamfang2=], [from_end=], ...);
// Usage: Rounded end prisms
// regular_prism(n, h, r, rounding=, ...);
// regular_prism(n, h, r, rounding1=, ...);
// regular_prism(n, h, r, rounding2=, ...);
// regular_prism(n, h, r, rounding1=, rounding2=, ...);
// Usage: Textured prisms
// regular_prism(n, h, r, texture=, [tex_size=]|[tex_reps=], [tex_depth=], [tex_rot=], [tex_samples=], [style=], [tex_inset=], ...);
// Usage: Called as a function to get a VNF
// vnf = rounded_prism(...);
// Description:
// Creates a prism whose ends are similar `n`-sided regular polygons, with optional rounding, chamfers or textures.
// You can specify the size of the ends using diameter or radius measured either inside or outside. Alternatively
// you can give the length of the side of the polygon. You can specify chamfers and roundings for the ends, but not
// the vertical edges. See {{rounded_prism()}} for prisms with rounded vertical edges. You can also specify texture for the side
// faces, but note that texture is not compatible with any roundings or chamfers.
// .
// 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.
// Additional 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.
// 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
// 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
// not split the edge/corner angle like the standard anchors.
// .
// 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,
// anchors are located on the true object instead of the ideal cylinder and you can anchor to the edges and faces.
// Named Anchors:
// "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
// "topedge0", "topedge1", etc = Center of each top edge, pointing in direction of associated side face, spin up
// "botedge0", "botedge1", etc = Center of each bottom edge, pointing in direction of associated side face, spin up
// "topcorner0", "topcorner1", 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
// Arguments:
// l / h / length / height = Length of prism
// r = Outer radius of prism.
// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
// ---
// r1/or1 = Outer radius of the bottom of prism
// r2/or2 = Outer radius of the top end of prism
// d = Outer Diameter of prism
// d1 / od1 = Outer diameter of bottom of prism
// d2 / od2 = Outer diameter of top end of prism
// ir = Inner radius of prism
// ir1 = Inner radius of bottom of prism
// ir2 = Inner radius of top of prism
// id = Inner diameter of prism
// id1 = Inner diameter of bottom of prism
// id2 = Inner diameter of top of prism
// side = Side length of prism faces
// side1 = Side length of prism faces at the bottom
// side2 = Side length of prism faces at the top
// shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
// chamfer = The size of the chamfers on the ends of the prism. (Also see: `from_end=`) Default: none.
// chamfer1 = The size of the chamfer on the bottom end of the prism. (Also see: `from_end1=`) Default: none.
// chamfer2 = The size of the chamfer on the top end of the prism. (Also see: `from_end2=`) Default: none.
// chamfang = The angle in degrees of the chamfers away from the ends of the prismr. Default: Chamfer angle is halfway between the endcap and side face.
// chamfang1 = The angle in degrees of the bottom chamfer away from the bottom end of the prism. Default: Chamfer angle is halfway between the endcap and side face.
// chamfang2 = The angle in degrees of the top chamfer away from the top end of the prism. Default: Chamfer angle is halfway between the endcap and side face.
// from_end = If true, chamfer is measured along the side face from the ends of the prism, instead of inset from the edge. Default: `false`.
// from_end1 = If true, chamfer on the bottom end of the prism is measured along the side face from the end of the prism, instead of inset from the edge. Default: `false`.
// from_end2 = If true, chamfer on the top end of the prism is measured along the side face from the end of the prism, instead of inset from the edge. Default: `false`.
// rounding = The radius of the rounding on the ends of the prism. Default: none.
// rounding1 = The radius of the rounding on the bottom end of the prism.
// rounding2 = The radius of the rounding on the top end of the prism.
// realign = If true, rotate the prism by half the angle of one face so that a face points in the X+ direction. Default: false
// teardrop = If given as a number, rounding around the bottom edge of the prism won't exceed this many degrees from vertical. If true, the limit angle is 45 degrees. Default: `false`
// texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to vertical surfaces. See {{texture()}} for what named textures are supported.
// tex_size = An optional 2D target size for the textures. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]`
// tex_reps = If given instead of tex_size, a 2-vector giving the number of texture tile repetitions in the horizontal and vertical directions.
// tex_inset = If numeric, lowers the texture into the surface by the specified proportion, e.g. 0.5 would lower it half way into the surface. If `true`, insets by exactly its full depth. Default: `false`
// tex_rot = Rotate texture by specified angle, which must be a multiple of 90 degrees. Default: 0
// tex_depth = Specify texture depth; if negative, invert the texture. Default: 1.
// tex_samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8
// style = {{vnf_vertex_array()}} style used to triangulate heightfield textures. Default: "min_edge"
// 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`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
// Example: Simple prism
// regular_prism(5,r=10,h=25);
// Example: With end rounding
// regular_prism(5,r=10,h=25,rounding=3,$fn=32);
// Example: By side length at bottom, inner radius at top, shallow chamfer
// regular_prism(7, side1=10, ir2=7, height=20,chamfer2=2,chamfang2=20);
// Example: With shift
// regular_prism(4, d=12, h=10, shift=[12,7]);
// Example: Attaching child to face
// regular_prism(5, d1=15, d2=10, h=20)
// recolor("lightblue")
// attach("face1",BOT) regular_prism(n=4,r1=3,r2=1,h=3);
// Example: Attaching child to edge
// regular_prism(5, d1=15, d2=10, h=20)
// recolor("lightblue")
// attach("edge2",RIGHT) cuboid([4,4,20]);
// Example: Placing child on top along an edge of a regular prism is possible with the top_edge anchors, but you cannot use {{align()}} or {{attach()}}, so you must manually anchor and spin the child by half of the polygon angle (180/n) to get to face0 and then 360/n more for each subsequent face. If you set `realign=true` then you don't need the initial angle for face0.
// regular_prism(5, d1=25, d2=20, h=15, realign=false) color("lightblue"){
// position("top_edge1") prismoid([5,5],[2,2],h=3,spin=-360/5*1.5,anchor=RIGHT+BOT);
// position("top_edge3") prismoid([5,5],[2,2],h=3,spin=-360/5*3.5,anchor=RIGHT+BOT);
// }
// Example: Textured prism
// regular_prism(5, side=25, h=50, texture="diamonds", tex_size=[5,5], style="concave");
module regular_prism(n,
h, r, center,
l, length, height,
r1,r2,ir,ir1,ir2,or,or1,or2,side,side1,side2,
d, d1, d2,id,id1,id2,od,od1,od2,
chamfer, chamfer1, chamfer2,
chamfang, chamfang1, chamfang2,
rounding, rounding1, rounding2,
realign=false, shift=[0,0],
teardrop=false,
from_end, from_end1, from_end2,
texture, tex_size=[5,5], tex_reps,
tex_inset=false, tex_rot=0,
tex_depth, tex_samples,
tex_taper, style,
anchor, spin=0, orient=UP
)
{
vnf_anchors_ovr = regular_prism(n=n,h=h,r=r,center=center, l=l,length=length,height=height,
r1=r1,r2=r2,ir=ir,ir1=ir1,ir2=ir2,or=or,or1=or1,or2=or2,side=side,side1=side1,side2=side2,
d=d,d1=d1,d2=d2,id=id,id1=id1,id2=id2,od=od,od1=od1,od2=od2,
chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
chamfang=chamfang,chamfang1=chamfang1,chamfang2=chamfang2,
rounding=rounding,rounding1=rounding1, rounding2=rounding2,
realign=realign, shift=shift,
teardrop=teardrop,
from_end=from_end, from_end1=from_end1, from_end2=from_end2,
texture=texture, tex_size=tex_size, tex_reps=tex_reps,
tex_inset=tex_inset, tex_rot=tex_rot,
tex_depth=tex_depth, tex_samples=tex_samples,
tex_taper=tex_taper, style=style,
_return_anchors=true);
attachable(anchor=anchor, orient=orient, spin=spin, vnf=vnf_anchors_ovr[0], anchors=vnf_anchors_ovr[1],override=vnf_anchors_ovr[2]){
vnf_polyhedron(vnf_anchors_ovr[0],convexity=is_def(texture)?10:2);
children();
}
}
function regular_prism(n,
h, r, center,
l, length, height,
r1,r2,ir,ir1,ir2,or,or1,or2,side,side1,side2,
d, d1, d2,id,id1,id2,od,od1,od2,
chamfer, chamfer1, chamfer2,
chamfang, chamfang1, chamfang2,
rounding, rounding1, rounding2,
circum=false, realign=false, shift=[0,0],
teardrop=false,
from_end, from_end1, from_end2,
texture, tex_size=[5,5], tex_reps,
tex_inset=false, tex_rot=0,
tex_depth, tex_samples, length, height,
tex_taper, style,
anchor, spin=0, orient=UP,_return_anchors=false
) =
assert(is_integer(n) && n>2, "n must be an integer 3 or greater")
let(
style = default(style,"min_edge"),
tex_depth = default(tex_depth,1),
height = one_defined([l, h, length, height],"l,h,length,height",dflt=1),
sc = 1/cos(180/n),
ir1 = u_mul(default(ir1,ir), sc),
ir2 = u_mul(default(ir2,ir), sc),
id1 = u_mul(default(id1,id), sc),
id2 = u_mul(default(id2,id), sc),
od1 = default(od1,od),
od2 = default(od2,od),
or1 = default(or1,or),
or2 = default(or2,or),
d1 = default(d1,d),
d2 = default(d2,d),
side = is_finite(side)? side/2/sin(180/n) : undef,
side1 = is_finite(side1)? side1/2/sin(180/n) : side,
side2 = is_finite(side2)? side2/2/sin(180/n) : side,
r1 = get_radius(r1=ir1,r2=or1,r=default(r1,r),d=d1,d1=id1,d2=od1,dflt=side1),
r2 = get_radius(r1=ir2,r2=or2,r=default(r2,r),d=d2,d1=id2,d2=od2,dflt=side2),
anchor = get_anchor(anchor,center,BOT,CENTER)
)
assert(num_defined([side,od,id,or,ir])<=1, "Can only define one of side, id, od, ir, and or")
assert(is_finite(r1), "Must specify finite number for prism bottom radius / diameter / side length")
assert(is_finite(r2), "Must specify finite number for prism top radius / diameter / side length")
assert(is_finite(height), "l/h/length/height must be a finite number.")
assert(is_vector(shift,2), "shift must be a 2D vector.")
let(
vnf = any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2])
? assert(is_undef(texture), "Cannot combine roundings or chamfers with texturing")
let(
vang = atan2(r1-r2,height),
_chamf1 = first_defined([chamfer1, if (is_undef(rounding1)) chamfer, 0]),
_chamf2 = first_defined([chamfer2, if (is_undef(rounding2)) chamfer, 0]),
_fromend1 = first_defined([from_end1, from_end, false]),
_fromend2 = first_defined([from_end2, from_end, false]),
chang1 = first_defined([chamfang1, chamfang, 45+sign(_chamf1)*vang/2]),
chang2 = first_defined([chamfang2, chamfang, 45-sign(_chamf2)*vang/2]),
round1 = first_defined([rounding1, if (is_undef(chamfer1)) rounding, 0]),
round2 = first_defined([rounding2, if (is_undef(chamfer2)) rounding, 0]),
checks1 =
assert(is_finite(_chamf1), "chamfer1 must be a finite number if given.")
assert(is_finite(_chamf2), "chamfer2 must be a finite number if given.")
assert(is_finite(chang1) && chang1>0, "chamfang1 must be a positive number if given.")
assert(is_finite(chang2) && chang2>0, "chamfang2 must be a positive number if given.")
assert(chang1<90+sign(_chamf1)*vang, "chamfang1 must be smaller than the cone face angle")
assert(chang2<90-sign(_chamf2)*vang, "chamfang2 must be smaller than the cone face angle")
assert(num_defined([chamfer1,rounding1])<2, "cannot define both chamfer1 and rounding1")
assert(num_defined([chamfer2,rounding2])<2, "cannot define both chamfer2 and rounding2")
assert(num_defined([chamfer,rounding])<2, "cannot define both chamfer and rounding")
undef,
chamf1r = !_chamf1? 0
: !_fromend1? _chamf1
: law_of_sines(a=_chamf1, A=chang1, B=180-chang1-(90-sign(_chamf2)*vang)),
chamf2r = !_chamf2? 0
: !_fromend2? _chamf2
: law_of_sines(a=_chamf2, A=chang2, B=180-chang2-(90+sign(_chamf2)*vang)),
chamf1l = !_chamf1? 0
: _fromend1? abs(_chamf1)
: abs(law_of_sines(a=_chamf1, A=180-chang1-(90-sign(_chamf1)*vang), B=chang1)),
chamf2l = !_chamf2? 0
: _fromend2? abs(_chamf2)
: abs(law_of_sines(a=_chamf2, A=180-chang2-(90+sign(_chamf2)*vang), B=chang2)),
facelen = adj_ang_to_hyp(height, abs(vang)),
roundlen1 = round1 >= 0 ? round1/tan(45-vang/2)
: round1/tan(45+vang/2),
roundlen2 = round2 >=0 ? round2/tan(45+vang/2)
: round2/tan(45-vang/2),
dy1 = abs(_chamf1 ? chamf1l : round1 ? roundlen1 : 0),
dy2 = abs(_chamf2 ? chamf2l : round2 ? roundlen2 : 0),
td_ang = teardrop == true? 45 :
teardrop == false? 90 :
assert(is_finite(teardrop))
assert(teardrop>=0 && teardrop<=90)
teardrop,
checks2 =
assert(is_finite(round1), "rounding1 must be a number if given.")
assert(is_finite(round2), "rounding2 must be a number if given.")
assert(chamf1r <= r1, "chamfer1 is larger than the r1 radius of the cylinder.")
assert(chamf2r <= r2, "chamfer2 is larger than the r2 radius of the cylinder.")
assert(roundlen1 <= r1, "size of rounding1 is larger than the r1 radius of the cylinder.")
assert(roundlen2 <= r2, "size of rounding2 is larger than the r2 radius of the cylinder.")
assert(dy1+dy2 <= facelen, "Chamfers/roundings don't fit on the cylinder/cone. They exceed the length of the cylinder/cone face.")
undef,
path = [
[0,-height/2],
if (!approx(chamf1r,0))
each [
[r1, -height/2] + polar_to_xy(chamf1r,180),
[r1, -height/2] + polar_to_xy(chamf1l,90+vang),
]
else if (!approx(round1,0) && td_ang < 90)
each _teardrop_corner(r=round1, corner=[[max(0,r1-2*roundlen1),-height/2],[r1,-height/2],[r2,height/2]], ang=td_ang)
else if (!approx(round1,0) && td_ang >= 90)
each arc(r=abs(round1), corner=[[max(0,r1-2*roundlen1),-height/2],[r1,-height/2],[r2,height/2]])
else [r1,-height/2],
if (is_finite(chamf2r) && !approx(chamf2r,0))
each [
[r2, height/2] + polar_to_xy(chamf2l,270+vang),
[r2, height/2] + polar_to_xy(chamf2r,180),
]
else if (is_finite(round2) && !approx(round2,0))
each arc(r=abs(round2), corner=[[r1,-height/2],[r2,height/2],[max(0,r2-2*roundlen2),height/2]])
else [r2,height/2],
[0,height/2],
]
)
rotate_sweep(path,closed=false,$fn=n)
: is_undef(texture) ? cylinder(h=height, r1=r1, r2=r2, center=true, $fn=n)
: linear_sweep(regular_ngon(n=n,r=r1),scale=r2/r1,height=height,center=true,
texture=texture, tex_reps=tex_reps, tex_size=tex_size,
tex_inset=tex_inset, tex_rot=tex_rot,
tex_depth=tex_depth, tex_samples=tex_samples,
style=style),
skmat = down(height/2) *
skew(sxz=shift.x/height, syz=shift.y/height) *
up(height/2) *
zrot(realign? 180/n : 0),
ovnf = apply(skmat, vnf),
edge_face = [ [r2-r1,0,height],[(r2-r1)/sc,0,height]], // regular edge, then face edge, in xz plane
names = ["edge","face"],
anchors = approx(shift,[0,0]) ?
[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 = [
for(i=[0:n-1])
let(
M1 = skmat*zrot(-i*360/n),
M2 = skmat*zrot(-(i+1)*360/n),
edge1 = apply(M1,[[r2,0,height/2], [r1,0,-height/2]]),
edge2 = apply(M2,[[r2,0,height/2], [r1,0,-height/2]]),
face_edge = (edge1+edge2)/2,
facenormal = unit(cross(edge1[0]-edge1[1], edge2[1]-edge1[0]))
)
[facenormal,face_edge[0]-face_edge[1],edge1[0]-edge1[1]] // [normal to face, edge through face center, actual edge]
]
)
[for(i=[0:n-1])
let(
Mface = skmat*zrot(-(i+1/2)*360/n),
faceedge = faces[i][1],
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, UP),
edgenormal = unit(vector_bisect(facenormal,select(faces,i-1)[0])),
Medge = skmat*zrot(-i*360/n),
edge = faces[i][2],
edgespin = _compute_spin(edgenormal, edge)
)
each [
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("top_edge",i), apply(Mface,[r2/sc,0,height/2]), facenormal, facespin),
named_anchor(str("top_corner",i), apply(Medge,[r2,0,height/2]), edgenormal, edgespin),
named_anchor(str("bot_edge",i), apply(Mface,[r1/sc,0,-height/2]), facenormal, facespin),
named_anchor(str("bot_corner",i), apply(Medge,[r1,0,-height/2]), edgenormal, edgespin)
]
],
override = approx(shift,[0,0]) ? undef : [[UP, [point3d(shift,height/2), UP]]],
final_vnf = reorient(anchor,spin,orient, vnf=ovnf, p=ovnf,anchors=anchors, override=override)
)
_return_anchors ? [final_vnf,anchors,override]
: final_vnf;
// Module: rect_tube()
// Synopsis: Creates a rectangular tube.
@ -1115,6 +1475,9 @@ function rect_tube(
// Description:
// When called as a module, creates a 3D triangular wedge with the hypotenuse in the X+Z+ quadrant.
// When called as a function, creates a VNF for a 3D triangular wedge with the hypotenuse in the X+Z+ quadrant.
// The anchors for the wedge are the anchors of the wedge's bounding box. The named enchors listed below
// give the sloped face and edges, and those edge anchors have spin oriented with positive Z value in the
// direction of the sloped edge.
//
// Arguments:
// size = [width, thickness, height]
@ -1147,10 +1510,15 @@ module wedge(size=[1, 1, 1], center, anchor, spin=0, orient=UP)
size = scalar_vec3(size);
anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]);
vnf = wedge(size, anchor="origin");
spindir = unit([0,-size.y,size.z]);
hypot_dir = unit([0,size.z,size.y],UP);
left_dir = unit(hypot_dir+LEFT);
right_dir = unit(hypot_dir+RIGHT);
hedge_spin=vector_angle(spindir,rot(from=UP,to=left_dir, p=BACK));
anchors = [
named_anchor("hypot", CTR, unit([0,size.z,size.y],UP)),
named_anchor("hypot_left", [-size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+LEFT)),
named_anchor("hypot_right", [size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+RIGHT)),
named_anchor("hypot", CTR, hypot_dir, 180),
named_anchor("hypot_left", [-size.x/2,0,0], left_dir,-hedge_spin),
named_anchor("hypot_right", [size.x/2,0,0], right_dir,hedge_spin),
];
attachable(anchor,spin,orient, size=size, anchors=anchors) {
if (size.z > 0) {
@ -1174,10 +1542,15 @@ function wedge(size=[1,1,1], center, anchor, spin=0, orient=UP) =
[1,4,2], [2,4,5], [2,5,3], [0,2,3],
],
vnf = [scale(size/2,p=pts), faces],
spindir = unit([0,-size.y,size.z]),
hypot_dir = unit([0,size.z,size.y],UP),
left_dir = unit(hypot_dir+LEFT),
right_dir = unit(hypot_dir+RIGHT),
hedge_spin=vector_angle(spindir,rot(from=UP,to=left_dir, p=BACK)),
anchors = [
named_anchor("hypot", CTR, unit([0,size.z,size.y],UP)),
named_anchor("hypot_left", [-size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+LEFT)),
named_anchor("hypot_right", [size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+RIGHT)),
named_anchor("hypot", CTR, hypot_dir, 180),
named_anchor("hypot_left", [-size.x/2,0,0], left_dir,-hedge_spin),
named_anchor("hypot_right", [size.x/2,0,0], right_dir,hedge_spin),
]
)
reorient(anchor,spin,orient, size=size, anchors=anchors, p=vnf);
@ -1276,7 +1649,7 @@ function cylinder(h, r1, r2, center, r, d, d1, d2, anchor, spin=0, orient=UP) =
// Synopsis: Creates an attachable cylinder with roundovers and chamfering.
// SynTags: Geom, VNF
// Topics: Cylinders, Textures, Rounding, Chamfers
// See Also: texture(), rotate_sweep(), cylinder()
// See Also: regular_prism(), texture(), rotate_sweep(), cylinder()
// Usage: Normal Cylinders
// cyl(l|h|length|height, r, [center], [circum=], [realign=]) [ATTACHMENTS];
// cyl(l|h|length|height, d=, ...) [ATTACHMENTS];
@ -1300,7 +1673,7 @@ function cylinder(h, r1, r2, center, r, d, d1, d2, anchor, spin=0, orient=UP) =
// cyl(l|h|length|height, r1=, r2=, texture=, [tex_size=]|[tex_reps=], [tex_depth=], [tex_rot=], [tex_samples=], [style=], [tex_taper=], [tex_inset=], ...);
// cyl(l|h|length|height, d1=, d2=, texture=, [tex_size=]|[tex_reps=], [tex_depth=], [tex_rot=], [tex_samples=], [style=], [tex_taper=], [tex_inset=], ...);
//
// Usage: Caled as a function to get a VNF
// Usage: Called as a function to get a VNF
// vnf = cyl(...);
//
// Description:
@ -1310,6 +1683,10 @@ function cylinder(h, r1, r2, center, r, d, d1, d2, anchor, spin=0, orient=UP) =
// the cylinder or cone's sloped side. The more specific parameters like chamfer1 or rounding2 override the more
// general ones like chamfer or rounding, so if you specify `rounding=3, chamfer2=3` you will get a chamfer at the top and
// rounding at the bottom.
// .
// When creating a textured cylinder, the number of facets is determined by the sampling of the texture. Any `$fn`, `$fa` or `$fs` values in
// effect are ignored. To create a textured prism with a specified number of flat facets use {{regular_prism()}}. Anchors for cylinders
// appear on the ideal cylinder, not on actual discretized shape the module produces. For anchors on the shape surface, use {{regular_prism()}}.
// Figure(2D,Big,NoAxes,VPR = [0, 0, 0], VPT = [0,0,0], VPD = 82): Chamfers on cones can be tricky. This figure shows chamfers of the same size and same angle, A=30 degrees. Note that the angle is measured on the inside, and produces a quite different looking chamfer at the top and bottom of the cone. Straight black arrows mark the size of the chamfers, which may not even appear the same size visually. When you do not give an angle, the triangle that is cut off will be isoceles, like the triangle at the top, with two equal angles.
// color("lightgray")
// projection()
@ -1520,13 +1897,13 @@ function cyl(
) =
assert(num_defined([style,tex_style])<2, "In cyl() the 'tex_style' parameters has been replaced by 'style'. You cannot give both.")
assert(num_defined([tex_reps,tex_counts])<2, "In cyl() the 'tex_counts' parameters has been replaced by 'tex_reps'. You cannot give both.")
assert(num_defined([tex_scale,tex_depth])<2, "In linear_sweep() the 'tex_scale' parameter has been replaced by 'tex_depth'. You cannot give both.")
assert(num_defined([tex_scale,tex_depth])<2, "In cyl() the 'tex_scale' parameter has been replaced by 'tex_depth'. You cannot give both.")
let(
style = is_def(tex_style)? echo("In cyl() the 'tex_style' parameter is deprecated and has been replaced by 'style'")tex_style
: default(style,"min_edge"),
tex_reps = is_def(tex_counts)? echo("In cyl() the 'tex_counts' parameter is deprecated and has been replaced by 'tex_reps'")tex_counts
: tex_reps,
tex_depth = is_def(tex_scale)? echo("In rotate_sweep() the 'tex_scale' parameter is deprecated and has been replaced by 'tex_depth'")tex_scale
tex_depth = is_def(tex_scale)? echo("In cyl() the 'tex_scale' parameter is deprecated and has been replaced by 'tex_depth'")tex_scale
: default(tex_depth,1),
l = one_defined([l, h, length, height],"l,h,length,height",dflt=1),
_r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1),
@ -1580,8 +1957,6 @@ function cyl(
: abs(law_of_sines(a=_chamf2, A=180-chang2-(90+sign(_chamf2)*vang), B=chang2)),
facelen = adj_ang_to_hyp(l, abs(vang)),
cp1 = [r1,-l/2],
cp2 = [r2,+l/2],
roundlen1 = round1 >= 0 ? round1/tan(45-vang/2)
: round1/tan(45+vang/2),
roundlen2 = round2 >=0 ? round2/tan(45+vang/2)
@ -3738,3 +4113,5 @@ module ruler(length=100, width, thickness=1, depth=3, labels=false, pipscale=1/3
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -1055,7 +1055,7 @@ function _slice_3dpolygons(polys, dir, cuts) =
// Arguments:
// vnf = A VNF structure, or list of VNF structures.
// convexity = Max number of times a line could intersect a wall of the shape.
// cp = Centerpoint for determining intersection 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 intersection anchors or centering the shape. Determines the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid"
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `"origin"`
// 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`
@ -2331,5 +2331,29 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false, opacity=0.
}
function _vnf_find_edge_faces(vnf,edge) =
let(
faces = vnf[1],
goodind = [for(i=idx(faces))
let(result=search(edge,faces[i]))
if (result*0==[0,0] &&
(abs(result[0]-result[1])==1
|| (min(result)==0 && max(result)==len(faces[i])-1)))
i
]
)
goodind;
function _vnf_find_corner_faces(vnf,corner) =
let(
faces = vnf[1]
)
[for(i=idx(faces))
let(result=search([corner],faces[i])[0])
if (result!=[])
i];
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap