Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Adrian Mariano 2022-08-25 21:12:01 -04:00
commit 800d1b2a62
16 changed files with 1611 additions and 1592 deletions

46
.github/workflows/gen_docs.yml vendored Normal file
View file

@ -0,0 +1,46 @@
name: Regenerate Docs
on: [workflow_dispatch]
jobs:
RegenerateDocs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Clone Wiki
uses: actions/checkout@v2
with:
repository: revarbat/BOSL2.wiki
path: BOSL2.wiki
- name: Apt Update
run: sudo apt update
- name: Install Python dev
run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil gifsicle
- name: Install OpenSCAD-DocsGen package.
run: sudo pip3 install openscad-docsgen
- name: Install OpenSCAD
run: |
cd $GITHUB_WORKSPACE
wget https://files.openscad.org/OpenSCAD-2021.01-x86_64.AppImage
sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad
sudo chmod +x /usr/local/bin/openscad
- name: Generate Docs
uses: GabrielBB/xvfb-action@v1.6
env:
OPENSCADPATH: ${{ github.workspace }}/..
with:
run: openscad-docsgen -f
- name: Upload Docs to Wiki
uses: SwiftDocOrg/github-wiki-publish-action@v1
with:
path: "BOSL2.wiki"
env:
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PAT }}

56
.github/workflows/gen_tutorials.yml vendored Normal file
View file

@ -0,0 +1,56 @@
name: Regenerate Tutorials
on: [workflow_dispatch]
jobs:
RegenerateTutorials:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Clone Wiki
uses: actions/checkout@v2
with:
repository: revarbat/BOSL2.wiki
path: BOSL2.wiki
- name: Apt Update
run: sudo apt update
- name: Install Python dev
run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil gifsicle
- name: Install OpenSCAD-DocsGen package.
run: sudo pip3 install openscad-docsgen
- name: Install OpenSCAD
run: |
cd $GITHUB_WORKSPACE
wget https://files.openscad.org/OpenSCAD-2021.01-x86_64.AppImage
sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad
sudo chmod +x /usr/local/bin/openscad
- name: Tabs Check
run: |
cd $GITHUB_WORKSPACE
./scripts/check_for_tabs.sh
- name: FooTest
env:
OPENSCADPATH: ${{ github.workspace }}/..
run: echo $OPENSCADPATH
- name: Generate Tutorials
uses: GabrielBB/xvfb-action@v1.6
env:
OPENSCADPATH: ${{ github.workspace }}/..
with:
run: openscad-mdimggen -f
- name: Upload Tutorials to Wiki
uses: SwiftDocOrg/github-wiki-publish-action@v1
with:
path: "BOSL2.wiki"
env:
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PAT }}

View file

