mirror of
https://github.com/revarbat/BOSL2.git
synced 2025-08-11 21:04:26 +02:00
Merge branch 'master' into issue1692
This commit is contained in:
@@ -15,13 +15,13 @@ Requires OpenSCAD 2021.01 or later.
|
||||
|
||||
The BOSL2 library is an enormous library that provides many different kinds of capabilities to simplify the development of models in OpenSCAD, and to make things possible that are difficult in native OpenSCAD. Some of the things BOSL2 provides are:
|
||||
|
||||
* **Attachments.** Unless you make models containing just one object the attachments features can revolutionize your modeling. They let you position components of a model relative to other components so you don't have to keep track of the positions and orientations of parts of the model. You can instead place an something on the TOP of something else, perhaps aligned to the RIGHT. For a full introduction to attachments, consult the [Attachments Tutorial.](https://github.com/BelfrySCAD/BOSL2/wiki/Tutorial-Attachments)
|
||||
* **Rounding and filleting.** Rounding and filleting is hard in OpenSCAD. The library provides modules like [cuboid()](https://github.com/BelfrySCAD/BOSL2/wiki/shapes3d.scad#module-cuboid) to make a cube with any of the edges rounded, [offset_sweep()](https://github.com/BelfrySCAD/BOSL2/wiki/rounding.scad#functionmodule-offset_sweep) to round the ends of a linear extrusion, and [prism_connector()](https://github.com/BelfrySCAD/BOSL2/wiki/rounding.scad#module-prism_connector) which works with the attachments feature to create filleted prisms between a variety of objects, or even rounded holes through a single object. You can also use [edge_profile()](https://github.com/BelfrySCAD/BOSL2/wiki/attachments.scad#module-edge_profile) to apply a variety of different mask profiles to chosen edges of a cubic shape, or you can directly subtract 3d mask shapes from an edge of objects that are not cubes.
|
||||
* **Attachments.** Unless you make models containing just one object, the attachments features can revolutionize your modeling. They let you position components of a model relative to other components so you don't have to keep track of the positions and orientations of parts of the model. You can instead place something on the TOP of something else, perhaps aligned to the RIGHT. For a full introduction to attachments, consult the [Attachments Tutorial.](https://github.com/BelfrySCAD/BOSL2/wiki/Tutorial-Attachments)
|
||||
* **Rounding and filleting.** Rounding and filleting is hard in OpenSCAD. The library provides modules like [cuboid()](https://github.com/BelfrySCAD/BOSL2/wiki/shapes3d.scad#module-cuboid) to make a cube with any of the edges rounded, [offset_sweep()](https://github.com/BelfrySCAD/BOSL2/wiki/rounding.scad#functionmodule-offset_sweep) to round the ends of a linear extrusion, and [prism_connector()](https://github.com/BelfrySCAD/BOSL2/wiki/rounding.scad#module-prism_connector) which works with the attachments feature to create filleted prisms between a variety of objects, or holes through a single object with rounded edges at the ends. You can also use [edge_profile()](https://github.com/BelfrySCAD/BOSL2/wiki/attachments.scad#module-edge_profile) to apply a variety of different mask profiles to chosen edges of a cubic shape, or you can directly subtract 3d mask shapes from an edge of objects that are not cubes.
|
||||
* **Complex object support.** The [path_sweep()](https://github.com/BelfrySCAD/BOSL2/wiki/skin.scad#functionmodule-path_sweep) function/module takes a 2d polygon moves it through space along a path and sweeps out a 3d shape as it moves. You can link together a series of arbitrary polygons with [skin()](https://github.com/BelfrySCAD/BOSL2/wiki/skin.scad#functionmodule-skin) or [vnf_vertex_array().](https://github.com/BelfrySCAD/BOSL2/wiki/vnf.scad#functionmodule-vnf_vertex_array) Support for [beziers](https://github.com/BelfrySCAD/BOSL2/wiki/beziers.scad) and [NURBS](https://github.com/BelfrySCAD/BOSL2/wiki/nurbs.scad) can help you construct the building blocks you need. [Metaballs](https://github.com/BelfrySCAD/BOSL2/wiki/isosurface.scad#functionmodule-metaballs) can create organic surfaces that blend shapes together.
|
||||
* **Building Blocks.** OpenSCAD provides cubes, cones and spheres. The BOSL2 library extends this to provide different kinds of prisms, tubes, and other abstract geometrical building blocks. In many cases the BOSL2 objects include options to round their edges. Basic objects have extensions like the ability to specify the **inner** radius of a circle to create holes with a guaranteed minimum size.
|
||||
* **Texturing.** Many kinds of objects can be created with [textures](https://github.com/BelfrySCAD/BOSL2/wiki/skin.scad#section-texturing) applied. This can create knurling, but it can do much more than that. A texture can be any repeating pattern, and applying a texture can actually replace the base object with something different based on repeating copies of the texture element. A texture can also be an image; using texturing you can emboss an arbitrary image onto your model.
|
||||
* **Parts library.** The parts library includes many useful specific functional parts including [gears](https://github.com/BelfrySCAD/BOSL2/wiki/gears.scad), generic [threading](https://github.com/BelfrySCAD/BOSL2/wiki/threading.scad#section-generic-threading), and specific threading to match plastic [bottles](https://github.com/BelfrySCAD/BOSL2/wiki/bottlecaps.scad), [pipe fittings](https://github.com/BelfrySCAD/BOSL2/wiki/threading.scad#module-npt_threaded_rod), or standard [screws.](https://github.com/BelfrySCAD/BOSL2/wiki/screws.scad) Also included are [clips](https://github.com/BelfrySCAD/BOSL2/wiki/joiners.scad#section-tension-clips), [hinges](https://github.com/BelfrySCAD/BOSL2/wiki/hinges.scad), and [dovetail joints.](https://github.com/BelfrySCAD/BOSL2/wiki/joiners.scad#section-dovetails)
|
||||
* **Shorthands.** The shorthands make your code a little shorter, and more importantly, they can make it significantly easier to read. Compare `up(x)` to `translate([0,0,x])`. The shorthands include operations for creating [copies of objects](https://github.com/BelfrySCAD/BOSL2/wiki/distributors.scad) and for applying [transformations](https://github.com/BelfrySCAD/BOSL2/wiki/transforms.scad) to objects, including [rot()](https://github.com/BelfrySCAD/BOSL2/wiki/transforms.scad#functionmodule-rot) which extends rotate in some useful ways that are not easy to do directly.
|
||||
* **Shorthands.** The shorthands make your code a little shorter, and more importantly, they can make it significantly easier to read. Compare `up(z)` to `translate([0,0,z])`. The shorthands include operations for creating [copies of objects](https://github.com/BelfrySCAD/BOSL2/wiki/distributors.scad) and for applying [transformations](https://github.com/BelfrySCAD/BOSL2/wiki/transforms.scad) to objects, including [rot()](https://github.com/BelfrySCAD/BOSL2/wiki/transforms.scad#functionmodule-rot) which extends rotate in some useful ways that are not easy to do directly.
|
||||
* **Geometrical operations on data.** In OpenSCAD, geometrical operations happen on geometry, and information can never be extracted from geometry. The BOLS2 library provides operations on 2d point lists (called "paths" or "regions") to make [rounded paths](https://github.com/BelfrySCAD/BOSL2/wiki/rounding.scad#function-round_corners) from ones with corners or do operations like [intersection](https://github.com/BelfrySCAD/BOSL2/wiki/regions.scad#functionmodule-intersection) and [offset](https://github.com/BelfrySCAD/BOSL2/wiki/regions.scad#function-offset). It can also do some limited operations on three dimensional data.
|
||||
* **Programming aids.** The library provides basic [mathematical operations](https://github.com/BelfrySCAD/BOSL2/wiki/math.scad) including solutions to [linear systems of equations](https://github.com/BelfrySCAD/BOSL2/wiki/linalg.scad#function-linear_solve) and [generic](https://github.com/BelfrySCAD/BOSL2/wiki/math.scad#function-root_find) and [polynomial](https://github.com/BelfrySCAD/BOSL2/wiki/math.scad#function-real_roots) numerical root finding. It provides [geometrical operations](https://github.com/BelfrySCAD/BOSL2/wiki/geometry.scad) like [line intersection](https://github.com/BelfrySCAD/BOSL2/wiki/geometry.scad#function-line_intersection) or [circle intersection](https://github.com/BelfrySCAD/BOSL2/wiki/geometry.scad#function-circle_circle_intersection), [coordinate transformations](https://github.com/BelfrySCAD/BOSL2/wiki/coords.scad), [string manipulation,](https://github.com/BelfrySCAD/BOSL2/wiki/strings.scad) and [list processing](https://github.com/BelfrySCAD/BOSL2/wiki/lists.scad).
|
||||
|
||||
|
163
attachments.scad
163
attachments.scad
@@ -4079,81 +4079,94 @@ function _find_anchor(anchor, geom)=
|
||||
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],
|
||||
// We want to catch the case where the points lie on an edge. The complication is that the edge
|
||||
// may appear twice WITH DIFFERENT VERTEX INDICES if repeated points appear in the vnf.
|
||||
edges_faces = len(idxs)==2 ? // Simple case, no repeated points, [idxs] gives the edge
|
||||
approx(vnf[0][idxs[0]],vnf[0][idxs[1]]) ? [] // Are edge points identical?
|
||||
: let( facelist = _vnf_find_edge_faces(vnf,idxs))
|
||||
len(facelist)==2 ? [[idxs], facelist] : []
|
||||
: len(idxs)!=4 ? [] // If we don't have four points it's not an edge pair
|
||||
: let(
|
||||
pts = select(vnf[0],idxs),
|
||||
matchind = [for(i=[1:3]) if (approx(pts[i],pts[0])) i] // indices where actual vertex point is the same as point zero
|
||||
)
|
||||
len(matchind)!=1 ? []
|
||||
: let( // After this runs we have two edges as index pairs, and their associated faces as index values
|
||||
match1 = select(idxs,[0,matchind[0]]),
|
||||
match2 = list_remove(idxs,[0,matchind[0]]),
|
||||
facelists = [for(i=[0:1], j=[0:1])
|
||||
let(
|
||||
ed = [match1[i],match2[j]],
|
||||
fl = _vnf_find_edge_faces(vnf,ed)
|
||||
)
|
||||
if (fl!=[]) [ed,fl]
|
||||
],
|
||||
final = [column(facelists,0), flatten(column(facelists,1))]
|
||||
)
|
||||
assert(len(final[1])==2, "invalid!")
|
||||
final,
|
||||
dir = len(idxs)>2 && edges_faces==[] ? [anchor,oang]
|
||||
: edges_faces!=[] ?
|
||||
let(
|
||||
faces = edges_faces[1],
|
||||
edge = select(vnf[0],edges_faces[0][0]),
|
||||
facenormals = [for(face=faces) polygon_normal(select(vnf[0],vnf[1][face]))],
|
||||
direction= unit(mean(facenormals)),
|
||||
projnormals = project_plane(point4d(cross(facenormals[0],facenormals[1])), facenormals),
|
||||
ang = 180- posmod(v_theta(projnormals[1])-v_theta(projnormals[0]),360),
|
||||
horiz_face = [for(i=[0:1]) if (approx(v_abs(facenormals[i]),UP)) i],
|
||||
spin = horiz_face==[] ?
|
||||
let(
|
||||
edgedir = edge[1]-edge[0],
|
||||
nz = [for(i=[0:2]) if (!approx(edgedir[i],0)) i],
|
||||
flip = edgedir[last(nz)] < 0 ? -1 : 1
|
||||
)
|
||||
_compute_spin(direction, flip*edgedir)
|
||||
:
|
||||
let(
|
||||
hedge = len(edges_faces[0])==1 ? edges_faces[0][0]
|
||||
: edges_faces[0][horiz_face[0]],
|
||||
face = select(vnf[1],faces[horiz_face[0]]),
|
||||
edgeind = search([hedge[0]], face)[0],
|
||||
flip = select(face,edgeind+1)== hedge[1] ? 1 : -1,
|
||||
edgedir = edge[1]-edge[0]
|
||||
)
|
||||
_compute_spin(direction, flip*edgedir)
|
||||
)
|
||||
[direction,spin,[["edge_angle",ang],["edge_length",norm(edge[0]-edge[1])]]]
|
||||
: let( // This section handles corner anchors, currently spins just point up
|
||||
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, default(override[0],pos),default(override[1],dir[0]),default(override[2],dir[1]),if (len(dir)==3) dir[2]]
|
||||
|
||||
idxmax = [for (i = idx(rpts)) approx(rpts[i].x, maxx)],
|
||||
idxs = [for (i = idx(rpts)) if(approx(rpts[i].x, maxx)) i],
|
||||
veflist=[
|
||||
for(face=vnf[1])
|
||||
let(
|
||||
facemax = [for(vertind=face) if (idxmax[vertind]) vertind],
|
||||
flip = facemax[0]==face[0] && facemax[1]==last(face)
|
||||
)
|
||||
[
|
||||
if (len(facemax)==1) facemax else [],
|
||||
if (len(facemax)==2) (flip ? reverse(facemax):facemax) else [],
|
||||
if (len(facemax)>2) facemax else []
|
||||
]],
|
||||
vlist = [for(i=idx(veflist)) if (veflist[i][0]!=[]) veflist[i][0]],
|
||||
elist = [for(i=idx(veflist)) if (veflist[i][1]!=[] && !approx(rpts[veflist[i][1][0]],rpts[veflist[i][1][1]])) veflist[i][1]],
|
||||
flist = [for(i=idx(veflist)) if (veflist[i][2]!=[]) veflist[i][2]],
|
||||
faceinfo = [for(face=flist) let(poly=select(vnf[0],face)) [polygon_area(poly), centroid(poly)]], //[ area, centroid]
|
||||
facearea = len(faceinfo)==0 ? 0 : sum(column(faceinfo,0)),
|
||||
basic_spin = _compute_spin(anchor, v_abs(anchor)==UP ? BACK: UP),
|
||||
res = len(flist)>0 && !approx(facearea,0) ?
|
||||
let(
|
||||
center = column(faceinfo,0)*column(faceinfo,1)/facearea
|
||||
)
|
||||
[center,anchor,basic_spin]
|
||||
: len(elist)==2 ? // One edge (which appears twice, once in each direction)
|
||||
let(
|
||||
edge = select(vnf[0],elist[0]),
|
||||
center = mean(edge),
|
||||
edgefaces = _vnf_find_edge_faces(vnf,elist), //unique([for(e=elist) each _vnf_find_edge_faces(vnf,e)]),
|
||||
facenormals = [for(face=edgefaces) polygon_normal(select(vnf[0],vnf[1][face]))],
|
||||
direction = unit(mean(facenormals)),
|
||||
projnormals = project_plane(point4d(cross(facenormals[0],facenormals[1])), facenormals),
|
||||
ang = 180- posmod(v_theta(projnormals[1])-v_theta(projnormals[0]),360),
|
||||
horiz_face = [for(i=[0:1]) if (approx(v_abs(facenormals[i]),UP)) i], // index of horizontal face, at most one exists
|
||||
spin = horiz_face==[] ?
|
||||
let(
|
||||
edgedir = edge[1]-edge[0],
|
||||
nz = [for(i=[0:2]) if (!approx(edgedir[i],0)) i],
|
||||
flip = edgedir[last(nz)] < 0 ? -1 : 1
|
||||
)
|
||||
_compute_spin(direction, flip*edgedir)
|
||||
:
|
||||
let( // Determine whether the edge is the right or wrong direction compared to the horizongal face
|
||||
// which will determine what clockwise means so we can assign spin
|
||||
face = select(vnf[1],edgefaces[horiz_face[0]]),
|
||||
endptidx=search(column(elist,0),face),
|
||||
hedge = elist[endptidx[0]!=[] ? 0:1],
|
||||
edgedir = deltas(select(vnf[0],hedge))[0],
|
||||
flip = select(face,flatten(endptidx)[0]+1)== hedge[1] ? 1 : -1
|
||||
)
|
||||
_compute_spin(direction, flip*edgedir)
|
||||
)
|
||||
[center,direction,spin,[["edge_angle",ang],["edge_length",norm(edge[1]-edge[0])]]]
|
||||
: len(elist)>2 ? // multiple edges, which must be coplanar, use average of edge endpoints
|
||||
let(
|
||||
plist = select(vnf[0],flatten(edge)),
|
||||
center = mean(plist)
|
||||
)
|
||||
[center,anchor,basic_spin]
|
||||
: len(vlist)==0 ? assert(false,"Cannot find anchor on the VNF")
|
||||
: let(
|
||||
vlist = flatten(vlist),
|
||||
uind = unique_approx_indexed(select(vnf[0],vlist)),
|
||||
ulist = select(vlist,uind)
|
||||
)
|
||||
len(ulist)>1 ? // Multiple vertices: return average
|
||||
let(
|
||||
center = mean(select(vnf[0],ulist))
|
||||
)
|
||||
[center, anchor, basic_spin]
|
||||
: let( // one vertex case
|
||||
vuniq = unique(vlist),
|
||||
vertices = vnf[0],
|
||||
faces = vnf[1],
|
||||
cornerfaces = _vnf_find_corner_faces(vnf,vuniq), // faces = [3,9,12] indicating which faces
|
||||
normals = [for(faceind=cornerfaces) polygon_normal(select(vertices, faces[faceind]))],
|
||||
angles = [for(faceind=cornerfaces)
|
||||
let(
|
||||
thisface = faces[faceind],
|
||||
vind = flatten(search(vuniq,thisface))[0]
|
||||
)
|
||||
vector_angle(select(vertices, select(thisface,vind-1,vind+1)))
|
||||
],
|
||||
direc = unit(angles*normals)
|
||||
)
|
||||
[vnf[0][ulist[0]], direc, atan2(direc.y,direc.x)+90]
|
||||
) [anchor, default(override[0],res[0]),default(override[1],res[1]),default(override[2],res[2]),if (len(res)==3) res[2]]
|
||||
) : 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")
|
||||
|
@@ -619,6 +619,46 @@ function unique_count(list) =
|
||||
[ select(list,ind), deltas( concat(ind,[len(list)]) ) ];
|
||||
|
||||
|
||||
// Function: unique_approx()
|
||||
// Usage:
|
||||
// ulist = unique_approx(data, [eps]);
|
||||
// Description:
|
||||
// Returns a subset of items that differ by more thatn eps.
|
||||
function unique_approx(data,eps=EPSILON) =
|
||||
is_vector(data) ?
|
||||
let(
|
||||
sdata = sort(data)
|
||||
)
|
||||
[sdata[0],
|
||||
for(i=[1:1:len(data)-1]) if (abs(sdata[i]-sdata[i-1])>eps) sdata[i]
|
||||
]
|
||||
:
|
||||
let(
|
||||
dups = vector_search(data,eps,data)
|
||||
)
|
||||
[for(i=idx(data)) if (min(dups[i])==i) data[i]];
|
||||
|
||||
// Function: unique_approx_indexed()
|
||||
// Usage:
|
||||
// ulist = unique_approx(data, [eps]);
|
||||
// Description:
|
||||
// Returns the indices of a subset of items that differ by more thatn eps.
|
||||
function unique_approx_indexed(data,eps=EPSILON) =
|
||||
is_vector(data) ?
|
||||
let(
|
||||
sind = sortidx(data)
|
||||
)
|
||||
[sind[0],
|
||||
for(i=[1:1:len(data)-1]) if (abs(data[sind[i]]-data[sind[i-1]])>eps) sind[i]
|
||||
]
|
||||
:
|
||||
let(
|
||||
dups = vector_search(data,eps,data)
|
||||
)
|
||||
[for(i=idx(data)) if (min(dups[i])==i) i];
|
||||
|
||||
|
||||
|
||||
|
||||
// Section: Sorting
|
||||
|
||||
|
@@ -289,7 +289,7 @@ include <screws.scad>
|
||||
// ang=0; // Hinge rotation angle
|
||||
// module myhinge(inner)
|
||||
// knuckle_hinge(length=25, segs=11,offset=1.2, inner=inner, clearance=clear, knuckle_diam=diam,
|
||||
// pin_diam=1.8, arm_angle=28, gap=seg_gap, in_place=true, anchor=CTR,clip=2+clear)
|
||||
// pin_diam=diam-0.2, arm_angle=28, gap=seg_gap, in_place=true, anchor=CTR,clip=2+clear)
|
||||
// children();
|
||||
// module leaf() cuboid([25,2,12],anchor=TOP+BACK,rounding=7,edges=[BOT+LEFT,BOT+RIGHT]);
|
||||
// xrot(90){ // Rotate to printing orientation
|
||||
|
@@ -173,7 +173,7 @@ module chamfer_cylinder_mask(r, chamfer, d, ang=45, from_end=false, anchor=CENTE
|
||||
// Section: Rounding Masks
|
||||
|
||||
// Module: rounding_edge_mask()
|
||||
// Synopsis: Creates a shape to round a 90° edge.
|
||||
// Synopsis: Creates a shape to round an arbitrary 3d edge.
|
||||
// SynTags: Geom
|
||||
// Topics: Masks, Rounding, Shapes (3D)
|
||||
// See Also: edge_profile(), rounding_corner_mask(), default_tag(), diff()
|
||||
|
@@ -849,7 +849,8 @@ function prismoid(
|
||||
// .
|
||||
// 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.
|
||||
// anchors are located on the true object instead of the ideal cylinder and you can anchor to the edges and faces. Chamfers and roundings
|
||||
// for this module are **always** evaluated relative to the faces of the prism and never at corners as is done by default in {{cyl()}}.
|
||||
// Named Anchors:
|
||||
// "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)
|
||||
@@ -1025,7 +1026,7 @@ function regular_prism(n,
|
||||
chamfang, chamfang1, chamfang2,
|
||||
rounding, rounding1, rounding2,
|
||||
from_end, from_end1, from_end2,
|
||||
teardrop, clip_angle),
|
||||
teardrop, clip_angle,n),
|
||||
[0,height/2]
|
||||
]
|
||||
)
|
||||
@@ -1985,6 +1986,11 @@ function cylinder(h, r1, r2, center, r, d, d1, d2, anchor, spin=0, orient=UP) =
|
||||
// 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()}}.
|
||||
// .
|
||||
// Note that when chamfering or rounding, the angle of chamfers is done at the face of the facets of the shape.
|
||||
// If `circum=false` (the default) then the radius or chamfer length is measured at the corner of the shape. If `circum=true`
|
||||
// then the radius or chamfer length applies in the more usual way in the center of a facet. For cylinders with a large `$fn`
|
||||
// the difference between these two things is negligible, but it can be quite sigificant when `$fn` is small.
|
||||
// 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()
|
||||
@@ -2183,16 +2189,27 @@ function cylinder(h, r1, r2, center, r, d, d1, d2, anchor, spin=0, orient=UP) =
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// This function produces a path to rotate_extrude to make a "cylinder". The extrusion
|
||||
// produces the extreme points, and so the path is the path of a "corner" of the resulting
|
||||
// object. But things like chamfer angle and roundings should be relative to FACES. So
|
||||
// the code corrects the path to account for this, which is why it needs n, the number of
|
||||
// sides. If n is omitted, no correction occurs. If you give n and set noscale=true then
|
||||
// it corrects for the angle but still makes the chamfer lengths or rounding lengths along
|
||||
// the corner edge. This makes cylinders stack as expected (e.g. if you make a cyl with
|
||||
// radius 5 and chamfer 1 then a radius 4 cyl fits on top.)
|
||||
|
||||
function _cyl_path(
|
||||
r1, r2, l,
|
||||
chamfer, chamfer1, chamfer2,
|
||||
chamfang, chamfang1, chamfang2,
|
||||
rounding, rounding1, rounding2,
|
||||
from_end, from_end1, from_end2,
|
||||
teardrop=false, clip_angle
|
||||
teardrop=false, clip_angle, n, noscale=false
|
||||
) =
|
||||
let(
|
||||
vang = atan2(r1-r2,l),
|
||||
let(
|
||||
scale= is_def(n) ? cos(180/n) : 1,
|
||||
vang = atan2(scale*(r1-r2),l),
|
||||
_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]),
|
||||
@@ -2212,12 +2229,13 @@ function _cyl_path(
|
||||
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,
|
||||
unscale = noscale ? scale : 1,
|
||||
chamf1r = !_chamf1? 0
|
||||
: !_fromend1? _chamf1
|
||||
: law_of_sines(a=_chamf1, A=chang1, B=180-chang1-(90-sign(_chamf2)*vang)),
|
||||
: !_fromend1? unscale * _chamf1
|
||||
: unscale * 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)),
|
||||
: !_fromend2? unscale * _chamf2
|
||||
: unscale * 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)),
|
||||
@@ -2233,46 +2251,45 @@ function _cyl_path(
|
||||
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,
|
||||
|
||||
clip_ang = clip_angle == undef? 90 :
|
||||
assert(is_finite(clip_angle))
|
||||
assert(clip_angle>=0 && clip_angle<=90)
|
||||
clip_angle
|
||||
td_ang = teardrop == true? 45
|
||||
: teardrop == false? 90
|
||||
: assert(is_finite(teardrop))
|
||||
assert(teardrop>=0 && teardrop<=90)
|
||||
teardrop,
|
||||
clip_ang = clip_angle == undef? 90
|
||||
: assert(is_finite(clip_angle))
|
||||
assert(clip_angle>=0 && clip_angle<=90)
|
||||
clip_angle
|
||||
)
|
||||
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(chamf1r/scale <= r1, "chamfer1 is larger than the r1 radius of the cylinder.")
|
||||
assert(chamf2r/scale <= r2, "chamfer2 is larger than the r2 radius of the cylinder.")
|
||||
assert(roundlen1*unscale/scale <= r1, "size of rounding1 is larger than the r1 radius of the cylinder.")
|
||||
assert(roundlen2*unscale/scale <= 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.")
|
||||
assert(td_ang==90 || clip_ang==90, "teardrop= and clip_angle= are mutually exclusive options.")
|
||||
[
|
||||
if (!approx(chamf1r,0))
|
||||
each [
|
||||
[r1, -l/2] + polar_to_xy(chamf1r,180),
|
||||
[r1, -l/2] + polar_to_xy(chamf1l,90+vang),
|
||||
[r1-chamf1r/scale, -l/2], // + [-chamf1r/scale,0],//polar_to_xy(chamf1r,180)),
|
||||
[r1, -l/2] + xscale(1/scale,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),-l/2],[r1,-l/2],[r2,l/2]], ang=td_ang)
|
||||
each xscale(1/scale,_teardrop_corner(r=round1*unscale, corner=[[r1*scale-2*roundlen1,-l/2],[r1*scale,-l/2],[r2*scale,l/2]], ang=td_ang))
|
||||
else if (!approx(round1,0) && clip_ang < 90)
|
||||
each _clipped_corner(r=round1, corner=[[max(0,r1-2*roundlen1),-l/2],[r1,-l/2],[r2,l/2]], ang=clip_ang)
|
||||
each xscale(1/scale,_clipped_corner(r=round1*unscale, corner=[[r1*scale-2*roundlen1,-l/2],[r1*scale,-l/2],[r2*scale,l/2]], ang=clip_ang))
|
||||
else if (!approx(round1,0) && td_ang >= 90)
|
||||
each arc(r=abs(round1), corner=[[max(0,r1-2*roundlen1),-l/2],[r1,-l/2],[r2,l/2]])
|
||||
each xscale(1/scale,arc(r=abs(round1*unscale), corner=[[r1*scale-2*roundlen1,-l/2],[r1*scale,-l/2],[r2*scale,l/2]]))
|
||||
else [r1,-l/2],
|
||||
|
||||
if (is_finite(chamf2r) && !approx(chamf2r,0))
|
||||
each [
|
||||
[r2, l/2] + polar_to_xy(chamf2l,270+vang),
|
||||
[r2, l/2] + polar_to_xy(chamf2r,180),
|
||||
[r2, l/2] + xscale(1/scale,polar_to_xy(chamf2l,270+vang)),
|
||||
[r2-chamf2r/scale, l/2]
|
||||
]
|
||||
else if (is_finite(round2) && !approx(round2,0))
|
||||
each arc(r=abs(round2), corner=[[r1,-l/2],[r2,l/2],[max(0,r2-2*roundlen2),l/2]])
|
||||
each xscale(1/scale,arc(r=abs(round2*unscale), corner=[[r1*scale,-l/2],[r2*scale,l/2],[r2*scale-2*roundlen2,l/2]]))
|
||||
else [r2,l/2],
|
||||
];
|
||||
|
||||
@@ -2331,7 +2348,7 @@ function cyl(
|
||||
chamfang, chamfang1, chamfang2,
|
||||
rounding, rounding1, rounding2,
|
||||
from_end, from_end1, from_end2,
|
||||
teardrop, clip_angle),
|
||||
teardrop, clip_angle, sides, !circum),
|
||||
path = [
|
||||
if (texture==undef) [0,-l/2-extra1],
|
||||
if (extra1>0) cpath[0]-[0,extra1],
|
||||
@@ -2438,7 +2455,7 @@ module cyl(
|
||||
cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides);
|
||||
} else {
|
||||
vnf = cyl(
|
||||
l=l, r1=r1, r2=r2, center=true,
|
||||
l=l, r1=_r1, r2=_r2, center=true, circum=circum,
|
||||
chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
|
||||
chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
|
||||
rounding=rounding, rounding1=rounding1, rounding2=rounding2,
|
||||
@@ -2718,7 +2735,10 @@ module zcyl(
|
||||
// See Also: rect_tube()
|
||||
// Description:
|
||||
// Makes a hollow tube that can be cylindrical or conical by specifying inner and outer dimensions or by giving one dimension and
|
||||
// wall thickness.
|
||||
// wall thickness.
|
||||
// .
|
||||
// Chamfering and rounding lengths are measured based on the corners of the object except for the inner diameter when `circum=true`, in
|
||||
// which case chamfers and roundings are measured from the facets. This only matters when `$fn` is small.
|
||||
// Usage: Basic cylindrical tube, specifying inner and outer radius or diameter
|
||||
// tube(h|l, or, ir, [center], [realign=], [anchor=], [spin=],[orient=]) [ATTACHMENTS];
|
||||
// tube(h|l, od=, id=, ...) [ATTACHMENTS];
|
||||
@@ -2912,12 +2932,12 @@ module tube(
|
||||
each _cyl_path(r1,r2,h,
|
||||
chamfer1=ochamfer1, chamfer2=ochamfer2,
|
||||
rounding1=orounding1, rounding2=orounding2,
|
||||
teardrop=teardrop, clip_angle=clip_angle),
|
||||
teardrop=teardrop, clip_angle=clip_angle,n=osides, noscale=true),
|
||||
[0,h/2]
|
||||
];
|
||||
ipath = _cyl_path(adj_ir1,adj_ir2,h,
|
||||
chamfer1=ichamfer1, chamfer2=ichamfer2,
|
||||
rounding1=irounding1,rounding2=irounding2);
|
||||
rounding1=irounding1,rounding2=irounding2,n=isides, noscale=!circum);
|
||||
inside = [
|
||||
[0,-h/2-1],
|
||||
ipath[0]-[0,1],
|
||||
|
37
vnf.scad
37
vnf.scad
@@ -46,8 +46,9 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
|
||||
// The "min_edge" style picks the shorter edge to
|
||||
// subdivide for each quadrilateral, so the division may not be uniform across the shape. The "quincunx" style
|
||||
// adds a vertex in the center of each quadrilateral and creates four triangles, and the "convex" and "concave" styles
|
||||
// choose the locally convex/concave subdivision. The "min_area" option creates the triangulation with the minimal area. Degenerate faces
|
||||
// are not included in the output, but if this results in unused vertices they still appear in the output.
|
||||
// choose the locally convex/concave subdivision. The "min_area" option creates the triangulation with the minimal area.
|
||||
// The "quad" style makes quadrilateral edges, which may not be coplanar, relying on OpensCAD to decide how to handle them. Degenerate faces
|
||||
// are not included in the output, but if this results in unused vertices, those unused vertices do still appear in the output.
|
||||
// .
|
||||
// You can apply a texture to the vertex array VNF using the usual texture parameters.
|
||||
// See [Texturing](skin.scad#section-texturing) for more details on how textures work.
|
||||
@@ -337,7 +338,7 @@ function vnf_vertex_array(
|
||||
texture, tex_reps, tex_size, tex_samples, tex_inset=false, tex_rot=0, tex_scaling="default",
|
||||
tex_depth=1, tex_extra, tex_skip, sidecaps,sidecap1,sidecap2, normals
|
||||
) =
|
||||
assert(in_list(style,["default","alt","quincunx", "convex","concave", "min_edge","min_area","flip1","flip2"]))
|
||||
assert(in_list(style,["default","alt","quincunx", "convex","concave", "min_edge","min_area","flip1","flip2","quad"]))
|
||||
assert(is_matrix(points[0], n=3),"\nPoint array has the wrong shape or points are not 3d.")
|
||||
assert(is_consistent(points), "\nNon-rectangular or invalid point array.")
|
||||
assert(is_bool(triangulate))
|
||||
@@ -389,6 +390,7 @@ function vnf_vertex_array(
|
||||
[[i1,i5,i2],[i2,i5,i3],[i3,i5,i4],[i4,i5,i1]]
|
||||
: style=="alt" || (style=="flip1" && ((r+c)%2==0)) || (style=="flip2" && ((r+c)%2==1)) || (style=="random" && rands(0,1,1)[0]<.5)?
|
||||
[[i1,i4,i2],[i2,i4,i3]]
|
||||
: style=="default" ? [[i1,i3,i2],[i1,i4,i3]]
|
||||
: style=="min_area"?
|
||||
let(
|
||||
area42 = norm(cross(pts[i2]-pts[i1], pts[i4]-pts[i1]))+norm(cross(pts[i4]-pts[i3], pts[i2]-pts[i3])),
|
||||
@@ -427,7 +429,7 @@ function vnf_vertex_array(
|
||||
: [[i1,i3,i2],[i1,i4,i3]]
|
||||
)
|
||||
concavefaces
|
||||
: [[i1,i3,i2],[i1,i4,i3]],
|
||||
: [[i1,i2,i3,i4]],
|
||||
// remove degenerate faces
|
||||
culled_faces= [for(face=faces)
|
||||
if (norm(cross(verts[face[1]]-verts[face[0]],
|
||||
@@ -2743,28 +2745,41 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false, opacity=0.
|
||||
}
|
||||
|
||||
|
||||
// Given a single edge (pair of vertex indices) or list of them, find faces
|
||||
// that contain that edge. You must not supply two edges that could appear in
|
||||
// the same face. The use-case for more than one edge is when a single geometric edge
|
||||
// has multiple representations in the VNF. Return is a pair of face indices.
|
||||
|
||||
function _vnf_find_edge_faces(vnf,edge) =
|
||||
let(
|
||||
edge = unique(flatten(edge)),
|
||||
faces = vnf[1],
|
||||
goodind = [for(i=idx(faces))
|
||||
let(result=search(edge,faces[i]))
|
||||
let(result=flatten(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;
|
||||
unique(goodind);
|
||||
|
||||
|
||||
|
||||
// Given a VNF and an index list of vertices, return all the
|
||||
// faces (as indices into the face array) which include an item
|
||||
// from the corner list. The idea is that corner will hold all
|
||||
// the indices that correspond to a single geometric point in
|
||||
// the VNF and return just the faces for that single corner.
|
||||
|
||||
function _vnf_find_corner_faces(vnf,corner) =
|
||||
let(
|
||||
faces = vnf[1]
|
||||
faces = vnf[1],
|
||||
corner = force_list(corner),
|
||||
nomatch = repeat([],len(corner))
|
||||
)
|
||||
[for(i=idx(faces))
|
||||
let(result=search([corner],faces[i])[0])
|
||||
if (result!=[])
|
||||
i];
|
||||
unique([for(i=idx(faces))
|
||||
if (search(corner,faces[i])!=nomatch) i]);
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user