@ -519,10 +519,10 @@ function affine3d_skew_xy(xa=0, ya=0) =
assert(is_finite(xa))
assert(is_finite(ya))
[
[1, 0, tan(xa), 0],
[0, 1, tan(ya), 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
[ 1, tan(xa), 0, 0],
[tan(ya), 1, 0, 0],
[ 0, 0, 1, 0],
[ 0, 0, 0, 1]
];
@ -551,10 +551,10 @@ function affine3d_skew_xz(xa=0, za=0) =
assert(is_finite(xa))
assert(is_finite(za))
[
[1, tan(xa), 0, 0],
[0, 1, 0, 0],
[0, tan(za), 1, 0],
[0, 0, 0, 1]
[ 1, 0, tan(xa), 0],
[ 0, 1, 0, 0],
[tan(za), 0, 1, 0],
[ 0, 0, 0, 1]
];
@ -583,10 +583,10 @@ function affine3d_skew_yz(ya=0, za=0) =
assert(is_finite(ya))
assert(is_finite(za))
[
[ 1, 0, 0, 0],
[tan(ya), 1, 0, 0],
[tan(za), 0, 1, 0],
[ 0, 0, 0, 1]
[1, 0, 0, 0],
[0, 1, tan(ya), 0],
[0, tan(za), 1, 0],
[0, 0, 0, 1]
];

View file

@ -1369,7 +1369,7 @@ function bezier_vnf_degenerate_patch(patch, splinesteps=16, reverse=false, retur
// patch = [for(i=[0:3])
// [for(j=[0:3]) pts1[i]+pts2[j] ] ];
// vnf_polyhedron(bezier_vnf(patch, 163));
// uv = [0,.1,.2,.3,,.7,.8,.9,1];//lerpn(0,1,8);
// uv = [0,.1,.2,.3,.7,.8,.9,1];//lerpn(0,1,8);
// pts = bezier_patch_points(patch, uv, uv);
// normals = bezier_patch_normals(patch, uv, uv);
// for(i=idx(uv),j=idx(uv))

View file

@ -181,9 +181,9 @@ module pco1810_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP)
difference() {
union() {
if (texture == "knurled") {
textured_cylinder(d=w, h=h, texture="diamonds", tex_size=[3,3], style="concave", anchor=BOT);
cyl(d=w, h=h, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT);
} else if (texture == "ribbed") {
textured_cylinder(d=w, h=h, texture="ribs", tex_size=[3,3], style="min_edge", anchor=BOT);
cyl(d=w, h=h, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT);
} else {
cyl(d=w, l=tamper_ring_h+wall, anchor=BOTTOM);
}
@ -362,9 +362,9 @@ module pco1881_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP)
difference() {
union() {
if (texture == "knurled") {
textured_cylinder(d=w, h=11.2+wall, texture="diamonds", tex_size=[3,3], style="concave", anchor=BOT);
cyl(d=w, h=11.2+wall, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT);
} else if (texture == "ribbed") {
textured_cylinder(d=w, h=11.2+wall, texture="ribs", tex_size=[3,3], style="min_edge", anchor=BOT);
cyl(d=w, h=11.2+wall, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT);
} else {
cyl(d=w, l=11.2+wall, anchor=BOTTOM);
}
@ -567,9 +567,9 @@ module generic_bottle_cap(
// thickness so the wall+texture are the specified wall thickness. That
// seems wrong so this does specified thickness+texture
if (texture == "knurled") {
textured_cylinder(d=w + 1.5*diamMagMult, l=h, texture="diamonds", tex_size=[3,3], style="concave", anchor=BOT);
cyl(d=w + 1.5*diamMagMult, l=h, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT);
} else if (texture == "ribbed") {
textured_cylinder(d=w + 1.5*diamMagMult, l=h, texture="ribs", tex_size=[3,3], style="min_edge", anchor=BOT);
cyl(d=w + 1.5*diamMagMult, l=h, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT);
} else {
cyl(d = w, l = h, anchor = BOTTOM);
}

View file

@ -85,7 +85,6 @@
// joint_color = If given, sets the color of the joints. Overrides `color=` and `dots_color=`.
// dots_color = If given, sets the color of the endcaps and joints. Overrides `color=`.
// convexity = Max number of times a line could intersect a wall of an endcap.
// hull = If true, use `hull()` to make higher quality joints between segments, at the cost of being much slower. Default: true
// Example(2D): Drawing a Path
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
// stroke(path, width=20);
@ -157,7 +156,7 @@ function stroke(
endcap_angle, endcap_angle1, endcap_angle2, joint_angle, dots_angle,
endcap_color, endcap_color1, endcap_color2, joint_color, dots_color, color,
trim, trim1, trim2,
convexity=10, hull=true
convexity=10
) = no_function("stroke");
@ -170,7 +169,7 @@ module stroke(
endcap_angle, endcap_angle1, endcap_angle2, joint_angle, dots_angle,
endcap_color, endcap_color1, endcap_color2, joint_color, dots_color, color,
trim, trim1, trim2,
convexity=10, hull=true
convexity=10
) {
no_children($children);
module setcolor(clr) {
@ -359,7 +358,7 @@ module stroke(
for (i = [1:1:len(path2)-2]) {
$fn = quantup(segs(widths[i]/2),4);
translate(path2[i]) {
if (joints != undef) {
if (joints != undef && joints != "round") {
joint_shape = _shape_path(
joints, width[i],
joint_width,
@ -372,18 +371,21 @@ module stroke(
? rot(from=BACK,to=v1)
: zrot(joint_angle);
multmatrix(mat) polygon(joint_shape);
} else if (hull) {
hull() {
rot(from=BACK, to=path2[i]-path2[i-1])
circle(d=widths[i]);
rot(from=BACK, to=path2[i+1]-path2[i])
circle(d=widths[i]);
}
} else {
rot(from=BACK, to=path2[i]-path2[i-1])
circle(d=widths[i]);
rot(from=BACK, to=path2[i+1]-path2[i])
circle(d=widths[i]);
v1 = path2[i] - path2[i-1];
v2 = path2[i+1] - path2[i];
ang = modang(v_theta(v2) - v_theta(v1));
pv1 = rot(-90, p=unit(v1,BACK));
pv2 = rot(-90, p=unit(v2,BACK));
if (!approx(ang,0)) {
if (ang>=0) {
rot(from=RIGHT, to=pv1)
arc(d=widths[i], angle=ang, wedge=true);
} else {
rot(from=RIGHT, to=-pv2)
arc(d=widths[i], angle=-ang, wedge=true);
}
}
}
}
}
@ -439,7 +441,7 @@ module stroke(
for (i = [1:1:len(path2)-2]) {
$fn = sides[i];
translate(path2[i]) {
if (joints != undef) {
if (joints != undef && joints != "round") {
joint_shape = _shape_path(
joints, width[i],
joint_width,
@ -462,21 +464,18 @@ module stroke(
}
}
}
} else if (hull) {
hull(){
multmatrix(rotmats[i]) {
sphere(d=widths[i],style="aligned");
}
multmatrix(rotmats[i-1]) {
sphere(d=widths[i],style="aligned");
}
}
} else {
multmatrix(rotmats[i]) {
sphere(d=widths[i],style="aligned");
}
multmatrix(rotmats[i-1]) {
sphere(d=widths[i],style="aligned");
corner = select(path2,i-1,i+1);
axis = vector_axis(corner);
ang = vector_angle(corner);
if (!approx(ang,0)) {
frame_map(x=path2[i-1]-path2[i], z=-axis) {
zrot(90-0.5) {
rotate_extrude(angle=180-ang+1) {
arc(d=widths[i], start=-90, angle=180);
}
}
}
}
}
}
@ -552,41 +551,57 @@ module stroke(
// ---
// width = The width of the dashed line to draw. Module only. Default: 1
// closed = If true, treat path as a closed polygon. Default: false
// fit = If true, shrink or stretch the dash pattern so that the path ends ofter a logical dash. Default: true
// roundcaps = (Module only) If true, draws dashes with rounded caps. This often looks better. Default: true
// mindash = (Function only) Specifies the minimal dash length to return at the end of a path when fit is false. Default: 0.5
// Example(2D): Open Path
// path = [for (a=[-180:10:180]) [a/3,20*sin(a)]];
// dashed_stroke(path, [3,2], width=1);
// Example(2D): Closed Polygon
// path = circle(d=100,$fn=72);
// dashpat = [10,2,3,2,3,2];
// dashpat = [10,2, 3,2, 3,2];
// dashed_stroke(path, dashpat, width=1, closed=true);
// Example(FlatSpin,VPD=250): 3D Dashed Path
// path = [for (a=[-180:5:180]) [a/3, 20*cos(3*a), 20*sin(3*a)]];
// dashed_stroke(path, [3,2], width=1);
function dashed_stroke(path, dashpat=[3,3], closed=false) =
is_region(path) ? [for(p=path) each dashed_stroke(p,dashpat,closed=true)] :
function dashed_stroke(path, dashpat=[3,3], closed=false, fit=true, mindash=0.5) =
is_region(path) ? [
for (p = path)
each dashed_stroke(p, dashpat, closed=true, fit=fit)
] :
let(
path = closed? close_path(path) : path,
dashpat = len(dashpat)%2==0? dashpat : concat(dashpat,[0]),
plen = path_length(path),
dlen = sum(dashpat),
doff = cumsum(dashpat),
reps = floor(plen / dlen),
step = plen / reps,
freps = plen / dlen,
reps = max(1, fit? round(freps) : floor(freps)),
tlen = !fit? plen :
reps * dlen + (closed? 0 : dashpat[0]),
sc = plen / tlen,
cuts = [
for (i=[0:1:reps-1], off=doff)
let (st=i*step, x=st+off)
if (x>0 && x<plen) x
for (i = [0:1:reps], off = doff*sc)
let (x = i*dlen*sc + off)
if (x > 0 && x < plen) x
],
dashes = path_cut(path, cuts, closed=false),
evens = [for (i=idx(dashes)) if (i%2==0) dashes[i]]
dcnt = len(dashes),
evens = [
for (i = idx(dashes))
if (i % 2 == 0)
let( dash = dashes[i] )
if (i < dcnt-1 || path_length(dash) > mindash)
dashes[i]
]
) evens;
module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) {
module dashed_stroke(path, dashpat=[3,3], width=1, closed=false, fit=true, roundcaps=false) {
no_children($children);
segs = dashed_stroke(path, dashpat=dashpat*width, closed=closed);
segs = dashed_stroke(path, dashpat=dashpat*width, closed=closed, fit=fit, mindash=0.5*width);
for (seg = segs)
stroke(seg, width=width, endcaps=false);
stroke(seg, width=width, endcaps=roundcaps? "round" : false);
}

View file

@ -1069,7 +1069,7 @@ function circle_circle_intersection(r1, cp1, r2, cp2, eps=EPSILON, d1, d2) =
// for (i = [0:1:5]) {
// crn = select(path, i*2-1, i*2+1);
// ci = circle_2tangents(5, crn[0], crn[1], crn[2]);
// move(ci[0]) cyl(h=10,r=5,,orient=ci[1]);
// move(ci[0]) cyl(h=10,r=5,orient=ci[1]);
// }
function circle_2tangents(r, pt1, pt2, pt3, tangents=false, d) =
let(r = get_radius(r=r, d=d, dflt=undef))

View file

@ -36,7 +36,7 @@
// #chamfer_edge_mask(l=50, chamfer=10, orient=RIGHT);
// }
// Example: Masking by Attachment
// diff("mask")
// diff()
// cube(50, center=true) {
// edge_mask(TOP+RIGHT)
// #chamfer_edge_mask(l=50, chamfer=10);
@ -71,7 +71,7 @@ module chamfer_edge_mask(l=1, chamfer=1, excess=0.1, anchor=CENTER, spin=0, orie
// move(25*[1,-1,1]) #chamfer_corner_mask(chamfer=10);
// }
// Example: Masking by Attachment
// diff("mask")
// diff()
// cuboid(100, chamfer=20, trimcorners=false) {
// corner_mask(TOP+FWD+RIGHT)
// chamfer_corner_mask(chamfer=20);
@ -166,12 +166,12 @@ module chamfer_cylinder_mask(r, chamfer, d, ang=45, from_end=false, anchor=CENTE
// #rounding_edge_mask(l=50, r1=25, r2=10, orient=UP, anchor=BOTTOM);
// }
// Example: Masking by Attachment
// diff("mask")
// diff()
// cube(100, center=true)
// edge_mask(FRONT+RIGHT)
// #rounding_edge_mask(l=$parent_size.z+0.01, r=25);
// Example: Multiple Masking by Attachment
// diff("mask")
// diff()
// cube([80,90,100], center=true) {
// let(p = $parent_size*1.01) {
// edge_mask(TOP)
@ -237,7 +237,7 @@ module rounding_edge_mask(l, r, r1, r2, d, d1, d2, excess=0.1, anchor=CENTER, sp
// #rounding_corner_mask(r=20, spin=90);
// }
// Example: Masking by Attachment
// diff("mask")
// diff()
// cube(size=[50, 60, 70]) {
// corner_mask(TOP)
// #rounding_corner_mask(r=20);
@ -396,10 +396,11 @@ module rounding_angled_corner_mask(r, ang=90, d, anchor=CENTER, spin=0, orient=U
// up(50) rounding_cylinder_mask(r=50, rounding=10);
// }
// Example: Masking by Attachment
// diff("mask")
// diff()
// cyl(h=30, d=30) {
// attach(TOP)
// #tag("mask")rounding_cylinder_mask(d=30, rounding=5);
// #tag("remove")
// rounding_cylinder_mask(d=30, rounding=5);
// }
function rounding_cylinder_mask(r, rounding, d) = no_function("rounding_cylinder_mask");
module rounding_cylinder_mask(r, rounding, d)
@ -475,7 +476,7 @@ module rounding_hole_mask(r, rounding, excess=0.1, d, anchor=CENTER, spin=0, ori
// Example(VPD=50,VPR=[55,0,120]):
// teardrop_edge_mask(l=20, r=10, angle=40);
// Example(VPD=300,VPR=[75,0,25]):
// diff("mask")
// diff()
// cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) {
// edge_mask(BOT)
// teardrop_edge_mask(l=max($parent_size)+1, r=10, angle=40);
@ -512,7 +513,7 @@ module teardrop_edge_mask(l, r, angle, excess=0.1, d)
// Example:
// teardrop_corner_mask(r=20, angle=40);
// Example:
// diff("mask")
// diff()
// cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) {
// edge_profile(BOT)
// mask2d_teardrop(r=10, angle=40);

View file

@ -5,297 +5,102 @@
// include <BOSL2/std.scad>
// include <BOSL2/nema_steppers.scad>
// FileGroup: Parts
// FileSummary: Mounting holes for NEMA motors, and simple motor models.
// FileSummary: NEMA motor mounts and stepper motor models.
//////////////////////////////////////////////////////////////////////
// Section: Functions
// Function: nema_motor_width()
// Description: Gets width of NEMA motor of given standard size.
// Arguments:
// size = The standard NEMA motor size.
function nema_motor_width(size) = lookup(size, [
[11.0, 28.2],
[14.0, 35.2],
[17.0, 42.3],
[23.0, 57.0],
[34.0, 86.0],
]);
// Function: nema_motor_plinth_height()
// Description: Gets plinth height of NEMA motor of given standard size.
// Arguments:
// size = The standard NEMA motor size.
function nema_motor_plinth_height(size) = lookup(size, [
[11.0, 1.5],
[14.0, 2.0],
[17.0, 2.0],
[23.0, 1.6],
[34.0, 2.03],
]);
// Function: nema_motor_plinth_diam()
// Description: Gets plinth diameter of NEMA motor of given standard size.
// Arguments:
// size = The standard NEMA motor size.
function nema_motor_plinth_diam(size) = lookup(size, [
[11.0, 22.0],
[14.0, 22.0],
[17.0, 22.0],
[23.0, 38.1],
[34.0, 73.0],
]);
// Function: nema_motor_screw_spacing()
// Description: Gets screw spacing of NEMA motor of given standard size.
// Arguments:
// size = The standard NEMA motor size.
function nema_motor_screw_spacing(size) = lookup(size, [
[11.0, 23.11],
[14.0, 26.0],
[17.0, 30.99],
[23.0, 47.14],
[34.0, 69.6],
]);
// Function: nema_motor_screw_size()
// Description: Gets mount screw size of NEMA motor of given standard size.
// Arguments:
// size = The standard NEMA motor size.
function nema_motor_screw_size(size) = lookup(size, [
[11.0, 2.6],
[14.0, 3.0],
[17.0, 3.0],
[23.0, 5.1],
[34.0, 5.5],
]);
// Function: nema_motor_screw_depth()
// Description: Gets mount screw-hole depth of NEMA motor of given standard size.
// Arguments:
// size = The standard NEMA motor size.
function nema_motor_screw_depth(size) = lookup(size, [
[11.0, 3.0],
[14.0, 4.5],
[17.0, 4.5],
[23.0, 4.8],
[34.0, 9.0],
]);
// Section: Motor Models
// Module: nema11_stepper()
// Description: Creates a model of a NEMA 11 stepper motor.
// Module: nema_stepper_motor()
// Usage:
// nema_stepper_motor(size, h, shaft_len, ...) [attachments];
// Topics: Parts, Motors
// Description:
// Creates a model of a NEMA standard stepper motor.
// Arguments:
// size = The NEMA standard size of the stepper motor.
// h = Length of motor body. Default: 24mm
// shaft = Shaft diameter. Default: 5mm
// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 20mm
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// ---
// details = If false, creates a very rough motor shape, suitable for using as a mask. Default: true
// atype = The attachment set type to use when anchoring. Default: `"body"`
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `TOP`
// 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`
// Extra Anchors:
// "shaft-top" = The top of the shaft.
// "shaft-middle" = The middle of the shaft.
// "shaft-bottom" = The bottom of the shaft, 0.1mm above the plinth.
// "plinth-top" = The top of the plinth.
// "screw1" = The screw-hole in the X+Y+ quadrant.
// "screw2" = The screw-hole in the X-Y+ quadrant.
// "screw3" = The screw-hole in the X-Y- quadrant.
// "screw4" = The screw-hole in the X+Y- quadrant.
// Example:
// nema11_stepper();
module nema11_stepper(h=24, shaft=5, shaft_len=20, anchor=TOP, spin=0, orient=UP)
// Anchor Types:
// "shaft" = Anchor relative to the shaft.
// "plinth" = Anchor relative to the plinth.
// "body" = Anchor relative to the motor body.
// "screws" = Anchor relative to the screw hole centers. ie: TOP+RIGHT+FRONT is the center-top of the front-right screwhole.
// See Also: nema_stepper_motor(), nema_mount_mask()
// Examples:
// nema_stepper_motor(size=8, h=24, shaft_len=15);
// nema_stepper_motor(size=11, h=24, shaft_len=20);
// nema_stepper_motor(size=17, h=40, shaft_len=30);
// nema_stepper_motor(size=23, h=50, shaft_len=40);
// nema_stepper_motor(size=23, h=50, shaft_len=40, details=false);
module nema_stepper_motor(size=17, h=24, shaft_len=20, details=true, atype="body", anchor=TOP, spin=0, orient=UP)
{
size = 11;
motor_width = nema_motor_width(size);
plinth_height = nema_motor_plinth_height(size);
plinth_diam = nema_motor_plinth_diam(size);
screw_spacing = nema_motor_screw_spacing(size);
screw_size = nema_motor_screw_size(size);
screw_depth = nema_motor_screw_depth(size);
anchors = [
named_anchor("shaft-top", [0,0,h/2+shaft_len]),
named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
named_anchor("plinth-top", [0,0,h/2+plinth_height]),
named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
];
attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) {
up(h/2)
union() {
difference() {
info = nema_motor_info(size);
motor_width = info[0];
plinth_height = info[1];
plinth_diam = info[2];
screw_spacing = info[3];
screw_size = info[4];
screw_depth = info[5];
shaft_diam = info[6];
geom = atype=="shaft"? attach_geom(r=shaft_diam/2, h=shaft_len-plinth_height, cp=[0,0,h/2+plinth_height/2+shaft_len/2]) :
atype=="plinth"? attach_geom(r=plinth_diam/2, h=plinth_height, cp=[0,0,h/2+plinth_height/2]) :
atype=="body"? attach_geom(size=[motor_width, motor_width, h]) :
atype=="screws"? attach_geom(size=[screw_spacing, screw_spacing, screw_depth], cp=[0,0,h/2-screw_depth/2]) :
assert(in_list(atype, ["shaft", "plinth", "body", "screws"]));
attachable(anchor,spin,orient, geom=geom) {
up(h/2) {
if (details == false) {
slop = get_slop();
color([0.4, 0.4, 0.4])
cuboid(size=[motor_width, motor_width, h], chamfer=2, edges="Z", anchor=TOP);
cuboid(size=[motor_width+2*slop, motor_width+2*slop, h+slop], anchor=TOP);
color([0.6, 0.6, 0.6])
cylinder(h=plinth_height+slop, d=plinth_diam+2*slop);
color("silver")
xcopies(screw_spacing)
ycopies(screw_spacing)
cyl(r=screw_size/2, h=screw_depth*2, $fn=max(12,segs(screw_size/2)));
}
color([0.6, 0.6, 0.6]) {
cylinder(h=shaft_len+slop, d=shaft_diam+2*slop, $fn=max(12,segs(shaft_diam/2)));
} else if (size < 23) {
difference() {
cylinder(h=plinth_height, d=plinth_diam);
cyl(h=plinth_height*3, d=shaft+0.75);
color([0.4, 0.4, 0.4])
cuboid(size=[motor_width, motor_width, h], chamfer=size>=8? 2 : 0.5, edges="Z", anchor=TOP);
color("silver")
xcopies(screw_spacing)
ycopies(screw_spacing)
cyl(r=screw_size/2, h=screw_depth*2, $fn=max(12,segs(screw_size/2)));
}
}
color("silver") cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2)));
}
children();
}
}
// Module: nema14_stepper()
// Description: Creates a model of a NEMA 14 stepper motor.
// Arguments:
// h = Length of motor body. Default: 24mm
// shaft = Shaft diameter. Default: 5mm
// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 24mm
// 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`
// Extra Anchors:
// "shaft-top" = The top of the shaft.
// "shaft-middle" = The middle of the shaft.
// "shaft-bottom" = The bottom of the shaft, 0.1mm above the plinth.
// "plinth-top" = The top of the plinth.
// "screw1" = The screw-hole in the X+Y+ quadrant.
// "screw2" = The screw-hole in the X-Y+ quadrant.
// "screw3" = The screw-hole in the X-Y- quadrant.
// "screw4" = The screw-hole in the X+Y- quadrant.
// Example:
// nema14_stepper();
module nema14_stepper(h=24, shaft=5, shaft_len=24, anchor=TOP, spin=0, orient=UP)
{
size = 14;
motor_width = nema_motor_width(size);
plinth_height = nema_motor_plinth_height(size);
plinth_diam = nema_motor_plinth_diam(size);
screw_spacing = nema_motor_screw_spacing(size);
screw_size = nema_motor_screw_size(size);
screw_depth = nema_motor_screw_depth(size);
anchors = [
named_anchor("shaft-top", [0,0,h/2+shaft_len]),
named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
named_anchor("plinth-top", [0,0,h/2+plinth_height]),
named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
];
attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) {
up(h/2)
union() {
difference() {
color([0.4, 0.4, 0.4])
cuboid(size=[motor_width, motor_width, h], chamfer=2, edges="Z", anchor=TOP);
color("silver")
xcopies(screw_spacing)
ycopies(screw_spacing)
cyl(d=screw_size, h=screw_depth*2, $fn=max(12,segs(screw_size/2)));
}
color([0.6, 0.6, 0.6]) {
color([0.6, 0.6, 0.6]) {
difference() {
cylinder(h=plinth_height, d=plinth_diam);
cyl(h=plinth_height*3, d=shaft_diam+0.75);
}
}
color("silver") cylinder(h=shaft_len, d=shaft_diam, $fn=max(12,segs(shaft_diam/2)));
} else {
difference() {
cylinder(h=plinth_height, d=plinth_diam);
cyl(h=plinth_height*3, d=shaft+0.75);
}
}
color("silver") cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2)));
}
children();
}
}
// Module: nema17_stepper()
// Description: Creates a model of a NEMA 17 stepper motor.
// Arguments:
// h = Length of motor body. Default: 34mm
// shaft = Shaft diameter. Default: 5mm
// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 20mm
// 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`
// Extra Anchors:
// "shaft-top" = The top of the shaft.
// "shaft-middle" = The middle of the shaft.
// "shaft-bottom" = The bottom of the shaft, 0.1mm above the plinth.
// "plinth-top" = The top of the plinth.
// "screw1" = The screw-hole in the X+Y+ quadrant.
// "screw2" = The screw-hole in the X-Y+ quadrant.
// "screw3" = The screw-hole in the X-Y- quadrant.
// "screw4" = The screw-hole in the X+Y- quadrant.
// Example:
// nema17_stepper();
module nema17_stepper(h=34, shaft=5, shaft_len=20, anchor=TOP, spin=0, orient=UP)
{
size = 17;
motor_width = nema_motor_width(size);
plinth_height = nema_motor_plinth_height(size);
plinth_diam = nema_motor_plinth_diam(size);
screw_spacing = nema_motor_screw_spacing(size);
screw_size = nema_motor_screw_size(size);
screw_depth = nema_motor_screw_depth(size);
anchors = [
named_anchor("shaft-top", [0,0,h/2+shaft_len]),
named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
named_anchor("plinth-top", [0,0,h/2+plinth_height]),
named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
];
attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) {
up(h/2)
union() {
difference() {
color([0.4, 0.4, 0.4])
cuboid([motor_width, motor_width, h], chamfer=2, edges="Z", anchor=TOP);
color("silver")
xcopies(screw_spacing)
ycopies(screw_spacing)
cyl(d=screw_size, h=screw_depth*2, $fn=max(12,segs(screw_size/2)));
}
color([0.6, 0.6, 0.6]) {
difference() {
cylinder(h=plinth_height, d=plinth_diam);
cyl(h=plinth_height*3, d=shaft+0.75);
}
}
color([0.9, 0.9, 0.9]) {
down(h-motor_width/12) {
fwd(motor_width/2+motor_width/24/2-0.1) {
difference() {
cube(size=[motor_width/8, motor_width/24, motor_width/8], center=true);
cyl(d=motor_width/8-2, h=motor_width/6, orient=BACK, $fn=12);
union() {
color([0.4, 0.4, 0.4])
cuboid([motor_width, motor_width, h], rounding=screw_size, edges="Z", anchor=TOP);
color([0.6, 0.6, 0.6]) {
difference() {
cylinder(h=plinth_height, d=plinth_diam);
cyl(h=plinth_height*3, d=shaft_diam+0.75);
}
}
color("silver")
cylinder(h=shaft_len, d=shaft_diam, $fn=max(12,segs(shaft_diam/2)));
}
}
}
color("silver") {
difference() {
cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2)));
up(shaft_len/2+1) {
right(shaft-0.75) {
cube([shaft, shaft, shaft_len], center=true);
color([0.4, 0.4, 0.4]) {
xcopies(screw_spacing) {
ycopies(screw_spacing) {
cyl(d=screw_size, h=screw_depth*3, $fn=max(12,segs(screw_size/2)));
down(screw_depth) cuboid([screw_size*2, screw_size*2, h], anchor=TOP);
}
}
}
}
@ -307,200 +112,62 @@ module nema17_stepper(h=34, shaft=5, shaft_len=20, anchor=TOP, spin=0, orient=UP
// Module: nema23_stepper()
// Description: Creates a model of a NEMA 23 stepper motor.
// Arguments:
// h = Length of motor body. Default: 50mm
// shaft = Shaft diameter. Default: 6.35mm
// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 25mm
// 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`
// Extra Anchors:
// "shaft-top" = The top of the shaft.
// "shaft-middle" = The middle of the shaft.
// "shaft-bottom" = The bottom of the shaft, 0.1mm above the plinth.
// "plinth-top" = The top of the plinth.
// "screw1" = The screw-hole in the X+Y+ quadrant.
// "screw2" = The screw-hole in the X-Y+ quadrant.
// "screw3" = The screw-hole in the X-Y- quadrant.
// "screw4" = The screw-hole in the X+Y- quadrant.
// Example:
// nema23_stepper();
module nema23_stepper(h=50, shaft=6.35, shaft_len=25, anchor=TOP, spin=0, orient=UP)
{
size = 23;
motor_width = nema_motor_width(size);
plinth_height = nema_motor_plinth_height(size);
plinth_diam = nema_motor_plinth_diam(size);
screw_spacing = nema_motor_screw_spacing(size);
screw_size = nema_motor_screw_size(size);
screw_depth = nema_motor_screw_depth(size);
screw_inset = motor_width - screw_spacing + 1;
anchors = [
named_anchor("shaft-top", [0,0,h/2+shaft_len]),
named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
named_anchor("plinth-top", [0,0,h/2+plinth_height]),
named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
];
attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) {
up(h/2)
difference() {
union() {
color([0.4, 0.4, 0.4])
cuboid([motor_width, motor_width, h], chamfer=2, edges="Z", anchor=TOP);
color([0.4, 0.4, 0.4])
cylinder(h=plinth_height, d=plinth_diam);
color("silver")
cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2)));
}
color([0.4, 0.4, 0.4]) {
xcopies(screw_spacing) {
ycopies(screw_spacing) {
cyl(d=screw_size, h=screw_depth*3, $fn=max(12,segs(screw_size/2)));
down(screw_depth) cuboid([screw_inset, screw_inset, h], anchor=TOP);
}
}
}
}
children();
}
}
// Module: nema34_stepper()
// Description: Creates a model of a NEMA 34 stepper motor.
// Arguments:
// h = Length of motor body. Default: 75mm
// shaft = Shaft diameter. Default: 12.7mm
// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 32mm
// 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`
// Extra Anchors:
// "shaft-top" = The top of the shaft.
// "shaft-middle" = The middle of the shaft.
// "shaft-bottom" = The bottom of the shaft, 0.1mm above the plinth.
// "plinth-top" = The top of the plinth.
// "screw1" = The screw-hole in the X+Y+ quadrant.
// "screw2" = The screw-hole in the X-Y+ quadrant.
// "screw3" = The screw-hole in the X-Y- quadrant.
// "screw4" = The screw-hole in the X+Y- quadrant.
// Example:
// nema34_stepper();
module nema34_stepper(h=75, shaft=12.7, shaft_len=32, anchor=TOP, spin=0, orient=UP)
{
size = 34;
motor_width = nema_motor_width(size);
plinth_height = nema_motor_plinth_height(size);
plinth_diam = nema_motor_plinth_diam(size);
screw_spacing = nema_motor_screw_spacing(size);
screw_size = nema_motor_screw_size(size);
screw_depth = nema_motor_screw_depth(size);
screw_inset = motor_width - screw_spacing + 1;
anchors = [
named_anchor("shaft-top", [0,0,h/2+shaft_len]),
named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
named_anchor("plinth-top", [0,0,h/2+plinth_height]),
named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
];
attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) {
up(h/2)
difference() {
union() {
color([0.4, 0.4, 0.4])
cuboid(size=[motor_width, motor_width, h], chamfer=2, edges="Z", anchor=TOP);
color([0.4, 0.4, 0.4])
cylinder(h=plinth_height, d=plinth_diam);
color("silver")
cylinder(h=shaft_len, d=shaft, $fn=max(24,segs(shaft/2)));
}
color([0.4, 0.4, 0.4]) {
xcopies(screw_spacing) {
ycopies(screw_spacing) {
cylinder(d=screw_size, h=screw_depth*3, center=true, $fn=max(12,segs(screw_size/2)));
down(screw_depth) cube([screw_inset, screw_inset, h], anchor=TOP);
}
}
}
}
children();
}
}
// Section: Masking Modules
// Module: nema_mount_holes()
// Module: nema_mount_mask()
// Usage:
// nema_mount_mask(size, depth, l, ...);
// Topics: Parts, Motors
// Description: Creates a mask to use when making standard NEMA stepper motor mounts.
// Arguments:
// size = The standard NEMA motor size to make a mount for.
// depth = The thickness of the mounting hole mask. Default: 5
// l = The length of the slots, for making an adjustable motor mount. Default: 5
// ---
// 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`
// $slop = The printer-specific slop value to make parts fit just right.
// Extra Anchors:
// "screw1" = The center top of the screw hole/slot in the X+Y+ quadrant.
// "screw2" = The center top of the screw hole/slot in the X-Y+ quadrant.
// "screw3" = The center top of the screw hole/slot in the X-Y- quadrant.
// "screw4" = The center top of the screw hole/slot in the X+Y- quadrant.
// Example:
// nema_mount_holes(size=14, depth=5, l=5);
// Example:
// nema_mount_holes(size=17, depth=5, l=5);
// Example:
// nema_mount_holes(size=17, depth=5, l=0);
module nema_mount_holes(size=17, depth=5, l=5, anchor=CENTER, spin=0, orient=UP)
// Anchor Types:
// "full" = Anchor relative the full mask.
// "screws" = Anchor relative to the screw hole centers. ie: TOP+RIGHT+FRONT is the center-top of the front-right screwhole.
// See Also: nema_stepper_motor(), nema_mount_mask()
// Examples:
// nema_mount_mask(size=14, depth=5, l=5);
// nema_mount_mask(size=17, depth=5, l=5);
// nema_mount_mask(size=17, depth=5, l=0);
module nema_mount_mask(size, depth=5, l=5, atype="full", anchor=CENTER, spin=0, orient=UP)
{
motor_width = nema_motor_width(size);
plinth_diam = nema_motor_plinth_diam(size)+get_slop();
screw_spacing = nema_motor_screw_spacing(size);
screw_size = nema_motor_screw_size(size)+get_slop();
anchors = [
named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, depth/2]),
named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, depth/2]),
named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, depth/2]),
named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, depth/2]),
];
slop = get_slop();
info = nema_motor_info(size);
motor_width = info[0];
plinth_height = info[1];
plinth_diam = info[2] + slop;
screw_spacing = info[3];
screw_size = info[4] + slop;
screw_depth = info[5];
shaft_diam = info[6];
screwfn = quantup(max(8,segs(screw_size/2)),4);
plinthfn = quantup(max(8,segs(plinth_diam/2)),4);
s = [screw_spacing+screw_size, screw_spacing+screw_size+l, depth];
attachable(anchor,spin,orient, size=s, anchors=anchors) {
s = atype=="full"? [screw_spacing+screw_size, screw_spacing+screw_size+l, depth] :
atype=="screws"? [screw_spacing, screw_spacing, depth] :
assert(in_list(atype, ["full", "screws"]));
attachable(anchor,spin,orient, size=s) {
union() {
xcopies(screw_spacing) {
ycopies(screw_spacing) {
if (l>0) {
union() {
ycopies(l) cyl(h=depth, d=screw_size, $fn=screwfn);
cube([screw_size, l, depth], center=true);
}
if (l > 0) {
ycopies(l) cyl(h=depth, d=screw_size, $fn=screwfn);
cube([screw_size, l, depth], center=true);
} else {
cyl(h=depth, d=screw_size, $fn=screwfn);
}
}
}
if (l>0) {
union () {
ycopies(l) cyl(h=depth, d=plinth_diam, $fn=plinthfn);
cube([plinth_diam, l, depth], center=true);
}
if (l > 0) {
ycopies(l) cyl(h=depth, d=plinth_diam, $fn=plinthfn);
cube([plinth_diam, l, depth], center=true);
} else {
cyl(h=depth, d=plinth_diam, $fn=plinthfn);
}
@ -511,128 +178,40 @@ module nema_mount_holes(size=17, depth=5, l=5, anchor=CENTER, spin=0, orient=UP)
// Module: nema11_mount_holes()
// Description: Creates a mask to use when making NEMA 11 stepper motor mounts.
// Section: Functions
// Function: nema_motor_info()
// Usage:
// info = nema_motor_info(size);
// Description:
// Gets various dimension info for a NEMA stepper motor of a specific size.
// Returns a list of scalar values, containing, in order:
// - MOTOR_WIDTH: The full width and length of the motor.
// - PLINTH_HEIGHT: The height of the circular plinth on the face of the motor.
// - PLINTH_DIAM: The diameter of the circular plinth on the face of the motor.
// - SCREW_SPACING: The spacing between screwhole centers in both X and Y axes.
// - SCREW_SIZE: The diameter of the screws.
// - SCREW_DEPTH: The depth of the screwholes.
// - SHAFT_DIAM: The diameter of the motor shaft.
// Arguments:
// depth = The thickness of the mounting hole mask. Default: 5
// l = The length of the slots, for making an adjustable motor mount. Default: 5
// 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`
// $slop = The printer-specific slop value to make parts fit just right.
// Extra Anchors:
// "screw1" = The center top of the screw hole/slot in the X+Y+ quadrant.
// "screw2" = The center top of the screw hole/slot in the X-Y+ quadrant.
// "screw3" = The center top of the screw hole/slot in the X-Y- quadrant.
// "screw4" = The center top of the screw hole/slot in the X+Y- quadrant.
// Example:
// nema11_mount_holes(depth=5, l=5);
// Example:
// nema11_mount_holes(depth=5, l=0);
module nema11_mount_holes(depth=5, l=5, anchor=CENTER, spin=0, orient=UP)
{
nema_mount_holes(size=11, depth=depth, l=l, anchor=anchor, spin=spin, orient=orient) children();
}
// Module: nema14_mount_holes()
// Description: Creates a mask to use when making NEMA 14 stepper motor mounts.
// Arguments:
// depth = The thickness of the mounting hole mask. Default: 5
// l = The length of the slots, for making an adjustable motor mount. Default: 5
// 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`
// $slop = The printer-specific slop value to make parts fit just right.
// Extra Anchors:
// "screw1" = The center top of the screw hole/slot in the X+Y+ quadrant.
// "screw2" = The center top of the screw hole/slot in the X-Y+ quadrant.
// "screw3" = The center top of the screw hole/slot in the X-Y- quadrant.
// "screw4" = The center top of the screw hole/slot in the X+Y- quadrant.
// Example:
// nema14_mount_holes(depth=5, l=5);
// Example:
// nema14_mount_holes(depth=5, l=0);
module nema14_mount_holes(depth=5, l=5, anchor=CENTER, spin=0, orient=UP)
{
nema_mount_holes(size=14, depth=depth, l=l, anchor=anchor, spin=spin, orient=orient) children();
}
// Module: nema17_mount_holes()
// Description: Creates a mask to use when making NEMA 17 stepper motor mounts.
// Arguments:
// depth = The thickness of the mounting hole mask. Default: 5
// l = The length of the slots, for making an adjustable motor mount. Default: 5
// 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`
// $slop = The printer-specific slop value to make parts fit just right.
// Extra Anchors:
// "screw1" = The center top of the screw hole/slot in the X+Y+ quadrant.
// "screw2" = The center top of the screw hole/slot in the X-Y+ quadrant.
// "screw3" = The center top of the screw hole/slot in the X-Y- quadrant.
// "screw4" = The center top of the screw hole/slot in the X+Y- quadrant.
// Example:
// nema17_mount_holes(depth=5, l=5);
// Example:
// nema17_mount_holes(depth=5, l=0);
module nema17_mount_holes(depth=5, l=5, anchor=CENTER, spin=0, orient=UP)
{
nema_mount_holes(size=17, depth=depth, l=l, anchor=anchor, spin=spin, orient=orient) children();
}
// Module: nema23_mount_holes()
// Description: Creates a mask to use when making NEMA 23 stepper motor mounts.
// Arguments:
// depth = The thickness of the mounting hole mask. Default: 5
// l = The length of the slots, for making an adjustable motor mount. Default: 5
// 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`
// $slop = The printer-specific slop value to make parts fit just right.
// Extra Anchors:
// "screw1" = The center top of the screw hole/slot in the X+Y+ quadrant.
// "screw2" = The center top of the screw hole/slot in the X-Y+ quadrant.
// "screw3" = The center top of the screw hole/slot in the X-Y- quadrant.
// "screw4" = The center top of the screw hole/slot in the X+Y- quadrant.
// Example:
// nema23_mount_holes(depth=5, l=5);
// Example:
// nema23_mount_holes(depth=5, l=0);
module nema23_mount_holes(depth=5, l=5, anchor=CENTER, spin=0, orient=UP)
{
nema_mount_holes(size=23, depth=depth, l=l, anchor=anchor, spin=spin, orient=orient) children();
}
// Module: nema34_mount_holes()
// Description: Creates a mask to use when making NEMA 34 stepper motor mounts.
// Arguments:
// depth = The thickness of the mounting hole mask. Default: 5
// l = The length of the slots, for making an adjustable motor mount. Default: 5
// 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`
// $slop = The printer-specific slop value to make parts fit just right.
// Extra Anchors:
// "screw1" = The center top of the screw hole/slot in the X+Y+ quadrant.
// "screw2" = The center top of the screw hole/slot in the X-Y+ quadrant.
// "screw3" = The center top of the screw hole/slot in the X-Y- quadrant.
// "screw4" = The center top of the screw hole/slot in the X+Y- quadrant.
// Example:
// nema34_mount_holes(depth=5, l=5);
// Example:
// nema34_mount_holes(depth=5, l=0);
module nema34_mount_holes(depth=5, l=5, anchor=CENTER, spin=0, orient=UP)
{
nema_mount_holes(size=34, depth=depth, l=l, anchor=anchor, spin=spin, orient=orient) children();
}
// size = The standard NEMA motor size.
function nema_motor_info(size) =
let(
info_arr = [
[ 6, [ 14.0, 1.50, 11.0, 11.50, 1.6, 2.5, 4.00]],
[ 8, [ 20.3, 1.50, 16.0, 15.40, 2.0, 2.5, 4.00]],
[11, [ 28.2, 1.50, 22.0, 23.11, 2.6, 3.0, 5.00]],
[14, [ 35.2, 2.00, 22.0, 26.00, 3.0, 4.5, 5.00]],
[17, [ 42.3, 2.00, 22.0, 31.00, 3.0, 4.5, 5.00]],
[23, [ 57.0, 1.60, 38.1, 47.00, 5.1, 4.8, 6.35]],
[34, [ 86.0, 2.00, 73.0, 69.60, 6.5, 10.0, 14.00]],
[42, [110.0, 1.50, 55.5, 88.90, 8.5, 12.7, 19.00]],
],
found = [for(info=info_arr) if(info[0]==size) info[1]]
)
assert(found, "Unsupported NEMA size.")
found[0];

View file

@ -143,8 +143,8 @@ function phillips_diam(size, depth) =
// Examples:
// torx_mask(size=30, l=10, $fa=1, $fs=1);
module torx_mask(size, l=5, center, anchor, spin=0, orient=UP) {
anchor = get_anchor(anchor, center, BOT, BOT);
od = torx_diam(size);
anchor = get_anchor(anchor, center, BOT, BOT);
attachable(anchor,spin,orient, d=od, l=l) {
linear_extrude(height=l, convexity=4, center=true) {
torx_mask2d(size);
@ -165,10 +165,11 @@ module torx_mask(size, l=5, center, anchor, spin=0, orient=UP) {
// torx_mask2d(size=30, $fa=1, $fs=1);
module torx_mask2d(size) {
no_children($children);
od = torx_diam(size);
id = _torx_inner_diam(size);
tip = _torx_tip_radius(size);
rounding = _torx_rounding_radius(size);
info = torx_info(size);
od = info[0];
id = info[1];
tip = info[3];
rounding = info[4];
base = od - 2*tip;
$fn = quantup(segs(od/2),12);
difference() {
@ -195,197 +196,70 @@ module torx_mask2d(size) {
}
// Function: torx_info()
// Usage:
// info = torx_info(size);
// Description:
// Get the typical dimensional info for a given Torx size.
// Returns a list containing, in order:
// - Outer Diameter
// - Inner Diameter
// - Drive Hole Depth
// - External Tip Rounding Radius
// - Inner Rounding Radius
// Arguments:
// size = Torx size.
function torx_info(size) =
let(
info_arr = [
//T# OD ID H Re Ri
[ 1, [ 0.90, 0.65, 1.19, 0.059, 0.201]],
[ 2, [ 1.00, 0.73, 1.70, 0.069, 0.224]],
[ 3, [ 1.20, 0.87, 1.70, 0.081, 0.266]],
[ 4, [ 1.35, 0.98, 1.70, 0.090, 0.308]],
[ 5, [ 1.48, 1.08, 1.70, 0.109, 0.330]],
[ 6, [ 1.75, 1.27, 1.87, 0.132, 0.383]],
[ 7, [ 2.08, 1.50, 3.10, 0.161, 0.446]],
[ 8, [ 2.40, 1.75, 3.10, 0.190, 0.510]],
[ 9, [ 2.58, 1.87, 3.35, 0.207, 0.554]],
[ 10, [ 2.80, 2.05, 3.61, 0.229, 0.598]],
[ 15, [ 3.35, 2.40, 3.86, 0.267, 0.716]],
[ 20, [ 3.95, 2.85, 4.12, 0.305, 0.859]],
[ 25, [ 4.50, 3.25, 4.50, 0.375, 0.920]],
[ 27, [ 5.07, 3.65, 4.75, 0.390, 1.108]],
[ 30, [ 5.60, 4.05, 5.00, 0.451, 1.194]],
[ 40, [ 6.75, 4.85, 5.64, 0.546, 1.428]],
[ 45, [ 7.93, 5.64, 6.27, 0.574, 1.796]],
[ 50, [ 8.95, 6.45, 6.53, 0.775, 1.816]],
[ 55, [ 11.35, 8.05, 6.78, 0.867, 2.667]],
[ 60, [ 13.45, 9.60, 8.22, 1.067, 2.883]],
[ 70, [ 15.70, 11.20, 9.01, 1.194, 3.477]],
[ 80, [ 17.75, 12.80, 9.95, 1.526, 3.627]],
[ 90, [ 20.20, 14.40, 10.61, 1.530, 4.468]],
[100, [ 22.40, 16.00, 11.40, 1.720, 4.925]],
],
found = [for(info=info_arr) if(info[0]==size) info[1]]
)
assert(found, "Unsupported Torx size.")
found[0];
// Function: torx_diam()
// Usage:
// diam = torx_diam(size);
// Description: Get the typical outer diameter of Torx profile.
// Arguments:
// size = Torx size.
function torx_diam(size) = lookup(size, [
[ 6, 1.75],
[ 8, 2.40],
[ 10, 2.80],
[ 15, 3.35],
[ 20, 3.95],
[ 25, 4.50],
[ 30, 5.60],
[ 40, 6.75],
[ 45, 7.93],
[ 50, 8.95],
[ 55, 11.35],
[ 60, 13.45],
[ 70, 15.70],
[ 80, 17.75],
[ 90, 20.20],
[100, 22.40]
]);
function torx_diam(size) = torx_info(size)[0];
/*
[
[ 1, 0.90],
[ 2, 1.00],
[ 3, 1.20],
[ 4, 1.35],
[ 5, 1.50],
[ 6, 1.75],
[ 7, 2.10],
[ 8, 2.40],
[ 9, 2.60],
[ 10, 2.80],
[ 15, 3.35],
[ 20, 3.95],
[ 25, 4.50],
[ 27, 5.10],
[ 30, 5.60],
[ 35, 5.90],
[ 40, 6.75],
[ 45, 7.93],
[ 50, 8.95],
[ 55, 11.35],
[ 60, 13.45],
[ 70, 15.70],
[ 80, 17.75],
[ 90, 20.20],
[100, 22.40]
];
*/
/// Internal Function: torx_inner_diam()
/// Usage:
/// diam = torx_inner_diam(size);
/// Description: Get typical inner diameter of Torx profile.
/// Arguments:
/// size = Torx size.
function _torx_inner_diam(size) = lookup(size, [
[ 6, 1.27],
[ 8, 1.75],
[ 10, 2.05],
[ 15, 2.40],
[ 20, 2.85],
[ 25, 3.25],
[ 30, 4.05],
[ 40, 4.85],
[ 45, 5.64],
[ 50, 6.45],
[ 55, 8.05],
[ 60, 9.60],
[ 70, 11.20],
[ 80, 12.80],
[ 90, 14.40],
[100, 16.00]
]);
/*
[
[ 1, 0.60],
[ 2, 0.07],
[ 3, 0.85],
[ 4, 1.00],
[ 5, 1.10],
[ 6, 1.27],
[ 7, 1.50],
[ 8, 1.75],
[ 9, 1.90],
[ 10, 2.05],
[ 15, 2.40],
[ 20, 2.85],
[ 25, 3.25],
[ 27, 3.68],
[ 30, 4.05],
[ 40, 4.85],
[ 45, 5.64],
[ 50, 6.45],
[ 55, 8.05],
[ 60, 9.60],
[ 70, 11.20],
[ 80, 12.80],
[ 90, 14.40],
[100, 16.00]
]);
*/
// Function: torx_depth()
// Usage:
// depth = torx_depth(size);
// Description: Gets typical drive hole depth.
// Arguments:
// size = Torx size.
function torx_depth(size) = lookup(size, [
[ 6, 1.82],
[ 8, 3.05],
[ 10, 3.56],
[ 15, 3.81],
[ 20, 4.07],
[ 25, 4.45],
[ 30, 4.95],
[ 40, 5.59],
[ 45, 6.22],
[ 50, 6.48],
[ 55, 6.73],
[ 60, 8.17],
[ 70, 8.96],
[ 80, 9.90],
[ 90, 10.56],
[100, 11.35]
]);
/// Internal Function: torx_tip_radius()
/// Usage:
/// rad = torx_tip_radius(size);
/// Description: Gets minor rounding radius of Torx profile.
/// Arguments:
/// size = Torx size.
function _torx_tip_radius(size) = lookup(size, [
[ 6, 0.132],
[ 8, 0.190],
[ 10, 0.229],
[ 15, 0.267],
[ 20, 0.305],
[ 25, 0.375],
[ 30, 0.451],
[ 40, 0.546],
[ 45, 0.574],
[ 50, 0.775],
[ 55, 0.867],
[ 60, 1.067],
[ 70, 1.194],
[ 80, 1.526],
[ 90, 1.530],
[100, 1.720]
]);
/// Internal Function: torx_rounding_radius()
/// Usage:
/// rad = torx_rounding_radius(size);
/// Description: Gets major rounding radius of Torx profile.
/// Arguments:
/// size = Torx size.
function _torx_rounding_radius(size) = lookup(size, [
[ 6, 0.383],
[ 8, 0.510],
[ 10, 0.598],
[ 15, 0.716],
[ 20, 0.859],
[ 25, 0.920],
[ 30, 1.194],
[ 40, 1.428],
[ 45, 1.796],
[ 50, 1.816],
[ 55, 2.667],
[ 60, 2.883],
[ 70, 3.477],
[ 80, 3.627],
[ 90, 4.468],
[100, 4.925]
]);
function torx_depth(size) = torx_info(size)[2];

View file

@ -52,6 +52,7 @@ use <builtins.scad>
// Example: Called as Function
// vnf = cube([20,40,50]);
// vnf_polyhedron(vnf);
module cube(size=1, center, anchor, spin=0, orient=UP)
{
anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]);
@ -177,6 +178,7 @@ function cube(size=1, center, anchor, spin=0, orient=UP) =
// cuboid([4,2,1], rounding=2, edges=[FWD+RIGHT,BACK+LEFT]);
// Example: Standard Connectors
// cuboid(40) show_anchors();
module cuboid(
size=[1,1,1],
p1, p2,
@ -633,6 +635,7 @@ function cuboid(
// Example(Spin,VPD=160,VPT=[0,0,10]): Standard Connectors
// prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5])
// show_anchors();
module prismoid(
size1, size2, h, shift=[0,0],
rounding=0, rounding1, rounding2,
@ -775,6 +778,7 @@ function prismoid(
// octahedron(size=40);
// Example: Anchors
// octahedron(size=40) show_anchors();
module octahedron(size=1, anchor=CENTER, spin=0, orient=UP) {
vnf = octahedron(size=size);
attachable(anchor,spin,orient, vnf=vnf, extent=true) {
@ -900,6 +904,7 @@ function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
// rounding1=[5,0,10,0], irounding1=[3,0,8,0],
// rounding2=[0,5,0,10], irounding2=[0,3,0,8]
// );
module rect_tube(
h, size, isize, center, shift=[0,0],
wall, size1, size2, isize1, isize2,
@ -1014,6 +1019,7 @@ function rect_tube(
// wedge([20, 40, 15]);
// Example: Standard Connectors
// wedge([20, 40, 15]) show_anchors();
module wedge(size=[1, 1, 1], center, anchor, spin=0, orient=UP)
{
size = scalar_vec3(size);
@ -1095,6 +1101,7 @@ function wedge(size=[1,1,1], center, anchor, spin=0, orient=UP) =
// cylinder(h=30, d=25) show_anchors();
// cylinder(h=30, d1=25, d2=10) show_anchors();
// }
module cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP)
{
anchor = get_anchor(anchor, center, BOTTOM, BOTTOM);
@ -1128,13 +1135,7 @@ function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP)
// Module: cyl()
//
// Description:
// Creates cylinders in various anchorings and orientations, with optional rounding and chamfers.
// You can use `h` and `l` interchangably, and all variants allow specifying size by either `r`|`d`,
// or `r1`|`d1` and `r2`|`d2`. Note: the chamfers and rounding cannot be cumulatively longer than
// the cylinder's length.
// Function&Module: cyl()
//
// Usage: Normal Cylinders
// cyl(l|h, r, [center], [circum=], [realign=]) [ATTACHMENTS];
@ -1154,6 +1155,14 @@ function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP)
// cyl(l|h, r|d, rounding2=, ...);
// cyl(l|h, r|d, rounding1=, rounding2=, ...);
//
// Topics: Cylinders, Textures, Rounding, Chamfers
//
// Description:
// Creates cylinders in various anchorings and orientations, with optional rounding and chamfers.
// You can use `h` and `l` interchangably, and all variants allow specifying size by either `r`|`d`,
// or `r1`|`d1` and `r2`|`d2`. Note: the chamfers and rounding cannot be cumulatively longer than
// the cylinder's length.
//
// Arguments:
// l / h = Length of cylinder along oriented axis. Default: 1
// r = Radius of cylinder. Default: 1
@ -1165,6 +1174,7 @@ function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP)
// d1 = Diameter of the negative (X-, Y-, Z-) end of cylinder.
// d2 = Diameter of the positive (X+, Y+, Z+) end of cylinder.
// circum = If true, cylinder should circumscribe the circle of the given size. Otherwise inscribes. Default: `false`
// 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 cylinder. Default: none.
// chamfer1 = The size of the chamfer on the bottom end of the cylinder. Default: none.
// chamfer2 = The size of the chamfer on the top end of the cylinder. Default: none.
@ -1176,10 +1186,20 @@ function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP)
// rounding1 = The radius of the rounding on the bottom end of the cylinder.
// rounding2 = The radius of the rounding on the top end of the cylinder.
// realign = If true, rotate the cylinder by half the angle of one face.
// 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_counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height.
// tex_inset = If numeric, lowers the texture into the surface by that amount, before the tex_scale multiplier is applied. If `true`, insets by exactly `1`. Default: `false`
// tex_rot = If true, rotates the texture 90º.
// tex_scale = Scaling multiplier for the texture depth.
// tex_samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8
// tex_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`
//
// See Also: texture(), rotate_sweep()
//
// Example: By Radius
// xdistribute(30) {
// cyl(l=40, r=10);
@ -1230,6 +1250,156 @@ function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP)
// cyl(l=30, d1=25, d2=10) show_anchors();
// }
//
// Example: Texturing with heightfield diamonds
// cyl(h=40, r=20, texture="diamonds", tex_size=[5,5]);
//
// Example: Texturing with heightfield pyramids
// cyl(h=40, r1=20, r2=15,
// texture="pyramids", tex_size=[5,5],
// tex_style="convex");
//
// Example: Texturing with heightfield truncated pyramids
// cyl(h=40, r1=20, r2=15, chamfer=5,
// texture="trunc_pyramids",
// tex_size=[5,5], tex_style="convex");
//
// Example: Texturing with VNF tile "vnf_dots"
// cyl(h=40, r1=20, r2=15, rounding=9,
// texture="vnf_dots", tex_size=[5,5],
// tex_samples=6);
//
// Example: Texturing with VNF tile "vnf_bricks"
// cyl(h=50, r1=25, r2=20, shift=[0,10], rounding1=-10,
// texture="vnf_bricks", tex_size=[10,10],
// tex_scale=0.5, tex_style="concave");
//
// Example: No Texture Taper
// cyl(d1=25, d2=20, h=30, rounding=5,
// texture="trunc_ribs", tex_size=[5,1]);
//
// Example: Taper Texure at Extreme Ends
// cyl(d1=25, d2=20, h=30, rounding=5,
// texture="trunc_ribs", tex_taper=0,
// tex_size=[5,1]);
//
// Example: Taper Texture over First and Last 10%
// cyl(d1=25, d2=20, h=30, rounding=5,
// texture="trunc_ribs", tex_taper=10,
// tex_size=[5,1]);
function cyl(
h, r, center,
l, r1, r2,
d, d1, d2,
length, height,
chamfer, chamfer1, chamfer2,
chamfang, chamfang1, chamfang2,
rounding, rounding1, rounding2,
circum=false, realign=false,
from_end=false, shift=[0,0],
texture, tex_size=[5,5], tex_counts,
tex_inset=false, tex_rot=false,
tex_scale=1, tex_samples,
tex_taper, tex_style="min_edge",
anchor, spin=0, orient=UP
) =
let(
l = first_defined([l, h, length, height, 1]),
_r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1),
_r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1),
sides = segs(max(_r1,_r2)),
sc = circum? 1/cos(180/sides) : 1,
r1 = _r1 * sc,
r2 = _r2 * sc,
phi = atan2(l, r2-r1),
anchor = get_anchor(anchor,center,BOT,CENTER)
)
assert(is_finite(l), "l/h/length/height must be a finite number.")
assert(is_finite(r1), "r/r1/d/d1 must be a finite number.")
assert(is_finite(r2), "r2 or d2 must be a finite number.")
assert(is_vector(shift,2), "shift must be a 2D vector.")
let(
vnf = texture != undef? _textured_cylinder(
l=l, r1=r1, r2=r2,
texture=texture, tex_size=tex_size,
counts=tex_counts, tex_scale=tex_scale,
inset=tex_inset, rot=tex_rot,
style=tex_style, taper=tex_taper,
chamfer=chamfer,
chamfer1=chamfer1,
chamfer2=chamfer2,
rounding=rounding,
rounding1=rounding1,
rounding2=rounding2,
samples=tex_samples
) :
!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2])?
cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides) :
let(
vang = atan2(l, r1-r2)/2,
chang = default(chamfang, 45),
chang1 = 90-first_defined([chamfang1, chamfang, vang]),
chang2 = 90-first_defined([chamfang2, chamfang, 90-vang]),
checks1 =
assert(is_finite(chang) && chang>0 && chang<90, "chamfang must be a number between 0 and 90 (exclusive) if given.")
assert(is_finite(chang1) && chang1>0 && chang1<90, "chamfang1 must be a number between 0 and 90 (exclusive) if given.")
assert(is_finite(chang2) && chang2>0 && chang2<90, "chamfang2 must be a number between 0 and 90 (exclusive) if given.")
undef,
chamf = default(chamfer, 0) * (from_end? 1 : tan(chang1)),
chamf1 = first_defined([chamfer1, chamfer, 0]) * (from_end? 1 : tan(chang1)),
chamf2 = first_defined([chamfer2, chamfer, 0]) * (from_end? 1 : tan(chang2)),
round = default(rounding, 0),
round1 = first_defined([rounding1, rounding, 0]),
round2 = first_defined([rounding2, rounding, 0]),
dy1 = abs(first_defined([chamf1, round1, 0])),
dy2 = abs(first_defined([chamf2, round2, 0])),
checks2 =
assert(is_finite(chamf), "chamfer must be a finite number if given.")
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(round), "rounding must be a finite number if given.")
assert(is_finite(round1), "rounding1 must be a finite number if given.")
assert(is_finite(round2), "rounding2 must be a finite number if given.")
assert(chamf <= r1, "chamfer is larger than the r1 radius of the cylinder.")
assert(chamf <= r2, "chamfer is larger than the r2 radius of the cylinder.")
assert(chamf1 <= r1, "chamfer1 is larger than the r1 radius of the cylinder.")
assert(chamf2 <= r2, "chamfer2 is larger than the r2 radius of the cylinder.")
assert(round <= r1, "rounding is larger than the r1 radius of the cylinder.")
assert(round <= r2, "rounding is larger than the r2 radius of the cylinder.")
assert(round1 <= r1, "rounding1 is larger than the r1 radius of the cylinder.")
assert(round2 <= r2, "rounding2 is larger than the r1 radius of the cylinder.")
assert(dy1+dy2 <= l, "Sum of fillets and chamfer sizes must be less than the length of the cylinder.")
undef,
path = [
[0,-l/2],
if (is_finite(chamf1) && !approx(chamf1,0))
let(
p1 = [r1-chamf1/tan(chang1),-l/2],
p2 = lerp([r1,-l/2],[r2,l/2],abs(chamf1)/l)
) each [p1,p2]
else if (is_finite(round1) && !approx(round1,0))
each arc(r=abs(round1), corner=[[(round1>0?0:1e6),-l/2],[r1,-l/2],[r2,l/2]])
else [r1,-l/2],
if (is_finite(chamf2) && !approx(chamf2,0))
let(
p1 = lerp([r2,l/2],[r1,-l/2],abs(chamf2)/l),
p2 = [r2-chamf2/tan(chang2),l/2]
) each [p1,p2]
else if (is_finite(round2) && !approx(round2,0))
each arc(r=abs(round2), corner=[[r1,-l/2],[r2,l/2],[(round2>0?0:1e6),l/2]])
else [r2,l/2],
[0,l/2]
]
) rotate_sweep(path),
skmat = down(l/2) *
skew(sxz=shift.x/l, syz=shift.y/l) *
up(l/2) *
zrot(realign? 180/sides : 0),
ovnf = apply(skmat, vnf)
)
reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, shift=shift, p=ovnf);
module cyl(
h, r, center,
l, r1, r2,
@ -1237,7 +1407,12 @@ module cyl(
chamfer, chamfer1, chamfer2,
chamfang, chamfang1, chamfang2,
rounding, rounding1, rounding2,
circum=false, realign=false, from_end=false,
circum=false, realign=false,
from_end=false, shift=[0,0],
texture, tex_size=[5,5], tex_counts,
tex_inset=false, tex_rot=false,
tex_scale=1, tex_samples,
tex_taper, tex_style="min_edge",
anchor, spin=0, orient=UP
) {
l = first_defined([l, h, 1]);
@ -1245,86 +1420,76 @@ module cyl(
_r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
sides = segs(max(_r1,_r2));
sc = circum? 1/cos(180/sides) : 1;
r1=_r1*sc;
r2=_r2*sc;
r1 = _r1 * sc;
r2 = _r2 * sc;
phi = atan2(l, r2-r1);
anchor = get_anchor(anchor,center,BOT,CENTER);
attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
skmat = down(l/2) * skew(sxz=shift.x/l, syz=shift.y/l) * up(l/2);
attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, shift=shift) {
multmatrix(skmat)
zrot(realign? 180/sides : 0) {
if (!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2])) {
if (texture != undef) {
_textured_cylinder(
l=l, r1=r1, r2=r2,
texture=texture, tex_size=tex_size,
counts=tex_counts, tex_scale=tex_scale,
inset=tex_inset, rot=tex_rot,
style=tex_style, taper=tex_taper,
chamfer=chamfer,
chamfer1=chamfer1,
chamfer2=chamfer2,
rounding=rounding,
rounding1=rounding1,
rounding2=rounding2,
convexity=10, samples=tex_samples
);
} else if (!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2])) {
cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides);
} else {
vang = atan2(l, r1-r2)/2;
chang1 = 90-first_defined([chamfang1, chamfang, vang]);
chang1 = 90-first_defined([chamfang1, chamfang, vang,]);
chang2 = 90-first_defined([chamfang2, chamfang, 90-vang]);
cham1 = u_mul(first_defined([chamfer1, chamfer]) , (from_end? 1 : tan(chang1)));
cham2 = u_mul(first_defined([chamfer2, chamfer]) , (from_end? 1 : tan(chang2)));
fil1 = first_defined([rounding1, rounding]);
fil2 = first_defined([rounding2, rounding]);
if (chamfer != undef) {
checks =
assert(chamfer <= r1, "chamfer is larger than the r1 radius of the cylinder.")
assert(chamfer <= r2, "chamfer is larger than the r2 radius of the cylinder.");
}
if (cham1 != undef) {
check = assert(cham1 <= r1, "chamfer1 is larger than the r1 radius of the cylinder.");
}
if (cham2 != undef) {
check = assert(cham2 <= r2, "chamfer2 is larger than the r2 radius of the cylinder.");
}
if (rounding != undef) {
checks =
assert(rounding <= r1, "rounding is larger than the r1 radius of the cylinder.")
assert(rounding <= r2, "rounding is larger than the r2 radius of the cylinder.");
}
if (fil1 != undef) {
check = assert(fil1 <= r1, "rounding1 is larger than the r1 radius of the cylinder.");
}
if (fil2 != undef) {
check = assert(fil2 <= r2, "rounding2 is larger than the r1 radius of the cylinder.");
}
dy1 = abs(first_defined([cham1, fil1, 0]));
dy2 = abs(first_defined([cham2, fil2, 0]));
check = assert(dy1+dy2 <= l, "Sum of fillets and chamfer sizes must be less than the length of the cylinder.");
path = concat(
[[0,l/2]],
!is_undef(cham2)? (
chamf = default(chamfer, 0) * (from_end? 1 : tan(chang1));
chamf1 = first_defined([chamfer1, chamfer, 0]) * (from_end? 1 : tan(chang1));
chamf2 = first_defined([chamfer2, chamfer, 0]) * (from_end? 1 : tan(chang2));
round = default(rounding, 0);
round1 = first_defined([rounding1, rounding, 0]);
round2 = first_defined([rounding2, rounding, 0]);
dy1 = abs(first_defined([chamf1, round1, 0]));
dy2 = abs(first_defined([chamf2, round2, 0]));
checks =
assert(chamf <= r1, "chamfer is larger than the r1 radius of the cylinder.")
assert(chamf <= r2, "chamfer is larger than the r2 radius of the cylinder.")
assert(chamf1 <= r1, "chamfer1 is larger than the r1 radius of the cylinder.")
assert(chamf2 <= r2, "chamfer2 is larger than the r2 radius of the cylinder.")
assert(round <= r1, "rounding is larger than the r1 radius of the cylinder.")
assert(round <= r2, "rounding is larger than the r2 radius of the cylinder.")
assert(round1 <= r1, "rounding1 is larger than the r1 radius of the cylinder.")
assert(round2 <= r2, "rounding2 is larger than the r1 radius of the cylinder.")
assert(dy1+dy2 <= l, "Sum of fillets and chamfer sizes must be less than the length of the cylinder.")
undef;
path = [
[0,-l/2],
if (is_finite(chamf1) && !approx(chamf1,0))
let(
p1 = [r2-cham2/tan(chang2),l/2],
p2 = lerp([r2,l/2],[r1,-l/2],abs(cham2)/l)
) [p1,p2]
) : !is_undef(fil2)? (
p1 = [r1-chamf1/tan(chang1),-l/2],
p2 = lerp([r1,-l/2],[r2,l/2],abs(chamf1)/l)
) each [p1,p2]
else if (is_finite(round1) && !approx(round1,0))
each arc(r=abs(round1), corner=[[(round1>0?0:1e6),-l/2],[r1,-l/2],[r2,l/2]])
else [r1,-l/2],
if (is_finite(chamf2) && !approx(chamf2,0))
let(
cn = circle_2tangents(abs(fil2), [r2-fil2,l/2], [r2,l/2], [r1,-l/2]),
ang = fil2<0? phi : phi-180,
steps = ceil(abs(ang)/360*segs(abs(fil2))),
step = ang/steps,
pts = [for (i=[0:1:steps]) let(a=90+i*step) cn[0]+abs(fil2)*[cos(a),sin(a)]]
) pts
) : [[r2,l/2]],
p1 = lerp([r2,l/2],[r1,-l/2],abs(chamf2)/l),
p2 = [r2-chamf2/tan(chang2),l/2]
) each [p1,p2]
else if (is_finite(round2) && !approx(round2,0))
each arc(r=abs(round2), corner=[[r1,-l/2],[r2,l/2],[(round2>0?0:1e6),l/2]])
else [r2,l/2],
[0,l/2]
];
!is_undef(cham1)? (
let(
p1 = lerp([r1,-l/2],[r2,l/2],abs(cham1)/l),
p2 = [r1-cham1/tan(chang1),-l/2]
) [p1,p2]
) : !is_undef(fil1)? (
let(
cn = circle_2tangents(abs(fil1), [r1-fil1,-l/2], [r1,-l/2], [r2,l/2]),
ang = fil1<0? 180-phi : -phi,
steps = ceil(abs(ang)/360*segs(abs(fil1))),
step = ang/steps,
pts = [for (i=[0:1:steps]) let(a=(fil1<0?180:0)+(phi-90)+i*step) cn[0]+abs(fil1)*[cos(a),sin(a)]]
) pts
) : [[r1,-l/2]],
[[0,-l/2]]
);
rotate_extrude(convexity=2) {
polygon(path);
}
rotate_extrude(convexity=2) polygon(path);
}
}
children();
@ -1378,6 +1543,7 @@ module cyl(
// xcyl(l=35, d=20);
// xcyl(l=35, d1=30, d2=10);
// }
module xcyl(
h, r, d, r1, r2, d1, d2, l,
chamfer, chamfer1, chamfer2,
@ -1448,6 +1614,7 @@ module xcyl(
// ycyl(l=35, d=20);
// ycyl(l=35, d1=30, d2=10);
// }
module ycyl(
h, r, d, r1, r2, d1, d2, l,
chamfer, chamfer1, chamfer2,
@ -1519,6 +1686,7 @@ module ycyl(
// zcyl(l=35, d=20);
// zcyl(l=35, d1=30, d2=10);
// }
module zcyl(
h, r, d, r1, r2, d1, d2, l,
chamfer, chamfer1, chamfer2,
@ -1596,6 +1764,7 @@ module zcyl(
// tube(h=30, or1=40, or2=30, ir1=20, ir2=30);
// Example: Standard Connectors
// tube(h=30, or=40, wall=5) show_anchors();
module tube(
h, or, ir, center,
od, id, wall,
@ -1673,6 +1842,7 @@ module tube(
// Example: Generating a VNF
// vnf = pie_slice(ang=150, l=20, r1=30, r2=50);
// vnf_polyhedron(vnf);
module pie_slice(
h, r, ang=30, center,
r1, r2, d, d1, d2, l,
@ -1696,7 +1866,6 @@ module pie_slice(
}
}
function pie_slice(
h, r, ang=30, center,
r1, r2, d, d1, d2, l,
@ -1774,6 +1943,7 @@ function pie_slice(
// Example: Called as Function
// vnf = sphere(d=100, style="icosa");
// vnf_polyhedron(vnf);
module sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP) {
r = get_radius(r=r, d=d, dflt=1);
if (!circum && style=="orig" && is_num(r)) {
@ -1789,7 +1959,6 @@ module sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP
}
}
function sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP) =
spheroid(r=r, d=d, circum=circum, style=style, anchor=anchor, spin=spin, orient=orient);
@ -1884,6 +2053,7 @@ function sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=
// Example: The dual of "icosa" features hexagons and always 12 pentagons:
// color("green")spheroid(r=10.01, $fn=256);
// spheroid(r=10, style="icosa", circum=true, $fn=16);
module spheroid(r, style="aligned", d, circum=false, dual=false, anchor=CENTER, spin=0, orient=UP)
{
r = get_radius(r=r, d=d, dflt=1);
@ -2194,6 +2364,7 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or
// vnf_polyhedron(torus(d_min=15, od=60), convexity=4);
// Example: Standard Connectors
// torus(od=60, id=30) show_anchors();
module torus(
r_maj, r_min, center,
d_maj, d_min,
@ -2314,6 +2485,7 @@ function torus(
// Example(Spin,VPD=150,Med): Named Conical Connectors
// teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16)
// show_anchors(std=false);
module teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, l, anchor=CENTER, spin=0, orient=UP)
{
r1 = get_radius(r=r, r1=r1, d=d, d1=d1, dflt=1);
@ -2432,6 +2604,7 @@ function teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, l, anc
// }
// Example: Standard Connectors
// onion(d=30, ang=30, cap_h=20) show_anchors();
module onion(r, ang=45, cap_h, d, anchor=CENTER, spin=0, orient=UP)
{
r = get_radius(r=r, d=d, dflt=1);
@ -2527,6 +2700,7 @@ function onion(r, ang=45, cap_h, d, anchor=CENTER, spin=0, orient=UP) =
// text3d("Foobar", h=2, anchor=CENTER);
// text3d("Foobar", h=2, anchor=str("baseline",CENTER));
// text3d("Foobar", h=2, anchor=str("baseline",BOTTOM+RIGHT));
module text3d(text, h=1, size=10, font="Helvetica", halign, valign, spacing=1.0, direction="ltr", language="em", script="latin", anchor="baseline[-1,0,-1]", spin=0, orient=UP) {
no_children($children);
dummy1 =
@ -2721,6 +2895,7 @@ function _cut_interp(pathcut, path, data) =
// color("red")stroke(path, width=.3);
// kern = [1,1.2,1,1,.3,-.2,1,0,.8,1,1.1,1];
// path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, kern=kern, normal=UP);
module path_text(path, text, font, size, thickness, lettersize, offset=0, reverse=false, normal, top, center=false, textmetrics=false, kern=0)
{
no_children($children);
@ -2849,6 +3024,7 @@ module path_text(path, text, font, size, thickness, lettersize, offset=0, revers
// position(BOT+FRONT)
// interior_fillet(l=50, r=10, spin=180, orient=RIGHT);
// }
module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, anchor=CENTER, spin=0, orient=UP) {
r = get_radius(r=r, d=d, dflt=1);
steps = ceil(segs(r)*(180-ang)/360);
@ -2896,7 +3072,7 @@ module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, anchor=CENTER, spin=0,
// 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. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards. See [orient](attachments.scad#subsection-orient). Default: `UP`
// See Also: heightfield(), cylindrical_heightfield(), textured_revolution(), textured_cylinder(), textured_linear_sweep()
// See Also: heightfield(), cylindrical_heightfield()
// Example:
// heightfield(size=[100,100], bottom=-20, data=[
// for (y=[-180:4:180]) [
@ -2923,6 +3099,7 @@ module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, anchor=CENTER, spin=0,
// size=[100,100], bottom=-20, data=fn,
// xrange=[-180:2:180], yrange=[-180:2:180]
// );
module heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04:1], yrange=[-1:0.04:1], style="default", convexity=10, anchor=CENTER, spin=0, orient=UP)
{
size = is_num(size)? [size,size] : point2d(size);
@ -3033,7 +3210,7 @@ function heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04
// 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. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards. See [orient](attachments.scad#subsection-orient). Default: `UP`
// See Also: heightfield(), cylindrical_heightfield(), textured_revolution(), textured_cylinder(), textured_linear_sweep()
// See Also: heightfield(), cylindrical_heightfield()
// Example(VPD=400;VPR=[55,0,150]):
// cylindrical_heightfield(l=100, r=30, base=5, data=[
// for (y=[-180:4:180]) [
@ -3057,6 +3234,7 @@ function heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04
// l=100, r=30, base=5, data=fn,
// xrange=[-180:2:180], yrange=[-180:2:180]
// );
function cylindrical_heightfield(
data, l, r, base=1,
transpose=false, aspect=1,
@ -3176,6 +3354,7 @@ module cylindrical_heightfield(
// Example(2D,Big): Metric vs Imperial
// ruler(12,width=50,inch=true,labels=true,maxscale=0);
// fwd(50)ruler(300,width=50,labels=true);
module ruler(length=100, width, thickness=1, depth=3, labels=false, pipscale=1/3, maxscale,
colors=["black","white"], alpha=1.0, unit=1, inch=false, anchor=LEFT+BACK+TOP, spin=0, orient=UP)
{

1345
skin.scad

File diff suppressed because it is too large Load diff

View file

@ -137,7 +137,7 @@ test_affine3d_skew();
module test_affine3d_skew_xy() {
for(ya = [-89:3:89]) {
for(xa = [-89:3:89]) {
assert(affine3d_skew_xy(xa=xa, ya=ya) == [[1,0,tan(xa),0],[0,1,tan(ya),0],[0,0,1,0],[0,0,0,1]]);
assert(affine3d_skew_xy(xa=xa, ya=ya) == [[1,tan(xa),0,0],[tan(ya),1,0,0],[0,0,1,0],[0,0,0,1]]);
}
}
}
@ -147,7 +147,7 @@ test_affine3d_skew_xy();
module test_affine3d_skew_xz() {
for(za = [-89:3:89]) {
for(xa = [-89:3:89]) {
assert(affine3d_skew_xz(xa=xa, za=za) == [[1,tan(xa),0,0],[0,1,0,0],[0,tan(za),1,0],[0,0,0,1]]);
assert(affine3d_skew_xz(xa=xa, za=za) == [[1,0,tan(xa),0],[0,1,0,0],[tan(za),0,1,0],[0,0,0,1]]);
}
}
}
@ -157,7 +157,7 @@ test_affine3d_skew_xz();
module test_affine3d_skew_yz() {
for(za = [-89:3:89]) {
for(ya = [-89:3:89]) {
assert(affine3d_skew_yz(ya=ya, za=za) == [[1,0,0,0],[tan(ya),1,0,0],[tan(za),0,1,0],[0,0,0,1]]);
assert(affine3d_skew_yz(ya=ya, za=za) == [[1,0,0,0],[0,1,tan(ya),0],[0,tan(za),1,0],[0,0,0,1]]);
}
}
}

View file

@ -53,8 +53,8 @@ test_arc();
module test_dashed_stroke() {
segs = dashed_stroke([[0,0],[10,0]], dashpat=[3,2], closed=false);
assert_equal(segs,[[[0,0],[3,0]], [[5,0],[8,0]]]);
segs = dashed_stroke([[0,0],[15,0]], dashpat=[3,2], closed=false);
assert_approx(segs,[[[0,0],[2.5,0]],[[4+1/6,0],[6+2/3,0]],[[8+1/3,0],[10+5/6,0]],[[12.5,0],[15,0]]]);
}
test_dashed_stroke();

View file

@ -13,49 +13,16 @@ module test_torx_diam() {
test_torx_diam();
module test_torx_inner_diam() {
assert_approx(_torx_inner_diam(10), 2.05);
assert_approx(_torx_inner_diam(15), 2.40);
assert_approx(_torx_inner_diam(20), 2.85);
assert_approx(_torx_inner_diam(25), 3.25);
assert_approx(_torx_inner_diam(30), 4.05);
assert_approx(_torx_inner_diam(40), 4.85);
}
test_torx_inner_diam();
module test_torx_depth() {
assert_approx(torx_depth(10), 3.56);
assert_approx(torx_depth(15), 3.81);
assert_approx(torx_depth(20), 4.07);
assert_approx(torx_depth(25), 4.45);
assert_approx(torx_depth(30), 4.95);
assert_approx(torx_depth(40), 5.59);
assert_approx(torx_depth(10), 3.61);
assert_approx(torx_depth(15), 3.86);
assert_approx(torx_depth(20), 4.12);
assert_approx(torx_depth(25), 4.50);
assert_approx(torx_depth(30), 5,00);
assert_approx(torx_depth(40), 5.64);
}
test_torx_depth();
module test_torx_tip_radius() {
assert_approx(_torx_tip_radius(10), 0.229);
assert_approx(_torx_tip_radius(15), 0.267);
assert_approx(_torx_tip_radius(20), 0.305);
assert_approx(_torx_tip_radius(25), 0.375);
assert_approx(_torx_tip_radius(30), 0.451);
assert_approx(_torx_tip_radius(40), 0.546);
}
test_torx_tip_radius();
module test_torx_rounding_radius() {
assert_approx(_torx_rounding_radius(10), 0.598);
assert_approx(_torx_rounding_radius(15), 0.716);
assert_approx(_torx_rounding_radius(20), 0.859);
assert_approx(_torx_rounding_radius(25), 0.920);
assert_approx(_torx_rounding_radius(30), 1.194);
assert_approx(_torx_rounding_radius(40), 1.428);
}
test_torx_rounding_radius();
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

255
vnf.scad
View file

@ -23,7 +23,7 @@
/// Constant: EMPTY_VNF
/// Description:
/// The empty VNF data structure. Equal to `[[],[]]`.
/// The empty VNF data structure. Equal to `[[],[]]`.
EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
@ -40,7 +40,7 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// 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
// chooses the locally convex/concave subdivision. Degenerate faces
// are not included in the output, but if this results in unused vertices they will still appear in the output.
// are not included in the output, but if this results in unused vertices they will still appear in the output.
// Arguments:
// points = A list of vertices to divide into columns and rows.
// ---
@ -128,7 +128,7 @@ function vnf_vertex_array(
row_wrap=false,
reverse=false,
style="default"
) =
) =
assert(!(any([caps,cap1,cap2]) && !col_wrap), "col_wrap must be true if caps are requested")
assert(!(any([caps,cap1,cap2]) && row_wrap), "Cannot combine caps with row_wrap")
assert(in_list(style,["default","alt","quincunx", "convex","concave", "min_edge"]))
@ -148,8 +148,8 @@ function vnf_vertex_array(
rowcnt = rows - (row_wrap?0:1),
verts = [
each pts,
if (style=="quincunx")
for (r = [0:1:rowcnt-1], c = [0:1:colcnt-1])
if (style=="quincunx")
for (r = [0:1:rowcnt-1], c = [0:1:colcnt-1])
let(
i1 = ((r+0)%rows)*cols + ((c+0)%cols),
i2 = ((r+1)%rows)*cols + ((c+0)%cols),
@ -169,41 +169,46 @@ function vnf_vertex_array(
i3 = ((r+1)%rows)*cols + ((c+1)%cols),
i4 = ((r+0)%rows)*cols + ((c+1)%cols),
faces =
style=="quincunx"?
style=="quincunx"?
let(i5 = pcnt + r*colcnt + c)
[[i1,i5,i2],[i2,i5,i3],[i3,i5,i4],[i4,i5,i1]]
: style=="alt"?
: style=="alt"?
[[i1,i4,i2],[i2,i4,i3]]
: style=="min_edge"?
let(
d42=norm(pts[i4]-pts[i2]),
d13=norm(pts[i1]-pts[i3]),
shortedge = d42<d13+EPSILON ? [[i1,i4,i2],[i2,i4,i3]]
: [[i1,i3,i2],[i1,i4,i3]]
shortedge = d42<d13+EPSILON
? [[i1,i4,i2],[i2,i4,i3]]
: [[i1,i3,i2],[i1,i4,i3]]
)
shortedge
: style=="convex"?
: style=="convex"?
let( // Find normal for 3 of the points. Is the other point above or below?
n = (reverse?-1:1)*cross(pts[i2]-pts[i1],pts[i3]-pts[i1]),
convexfaces = n==0 ? [[i1,i4,i3]]
: n*pts[i4] > n*pts[i1] ? [[i1,i4,i2],[i2,i4,i3]]
: [[i1,i3,i2],[i1,i4,i3]]
convexfaces = n==0
? [[i1,i4,i3]]
: n*pts[i4] > n*pts[i1]
? [[i1,i4,i2],[i2,i4,i3]]
: [[i1,i3,i2],[i1,i4,i3]]
)
convexfaces
: style=="concave"?
: style=="concave"?
let( // Find normal for 3 of the points. Is the other point above or below?
n = (reverse?-1:1)*cross(pts[i2]-pts[i1],pts[i3]-pts[i1]),
concavefaces = n==0 ? [[i1,i4,i3]]
: n*pts[i4] <= n*pts[i1] ? [[i1,i4,i2],[i2,i4,i3]]
: [[i1,i3,i2],[i1,i4,i3]]
concavefaces = n==0
? [[i1,i4,i3]]
: n*pts[i4] <= n*pts[i1]
? [[i1,i4,i2],[i2,i4,i3]]
: [[i1,i3,i2],[i1,i4,i3]]
)
concavefaces
: [[i1,i3,i2],[i1,i4,i3]],
// remove degenerate faces
// remove degenerate faces
culled_faces= [for(face=faces)
if (norm(verts[face[0]]-verts[face[1]])>EPSILON &&
norm(verts[face[1]]-verts[face[2]])>EPSILON &&
norm(verts[face[2]]-verts[face[0]])>EPSILON)
norm(verts[face[1]]-verts[face[2]])>EPSILON &&
norm(verts[face[2]]-verts[face[0]])>EPSILON)
face
],
rfaces = reverse? [for (face=culled_faces) reverse(face)] : culled_faces
@ -221,7 +226,7 @@ function vnf_vertex_array(
// Produces a vnf from an array of points where each row length can differ from the adjacent rows by up to 2 in length. This enables
// the construction of triangular VNF patches. The resulting VNF can be wrapped along the rows by setting `row_wrap` to true.
// You cannot wrap columns: if you need to do that you'll need to merge two VNF arrays that share edges. Degenerate faces
// are not included in the output, but if this results in unused vertices they will still appear in the output.
// are not included in the output, but if this results in unused vertices they will still appear in the output.
// Arguments:
// points = List of point lists for each row
// row_wrap = If true then add faces connecting the first row and last row. These rows must differ by at most 2 in length.
@ -256,7 +261,7 @@ function vnf_vertex_array(
// vnf = vnf_tri_array(pts);
// vnf_wireframe(vnf,width=0.1);
// color("red")move_copies(flatten(pts)) sphere(r=.15,$fn=9);
function vnf_tri_array(points, row_wrap=false, reverse=false) =
function vnf_tri_array(points, row_wrap=false, reverse=false) =
let(
lens = [for(row=points) len(row)],
rowstarts = [0,each cumsum(lens)],
@ -297,8 +302,8 @@ function vnf_tri_array(points, row_wrap=false, reverse=false) =
culled_faces=
[for(face=faces)
if (norm(verts[face[0]]-verts[face[1]])>EPSILON &&
norm(verts[face[1]]-verts[face[2]])>EPSILON &&
norm(verts[face[2]]-verts[face[0]])>EPSILON)
norm(verts[face[1]]-verts[face[2]])>EPSILON &&
norm(verts[face[2]]-verts[face[0]])>EPSILON)
face
]
)
@ -318,30 +323,30 @@ function vnf_tri_array(points, row_wrap=false, reverse=false) =
// .
// Note that this is a tool for manipulating polyhedron data. It is for
// building up a full polyhedron from partial polyhedra.
// It is *not* a union operator for VNFs. The VNFs to be joined must not intersect each other,
// It is *not* a union operator for VNFs. The VNFs to be joined must not intersect each other,
// except at edges, or the result will be an invalid polyhedron. Similarly the
// result must not have any other illegal polyhedron characteristics, such as creating
// more than two faces sharing the same edge.
// more than two faces sharing the same edge.
// If you want a valid result it is your responsibility to ensure that the polyhedron
// has no holes, no intersecting faces or edges, and obeys all the requirements
// that CGAL expects.
// that CGAL expects.
// .
// For example, if you combine two pyramids to try to make an octahedron, the result will
// be invalid because of the two internal faces created by the pyramid bases. A valid
// use would be to build a cube missing one face and a pyramid missing its base and
// then join them into a cube with a point.
// then join them into a cube with a point.
// Arguments:
// vnfs = a list of the VNFs to joint into one VNF.
// Example(3D,VPR=[60,0,26],VPD=55,VPT=[5.6,-5.3,9.8]): Here is a VNF where the top face is missing. It is not a valid polyhedron like this, but we can use it as a building block to make a polyhedron.
// Example(3D,VPR=[60,0,26],VPD=55,VPT=[5.6,-5.3,9.8]): Here is a VNF where the top face is missing. It is not a valid polyhedron like this, but we can use it as a building block to make a polyhedron.
// bottom = vnf_vertex_array([path3d(rect(8)), path3d(rect(5),4)],col_wrap=true,cap1=true);
// vnf_polyhedron(bottom);
// Example(3D,VPR=[60,0,26],VPD=55,VPT=[5.6,-5.3,9.8]): Here is a VNF that also has a missing face.
// Example(3D,VPR=[60,0,26],VPD=55,VPT=[5.6,-5.3,9.8]): Here is a VNF that also has a missing face.
// triangle = yrot(-90,path3d(regular_ngon(n=3,side=5,anchor=LEFT)));
// top = up(4,vnf_vertex_array([list_set(right(2.5,triangle),0,[0,0,7]),
// right(6,triangle)
// ], col_wrap=true, cap2=true));
// vnf_polyhedron(zrot(90,top));
// Example(3D,VPR=[60,0,26],VPD=55,VPT=[5.6,-5.3,9.8]): Using vnf_join combines the two VNFs into a single VNF. Note that they share an edge. But the result still isn't closed, so it is not yet a valid polyhedron.
// Example(3D,VPR=[60,0,26],VPD=55,VPT=[5.6,-5.3,9.8]): Using vnf_join combines the two VNFs into a single VNF. Note that they share an edge. But the result still isn't closed, so it is not yet a valid polyhedron.
// bottom = vnf_vertex_array([path3d(rect(8)), path3d(rect(5),4)],col_wrap=true,cap1=true);
// triangle = yrot(-90,path3d(regular_ngon(n=3,side=5,anchor=LEFT)));
// top = up(4,vnf_vertex_array([list_set(right(2.5,triangle),0,[0,0,7]),
@ -359,7 +364,7 @@ function vnf_tri_array(points, row_wrap=false, reverse=false) =
// for(theta=[0:90:359]) zrot(theta,top)
// ]);
// vnf_polyhedron(full);
// Example(3D): The vnf_join function is not a union operator for polyhedra. If any faces intersect, like they do in this example where we combine the faces of two cubes, the result is invalid and will give rise to CGAL errors when you add more objects into the model.
// Example(3D): The vnf_join function is not a union operator for polyhedra. If any faces intersect, like they do in this example where we combine the faces of two cubes, the result is invalid and will give rise to CGAL errors when you add more objects into the model.
// cube1 = cube(5);
// cube2 = move([2,2,2],cube1);
// badvnf = vnf_join([cube1,cube2]);
@ -367,7 +372,7 @@ function vnf_tri_array(points, row_wrap=false, reverse=false) =
// right(2.5)up(3)color("red")
// text3d("Invalid",size=1,anchor=CENTER,
// orient=FRONT,h=.1);
function vnf_join(vnfs) =
function vnf_join(vnfs) =
assert(is_vnf_list(vnfs) , "Input must be a list of VNFs")
len(vnfs)==1 ? vnfs[0]
:
@ -375,12 +380,12 @@ function vnf_join(vnfs) =
offs = cumsum([ 0, for (vnf = vnfs) len(vnf[0]) ]),
verts = [for (vnf=vnfs) each vnf[0]],
faces =
[ for (i = idx(vnfs))
[ for (i = idx(vnfs))
let( faces = vnfs[i][1] )
for (face = faces)
for (face = faces)
if ( len(face) >= 3 )
[ for (j = face)
assert( j>=0 && j<len(vnfs[i][0]),
[ for (j = face)
assert( j>=0 && j<len(vnfs[i][0]),
str("VNF number ", i, " has a face indexing an nonexistent vertex") )
offs[i] + j ]
]
@ -393,7 +398,7 @@ function vnf_join(vnfs) =
// Usage:
// vnf = vnf_from_polygons(polygons);
// Description:
// Given a list of 3d polygons, produces a VNF containing those polygons.
// Given a list of 3d polygons, produces a VNF containing those polygons.
// It is up to the caller to make sure that the points are in the correct order to make the face
// normals point outwards. No checking for duplicate vertices is done. If you want to
// remove duplicate vertices use {{vnf_merge_points()}}.
@ -431,31 +436,31 @@ function _join_paths_at_vertices(path1,path2,v1,v2) =
if (repeat_start) path1[0],
each path2,
if (repeat_start) path2[0],
];
];
/// Internal Function: _cleave_connected_region(region, eps)
/// Description:
/// Given a region that is connected and has its outer border in region[0],
/// produces a overlapping connected path to join internal holes to
/// the outer border without adding points. Output is a single non-simple polygon.
/// produces a overlapping connected path to join internal holes to
/// the outer border without adding points. Output is a single non-simple polygon.
/// Requirements:
/// It expects that all region paths be simple closed paths, with region[0] CW and
/// the other paths CCW and encircled by region[0]. The input region paths are also
/// supposed to be disjoint except for common vertices and common edges but with
/// It expects that all region paths be simple closed paths, with region[0] CW and
/// the other paths CCW and encircled by region[0]. The input region paths are also
/// supposed to be disjoint except for common vertices and common edges but with
/// no crossings. It may return `undef` if these conditions are not met.
/// This function implements an extension of the algorithm discussed in:
/// This function implements an extension of the algorithm discussed in:
/// https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
function _cleave_connected_region(region, eps=EPSILON) =
len(region)==1 ? region[0] :
let(
outer = deduplicate(region[0]), //
let(
outer = deduplicate(region[0]), //
holes = [for(i=[1:1:len(region)-1]) // deduplication possibly unneeded
deduplicate( region[i] ) ], //
extridx = [for(li=holes) max_index(column(li,0)) ],
// the right extreme vertex for each hole sorted by decreasing x values
extremes = sort( [for(i=idx(holes)) [ i, extridx[i], -holes[i][extridx[i]].x] ], idx=2 )
)
)
_polyHoles(outer, holes, extremes, eps, 0);
@ -463,8 +468,8 @@ function _cleave_connected_region(region, eps=EPSILON) =
// 'extremes' is the list of the right extreme vertex of each hole sorted by decreasing abscissas
// see: _cleave_connected_region(region, eps)
function _polyHoles(outer, holes, extremes, eps=EPSILON, n=0) =
let(
extr = extremes[n], //
let(
extr = extremes[n], //
hole = holes[extr[0]], // hole path to bridge to the outer path
ipt = extr[1], // index of the hole point with maximum abscissa
brdg = _bridge(hole[ipt], outer, eps) // the index of a point in outer to bridge hole[ipt] to
@ -475,32 +480,32 @@ function _polyHoles(outer, holes, extremes, eps=EPSILON, n=0) =
lh = len(hole),
// the new outer polygon bridging the hole to the old outer
npoly =
approx(outer[brdg], hole[ipt], eps)
approx(outer[brdg], hole[ipt], eps)
? [ for(i=[brdg: 1: brdg+l]) outer[i%l] ,
for(i=[ipt+1: 1: ipt+lh-1]) hole[i%lh] ]
: [ for(i=[brdg: 1: brdg+l]) outer[i%l] ,
for(i=[ipt: 1: ipt+lh]) hole[i%lh] ]
)
n==len(holes)-1 ? npoly :
_polyHoles(npoly, holes, extremes, eps, n+1);
// find a point in outer to be connected to pt in the interior of outer
n==len(holes)-1 ? npoly :
_polyHoles(npoly, holes, extremes, eps, n+1);
// find a point in outer to be connected to pt in the interior of outer
// by a segment that not cross or touch any non adjacente edge of outer.
// return the index of a vertex in the outer path where the bridge should end
// see _polyHoles(outer, holes, extremes, eps)
function _bridge(pt, outer,eps) =
// find the intersection of a ray from pt to the right
// find the intersection of a ray from pt to the right
// with the boundary of the outer cycle
let(
let(
l = len(outer),
crxs =
crxs =
let( edges = pair(outer,wrap=true) )
[for( i = idx(edges) )
let( edge = edges[i] )
// consider just descending outer edges at right of pt crossing ordinate pt.y
if( (edge[0].y > pt.y) //+eps)
&& (edge[1].y <= pt.y)
&& _is_at_left(pt, [edge[1], edge[0]], eps) )
if( (edge[0].y > pt.y) //+eps)
&& (edge[1].y <= pt.y)
&& _is_at_left(pt, [edge[1], edge[0]], eps) )
[ i,
// the point of edge with ordinate pt.y
abs(pt.y-edge[1].y)<eps ? edge[1] :
@ -510,11 +515,11 @@ function _bridge(pt, outer,eps) =
]
)
crxs == [] ? undef :
let(
let(
// the intersection point of the nearest edge to pt with minimum slope
minX = min([for(p=crxs) p[1].x]),
crxcand = [for(crx=crxs) if(crx[1].x < minX+eps) crx ], // nearest edges
nearest = min_index([for(crx=crxcand)
nearest = min_index([for(crx=crxcand)
(outer[crx[0]].x - pt.x) / (outer[crx[0]].y - pt.y) ]), // minimum slope
proj = crxcand[nearest],
vert0 = outer[proj[0]], // the two vertices of the nearest crossing edge
@ -522,33 +527,33 @@ function _bridge(pt, outer,eps) =
isect = proj[1] // the intersection point
)
norm(pt-vert1) < eps ? (proj[0]+1)%l : // if pt touches an outer vertex, return its index
// as vert0.y > pt.y then pt!=vert0
// as vert0.y > pt.y then pt!=vert0
norm(pt-isect) < eps ? undef : // if pt touches the middle of an outer edge -> error
let(
let(
// the edge [vert0, vert1] necessarily satisfies vert0.y > vert1.y
// indices of candidates to an outer bridge point
cand =
(vert0.x > pt.x)
? [ proj[0],
cand =
(vert0.x > pt.x)
? [ proj[0],
// select reflex vertices inside of the triangle [pt, vert0, isect]
for(i=idx(outer))
if( _tri_class(select(outer,i-1,i+1),eps) <= 0
for(i=idx(outer))
if( _tri_class(select(outer,i-1,i+1),eps) <= 0
&& _pt_in_tri(outer[i], [pt, vert0, isect], eps)>=0 )
i
i
]
: [ (proj[0]+1)%l,
// select reflex vertices inside of the triangle [pt, isect, vert1]
for(i=idx(outer))
if( _tri_class(select(outer,i-1,i+1),eps) <= 0
// select reflex vertices inside of the triangle [pt, isect, vert1]
for(i=idx(outer))
if( _tri_class(select(outer,i-1,i+1),eps) <= 0
&& _pt_in_tri(outer[i], [pt, isect, vert1], eps)>=0 )
i
i
],
// choose the candidate outer[i] such that the line [pt, outer[i]] has minimum slope
// among those with minimum slope choose the nearest to pt
slopes = [for(i=cand) 1-abs(outer[i].x-pt.x)/norm(outer[i]-pt) ],
min_slp = min(slopes),
cand2 = [for(i=idx(cand)) if(slopes[i]<=min_slp+eps) cand[i] ],
nearest = min_index([for(i=cand2) norm(pt-outer[i]) ])
nearest = min_index([for(i=cand2) norm(pt-outer[i]) ])
)
cand2[nearest];
@ -558,7 +563,7 @@ function _bridge(pt, outer,eps) =
// vnf = vnf_from_region(region, [transform], [reverse]);
// Description:
// Given a (two-dimensional) region, applies the given transformation matrix to it and makes a (three-dimensional) triangulated VNF of
// faces for that region, reversed if desired.
// faces for that region, reversed if desired.
// Arguments:
// region = The region to conver to a vnf.
// transform = If given, a transformation matrix to apply to the faces generated from the region. Default: No transformation applied.
@ -573,10 +578,14 @@ function _bridge(pt, outer,eps) =
// vnf_wireframe(vnf,width=.25);
function vnf_from_region(region, transform, reverse=false) =
let (
region = [for (path = region) deduplicate(path, closed=true)],
regions = region_parts(force_region(region)),
vnfs =
[ for (rgn = regions)
let( cleaved = path3d(_cleave_connected_region(rgn)) )
[
for (rgn = regions)
let(
cleaved = path3d(_cleave_connected_region(rgn))
)
assert( cleaved, "The region is invalid")
let(
face = is_undef(transform)? cleaved : apply(transform,cleaved),
@ -653,29 +662,29 @@ function vnf_quantize(vnf,q=pow(2,-12)) =
// Description:
// Given a VNF, consolidates all duplicate vertices with a tolerance `eps`, relabeling the faces as necessary,
// and eliminating any face with fewer than 3 vertices. Unreferenced vertices of the input VNF are not dropped.
// To remove such vertices uses {{vnf_drop_unused_points()}}.
// To remove such vertices uses {{vnf_drop_unused_points()}}.
// Arguments:
// vnf = a VNF to consolidate
// eps = the tolerance in finding duplicates. Default: EPSILON
function vnf_merge_points(vnf,eps=EPSILON) =
function vnf_merge_points(vnf,eps=EPSILON) =
let(
verts = vnf[0],
verts = vnf[0],
dedup = vector_search(verts,eps,verts), // collect vertex duplicates
map = [for(i=idx(verts)) min(dedup[i]) ], // remap duplic vertices
offset = cumsum([for(i=idx(verts)) map[i]==i ? 0 : 1 ]), // remaping face vertex offsets
offset = cumsum([for(i=idx(verts)) map[i]==i ? 0 : 1 ]), // remaping face vertex offsets
map2 = list(idx(verts))-offset, // map old vertex indices to new indices
nverts = [for(i=idx(verts)) if(map[i]==i) verts[i] ], // this doesn't eliminate unreferenced vertices
nfaces =
[ for(face=vnf[1])
nfaces =
[ for(face=vnf[1])
let(
nface = [ for(vi=face) map2[map[vi]] ],
dface = [for (i=idx(nface))
if( nface[i]!=nface[(i+1)%len(nface)])
nface[i] ]
dface = [for (i=idx(nface))
if( nface[i]!=nface[(i+1)%len(nface)])
nface[i] ]
)
if(len(dface) >= 3) dface
if(len(dface) >= 3) dface
]
)
)
[nverts, nfaces];
@ -690,20 +699,20 @@ function vnf_drop_unused_points(vnf) =
let(
flat = flatten(vnf[1]),
ind = _link_indicator(flat,0,len(vnf[0])-1),
verts = [for(i=idx(vnf[0])) if(ind[i]==1) vnf[0][i] ],
map = cumsum(ind)
verts = [for(i=idx(vnf[0])) if(ind[i]==1) vnf[0][i] ],
map = cumsum(ind)
)
[ verts, [for(face=vnf[1]) [for(v=face) map[v]-1 ] ] ];
function _link_indicator(l,imin,imax) =
len(l) == 0 ? repeat(imax-imin+1,0) :
imax-imin<100 || len(l)<400 ? [for(si=search(list([imin:1:imax]),l,1)) si!=[] ? 1: 0 ] :
let(
imax-imin<100 || len(l)<400 ? [for(si=search(list([imin:1:imax]),l,1)) si!=[] ? 1: 0 ] :
let(
pivot = floor((imax+imin)/2),
lesser = [ for(li=l) if( li< pivot) li ],
greater = [ for(li=l) if( li> pivot) li ]
greater = [ for(li=l) if( li> pivot) li ]
)
concat( _link_indicator(lesser ,imin,pivot-1),
concat( _link_indicator(lesser ,imin,pivot-1),
search(pivot,l,1) ? 1 : 0 ,
_link_indicator(greater,pivot+1,imax) ) ;
@ -723,13 +732,13 @@ function _link_indicator(l,imin,imax) =
function vnf_triangulate(vnf) =
let(
verts = vnf[0],
faces = [for (face=vnf[1])
each (len(face)==3 ? [face] :
faces = [for (face=vnf[1])
each (len(face)==3 ? [face] :
let( tris = polygon_triangulate(verts, face) )
assert( tris!=undef, "Some `vnf` face cannot be triangulated.")
tris ) ]
)
[verts, faces];
)
[verts, faces];
@ -817,7 +826,7 @@ function _split_2dpolygons_at_each_x(polys, xs, _i=0) =
/// Topics: Geometry, Polygons, Intersections
/// Description:
/// Given a list of 3D polygons, a choice of X, Y, or Z, and a cut list, `cuts`, splits all of the polygons where they cross
/// X/Y/Z at any value given in cuts.
/// X/Y/Z at any value given in cuts.
/// Arguments:
/// polys = A list of 3D polygons to split.
/// dir_ind = slice direction, 0=X, 1=Y, or 2=Z
@ -877,6 +886,11 @@ function _slice_3dpolygons(polys, dir, cuts) =
// 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`
// atype = Select "hull" or "intersect" anchor type. Default: "hull"
// Anchor Types:
// "hull" = Anchors to the virtual convex hull of the shape.
// "intersect" = Anchors to the surface of the shape.
// Extra Anchors:
// "origin" = Anchor at the origin, oriented UP.
module vnf_polyhedron(vnf, convexity=2, extent=true, cp="centroid", anchor="origin", spin=0, orient=UP, atype="hull") {
vnf = is_vnf_list(vnf)? vnf_join(vnf) : vnf;
assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\"");
@ -893,7 +907,7 @@ module vnf_polyhedron(vnf, convexity=2, extent=true, cp="centroid", anchor="orig
// Description:
// Given a VNF, creates a wire frame ball-and-stick model of the polyhedron with a cylinder for
// each edge and a sphere at each vertex. The width parameter specifies the width of the sticks
// that form the wire frame and the diameter of the balls.
// that form the wire frame and the diameter of the balls.
// Arguments:
// vnf = A vnf structure
// width = width of the cylinders forming the wire frame. Default: 1
@ -922,7 +936,7 @@ module vnf_wireframe(vnf, width=1)
for (e=edges) extrude_from_to(vertex[e[0]],vertex[e[1]]) circle(d=width);
// Identify vertices actually used and draw them
vertused = search(count(len(vertex)), flatten(edges), 1);
for(i=idx(vertex)) if(vertused[i]!=[]) move(vertex[i]) sphere(d=width);
for(i=idx(vertex)) if(vertused[i]!=[]) move(vertex[i]) sphere(d=width);
}
@ -949,7 +963,7 @@ function vnf_volume(vnf) =
// Usage:
// area = vnf_area(vnf);
// Description:
// Returns the surface area in any VNF by adding up the area of all its faces. The VNF need not be a manifold.
// Returns the surface area in any VNF by adding up the area of all its faces. The VNF need not be a manifold.
function vnf_area(vnf) =
let(verts=vnf[0])
sum([for(face=vnf[1]) polygon_area(select(verts,face))]);
@ -962,11 +976,11 @@ function vnf_area(vnf) =
/// Returns the centroid of the given manifold VNF. The VNF must describe a valid polyhedron with consistent face direction and
/// no holes; otherwise the results are undefined.
/// Divide the solid up into tetrahedra with the origin as one vertex.
/// Divide the solid up into tetrahedra with the origin as one vertex.
/// The centroid of a tetrahedron is the average of its vertices.
/// The centroid of the total is the volume weighted average.
function _vnf_centroid(vnf,eps=EPSILON) =
assert(is_vnf(vnf) && len(vnf[0])!=0 && len(vnf[1])!=0,"Invalid or empty VNF given to centroid")
assert(is_vnf(vnf) && len(vnf[0])!=0 && len(vnf[1])!=0,"Invalid or empty VNF given to centroid")
let(
verts = vnf[0],
pos = sum([
@ -990,7 +1004,7 @@ function _vnf_centroid(vnf,eps=EPSILON) =
// Returns the intersection of the vnf with a half space. The half space is defined by
// plane = [A,B,C,D], taking the side where the normal [A,B,C] points: Ax+By+CzD.
// If closed is set to false then the cut face is not included in the vnf. This could
// allow further extension of the vnf by merging with other vnfs.
// allow further extension of the vnf by merging with other vnfs.
// Arguments:
// plane = plane defining the boundary of the half space
// vnf = vnf to cut
@ -1010,11 +1024,11 @@ function _vnf_centroid(vnf,eps=EPSILON) =
// cutvnf = vnf_halfspace([0,0.7,-4,0], vnf);
// vnf_polyhedron(cutvnf);
// Example(3D): Cut object has multiple components
// function knot(a,b,t) = // rolling knot
// [ a * cos (3 * t) / (1 - b* sin (2 *t)),
// a * sin( 3 * t) / (1 - b* sin (2 *t)),
// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))];
// a = 0.8; b = sqrt (1 - a * a);
// function knot(a,b,t) = // rolling knot
// [ a * cos (3 * t) / (1 - b* sin (2 *t)),
// a * sin( 3 * t) / (1 - b* sin (2 *t)),
// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))];
// a = 0.8; b = sqrt (1 - a * a);
// ksteps = 400;
// knot_path = [for (i=[0:ksteps-1]) 50 * knot(a,b,(i/ksteps)*360)];
// ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]];
@ -1037,7 +1051,7 @@ function vnf_halfspace(plane, vnf, closed=true) =
else assert(approx(p[0],p[1]),"Orphan edge found when assembling cut edges.")
]
)
len(newpaths)<=1 ? [newvert, concat(faces_edges_vertices[0], newpaths)]
len(newpaths)<=1 ? [newvert, concat(faces_edges_vertices[0], newpaths)]
:
let(
M = project_plane(plane),
@ -1055,9 +1069,9 @@ function _assemble_paths(vertices, edges, paths=[],i=0) =
right = [for(j=idx(paths)) if (approx(vertices[edges[i][1]],vertices[paths[j][0]])) j]
)
assert(len(left)<=1 && len(right)<=1)
let(
let(
keep_path = list_remove(paths,concat(left,right)),
update_path = left==[] && right==[] ? edges[i]
update_path = left==[] && right==[] ? edges[i]
: left==[] ? concat([edges[i][0]],paths[right[0]])
: right==[] ? concat(paths[left[0]],[edges[i][1]])
: left != right ? concat(paths[left[0]], paths[right[0]])
@ -1092,7 +1106,7 @@ function _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount, newfaces=
concat(newfaces, [list_head(newface)]), newedges,concat(newvertices,[newvert[0]]),i+1)
:
_vnfcut(plane, vertices, vertexmap, inside, faces, vertcount,newfaces, newedges, newvert, i+1);
@ -1226,7 +1240,6 @@ function vnf_bend(vnf,r,d,axis="Z") =
axis=="X"? [p.x, p.z*sin(a), p.z*cos(a)] :
axis=="Y"? [p.z*sin(a), p.y, p.z*cos(a)] :
[p.y*sin(a), p.y*cos(a), p.z]]
) [new_vert,sliced[1]];
@ -1335,7 +1348,7 @@ module _show_faces(vertices, faces, size=1, filter) {
// in red, aligned with the center of face. All given faces are drawn with
// transparency. All children of this module are drawn with transparency.
// Works best with Thrown-Together preview mode, to see reversed faces.
// You can set opacity to 0 if you want to supress the display of the polyhedron faces.
// You can set opacity to 0 if you want to supress the display of the polyhedron faces.
// .
// The vertex numbers are shown rotated to face you. As you rotate your polyhedron you
// can rerun the preview to display them oriented for viewing from a different viewpoint.
@ -1690,9 +1703,9 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false, opacity=0.
color(clr) {
if (is_vector(pts[0])) {
if (len(pts)==2) {
stroke(pts, width=size, closed=true, endcaps="butt", hull=false, $fn=8);
stroke(pts, width=size, closed=true, endcaps="butt", $fn=8);
} else if (len(pts)>2) {
stroke(pts, width=size, closed=true, hull=false, $fn=8);
stroke(pts, width=size, closed=true, $fn=8);
polyhedron(pts,[[for (i=idx(pts)) i]]);
} else {
move_copies(pts) sphere(d=size*3, $fn=18);