This commit is contained in:
RonaldoCMP 2021-06-21 20:15:00 +01:00
commit c010626bc9
40 changed files with 1978 additions and 930 deletions

View file

@ -1,12 +1,13 @@
name: CI
name: Docs
on:
push:
branches:
- master
pull_request:
branches: [master]
types: [closed]
jobs:
GenerateDocs:
runs-on: macos-10.15
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
@ -17,28 +18,32 @@ jobs:
repository: revarbat/BOSL2.wiki
path: BOSL2.wiki
- name: Install gifsicle
run: brew install gifsicle
- name: Apt Update
run: sudo apt update
- name: Install Pillow
run: sudo pip3 install Pillow
- name: Install Packages
run: sudo apt-get install -y python3-pip python3-dev python3-setuptools python3-pil gifsicle
- name: Install Docsgen
- name: Install openscad-docsgen
run: sudo pip3 install openscad_docsgen
- name: Install OpenSCAD
run: |
curl -L -o OpenSCAD.dmg https://files.openscad.org/snapshots/OpenSCAD-2021.05.07.dmg
hdiutil attach OpenSCAD.dmg
cp -a /Volumes/OpenSCAD/OpenSCAD.app /Applications/
- name: Generating Docs
run: |
cd $GITHUB_WORKSPACE
export OPENSCADPATH=$(dirname $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
echo "::add-matcher::.github/openscad_docsgen.json"
openscad-docsgen -m -i -t -c -I *.scad
cd BOSL2.wiki
- name: Generating Docs Headless
run: |
export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE)
xvfb-run --server-args="-screen 0, 1280x720x24" -a \
openscad-docsgen -ticmI *.scad
- name: Commit Wiki Docs
run: |
cd $GITHUB_WORKSPACE/BOSL2.wiki
git config user.name github-actions
git config user.email github-actions@github.com
git add --all

49
.github/workflows/forced_docsgen.yml vendored Normal file
View file

@ -0,0 +1,49 @@
name: FDocs
on:
workflow_dispatch:
branches: [master]
jobs:
GenerateDocs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Checkout Wiki
uses: actions/checkout@v2
with:
repository: revarbat/BOSL2.wiki
path: BOSL2.wiki
- name: Apt Update
run: sudo apt update
- name: Install Packages
run: sudo apt-get install -y python3-pip python3-dev python3-setuptools python3-pil gifsicle
- name: Install openscad-docsgen
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
echo "::add-matcher::.github/openscad_docsgen.json"
- name: Generating Docs Headless
run: |
export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE)
xvfb-run --server-args="-screen 0, 1280x720x24" -a \
openscad-docsgen -ticmIf *.scad
- name: Commit Wiki Docs
run: |
cd $GITHUB_WORKSPACE/BOSL2.wiki
git config user.name github-actions
git config user.email github-actions@github.com
git add --all
git commit -m "Wiki docs auto-regen." && git push || true

49
.github/workflows/testwf.yml vendored Normal file
View file

@ -0,0 +1,49 @@
name: TestWorkflow
on:
workflow_dispatch:
branches: [master]
jobs:
TestJob:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Checkout Wiki
uses: actions/checkout@v2
with:
repository: revarbat/BOSL2.wiki
path: BOSL2.wiki
- name: Setup OpenGL
uses: openrndr/setup-opengl@v1.1
- name: Apt Update
run: sudo apt update
- name: Install Packages
run: sudo apt-get install -y python3-pip python3-dev python3-setuptools python3-pil gifsicle
- name: Install openscad-docsgen
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
echo "::add-matcher::.github/openscad_docsgen.json"
- name: Make SCAD File
run: |
cd $GITHUB_WORKSPACE
echo "cube(50, center=100);" > testwf.scad
- name: TestScript
run: |
export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE)
xvfb-run --server-args="-screen 0, 1280x720x24" -a \
glxinfo

View file

@ -46,4 +46,5 @@ PrioritizeFiles:
DefineHeader(BulletList): Side Effects
DefineHeader(Table:Anchor Name|Position): Extra Anchors
DefineHeader(Table:Name|Definition): Terminology
DefineHeader(BulletList): Requirements

View file

@ -328,8 +328,11 @@ function attach_geom_size(geom) =
[2*maxxr, 2*maxyr,l]
) : type == "spheroid"? ( //r
let( r=geom[1] )
is_num(r)? [2,2,2]*r : vmul([2,2,2],point3d(r))
is_num(r)? [2,2,2]*r : v_mul([2,2,2],point3d(r))
) : type == "vnf_extent" || type=="vnf_isect"? ( //vnf
let(
vnf = geom[1]
) vnf==EMPTY_VNF? [0,0,0] :
let(
mm = pointlist_bounds(geom[1][0]),
delt = mm[1]-mm[0]
@ -341,7 +344,7 @@ function attach_geom_size(geom) =
) [maxx, size.y]
) : type == "circle"? ( //r
let( r=geom[1] )
is_num(r)? [2,2]*r : vmul([2,2],point2d(r))
is_num(r)? [2,2]*r : v_mul([2,2],point2d(r))
) : type == "path_isect" || type == "path_extent"? ( //path
let(
mm = pointlist_bounds(geom[1]),
@ -425,7 +428,7 @@ function attach_transform(anchor, spin, orient, geom, p) =
)
)
) is_undef(p)? m :
is_vnf(p)? [apply(m, p[0]), p[1]] :
is_vnf(p)? [(p==EMPTY_VNF? p : apply(m, p[0])), p[1]] :
apply(m, p);
@ -445,7 +448,8 @@ function attach_transform(anchor, spin, orient, geom, p) =
function find_anchor(anchor, geom) =
let(
cp = select(geom,-3),
offset = anchor==CENTER? CENTER : select(geom,-2),
offset_raw = select(geom,-2),
offset = [for (i=[0:2]) anchor[i]==0? 0 : offset_raw[i]], // prevents bad centering.
anchors = last(geom),
type = geom[0]
)
@ -472,8 +476,8 @@ function find_anchor(anchor, geom) =
h = size.z,
u = (anch.z+1)/2,
axy = point2d(anch),
bot = point3d(vmul(point2d(size)/2,axy),-h/2),
top = point3d(vmul(point2d(size2)/2,axy)+shift,h/2),
bot = point3d(v_mul(point2d(size)/2,axy),-h/2),
top = point3d(v_mul(point2d(size2)/2,axy)+shift,h/2),
pos = point3d(cp) + lerp(bot,top,u) + offset,
sidevec = unit(rot(from=UP, to=top-bot, p=point3d(axy)),UP),
vvec = anch==CENTER? UP : unit([0,0,anch.z],UP),
@ -493,8 +497,8 @@ function find_anchor(anchor, geom) =
anch = rot(from=axis, to=UP, p=anchor),
u = (anch.z+1)/2,
axy = unit(point2d(anch),[0,0]),
bot = point3d(vmul(r1,axy), -l/2),
top = point3d(vmul(r2,axy)+shift, l/2),
bot = point3d(v_mul(r1,axy), -l/2),
top = point3d(v_mul(r2,axy)+shift, l/2),
pos = point3d(cp) + lerp(bot,top,u) + offset,
sidevec = rot(from=UP, to=top-bot, p=point3d(axy)),
vvec = anch==CENTER? UP : unit([0,0,anch.z],UP),
@ -510,12 +514,14 @@ function find_anchor(anchor, geom) =
rr = geom[1],
r = is_num(rr)? [rr,rr,rr] : point3d(rr),
anchor = unit(point3d(anchor),CENTER),
pos = point3d(cp) + vmul(r,anchor) + point3d(offset),
vec = unit(vmul(r,anchor),UP)
pos = point3d(cp) + v_mul(r,anchor) + point3d(offset),
vec = unit(v_mul(r,anchor),UP)
) [anchor, pos, vec, oang]
) : type == "vnf_isect"? ( //vnf
let(
vnf=geom[1],
vnf=geom[1]
) vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor), 0] :
let(
eps = 1/2048,
points = vnf[0],
faces = vnf[1],
@ -565,7 +571,9 @@ function find_anchor(anchor, geom) =
[anchor, pos, n, oang]
) : type == "vnf_extent"? ( //vnf
let(
vnf=geom[1],
vnf=geom[1]
) vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor), 0] :
let(
rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]),
maxx = max(subindex(rpts,0)),
idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i],
@ -589,8 +597,8 @@ function find_anchor(anchor, geom) =
rr = geom[1],
r = is_num(rr)? [rr,rr] : point2d(rr),
anchor = unit(point2d(anchor),[0,0]),
pos = point2d(cp) + vmul(r,anchor) + point2d(offset),
vec = unit(vmul(r,anchor),[0,1])
pos = point2d(cp) + v_mul(r,anchor) + point2d(offset),
vec = unit(v_mul(r,anchor),[0,1])
) [anchor, pos, vec, 0]
) : type == "path_isect"? ( //path
let(
@ -986,6 +994,92 @@ module attachable(
}
// Module: atext()
// Topics: Attachments, Text
// Usage:
// atext(text, <h>, <size>, <font>);
// Description:
// Creates a 3D text block that can be attached to other attachable objects.
// NOTE: This cannot have children attached to it.
// Arguments:
// text = The text string to instantiate as an object.
// h = The height to which the text should be extruded. Default: 1
// size = The font size used to create the text block. Default: 10
// font = The name of the font used to create the text block. Default: "Courier"
// ---
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `"baseline"`
// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards. See [orient](attachments.scad#orient). Default: `UP`
// See Also: attachable()
// Extra Anchors:
// "baseline" = Anchors at the baseline of the text, at the start of the string.
// str("baseline",VECTOR) = Anchors at the baseline of the text, modified by the X and Z components of the appended vector.
// Examples:
// atext("Foobar", h=3, size=10);
// atext("Foobar", h=2, size=12, font="Helvetica");
// atext("Foobar", h=2, anchor=CENTER);
// atext("Foobar", h=2, anchor=str("baseline",CENTER));
// atext("Foobar", h=2, anchor=str("baseline",BOTTOM+RIGHT));
// Example: Using line_of() distributor
// txt = "This is the string.";
// line_of(spacing=[10,-5],n=len(txt))
// atext(txt[$idx], size=10, anchor=CENTER);
// Example: Using arc_of() distributor
// txt = "This is the string";
// arc_of(r=50, n=len(txt), sa=0, ea=180)
// atext(select(txt,-1-$idx), size=10, anchor=str("baseline",CENTER), spin=-90);
module atext(text, h=1, size=9, font="Courier", anchor="baseline", spin=0, orient=UP) {
no_children($children);
dummy1 =
assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
assert(is_undef(spin) || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient));
anchor = default(anchor, CENTER);
spin = default(spin, 0);
orient = default(orient, UP);
geom = attach_geom(size=[size,size,h]);
anch = !any([for (c=anchor) c=="["])? anchor :
let(
parts = str_split(str_split(str_split(anchor,"]")[0],"[")[1],","),
vec = [for (p=parts) str_float(str_strip_leading(p," "))]
) vec;
ha = anchor=="baseline"? "left" :
anchor==anch && is_string(anchor)? "center" :
anch.x<0? "left" :
anch.x>0? "right" :
"center";
va = starts_with(anchor,"baseline")? "baseline" :
anchor==anch && is_string(anchor)? "center" :
anch.y<0? "bottom" :
anch.y>0? "top" :
"center";
base = anchor=="baseline"? CENTER :
anchor==anch && is_string(anchor)? CENTER :
anch.z<0? BOTTOM :
anch.z>0? TOP :
CENTER;
m = attach_transform(base,spin,orient,geom);
multmatrix(m) {
$parent_anchor = anchor;
$parent_spin = spin;
$parent_orient = orient;
$parent_geom = geom;
$parent_size = attach_geom_size(geom);
$attach_to = undef;
do_show = attachment_is_shown($tags);
if (do_show) {
if (is_undef($color)) {
linear_extrude(height=h, center=true)
text(text=text, size=size, halign=ha, valign=va);
} else color($color) {
$color = undef;
linear_extrude(height=h, center=true)
text(text=text, size=size, halign=ha, valign=va);
}
}
}
}
// Section: Attachment Positioning
@ -1057,11 +1151,12 @@ module attach(from, to, overlap, norot=false)
$attach_to = to;
$attach_anchor = anch;
$attach_norot = norot;
olap = two_d? [0,-overlap,0] : [0,0,-overlap];
if (norot || (norm(anch[2]-UP)<1e-9 && anch[3]==0)) {
translate(anch[1]) translate([0,0,-overlap]) children();
translate(anch[1]) translate(olap) children();
} else {
fromvec = two_d? BACK : UP;
translate(anch[1]) rot(anch[3],from=fromvec,to=anch[2]) translate([0,0,-overlap]) children();
translate(anch[1]) rot(anch[3],from=fromvec,to=anch[2]) translate(olap) children();
}
}
}
@ -1137,10 +1232,10 @@ module edge_profile(edges=EDGES_ALL, except=[], convexity=10) {
psize = point3d($parent_size);
length = [for (i=[0:2]) if(!vec[i]) psize[i]][0]+0.1;
rotang =
vec.z<0? [90,0,180+vang(point2d(vec))] :
vec.z==0 && sign(vec.x)==sign(vec.y)? 135+vang(point2d(vec)) :
vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+vang(point2d(vec))] :
[-90,0,180+vang(point2d(vec))];
vec.z<0? [90,0,180+v_theta(vec)] :
vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) :
vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] :
[-90,0,180+v_theta(vec)];
translate(anch[1]) {
rot(rotang) {
linear_extrude(height=length, center=true, convexity=convexity) {
@ -1191,8 +1286,8 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
$attach_norot = true;
$tags = "mask";
rotang = vec.z<0?
[ 0,0,180+vang(point2d(vec))-45] :
[180,0,-90+vang(point2d(vec))-45];
[ 0,0,180+v_theta(vec)-45] :
[180,0,-90+v_theta(vec)-45];
translate(anch[1]) {
rot(rotang) {
render(convexity=convexity)
@ -1223,8 +1318,18 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
// Topics: Attachments
// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_mask()
// Description:
// Takes a 3D mask shape, and attaches it to the given edges, with the appropriate orientation to be `diff()`ed away.
// For a more step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
// Takes a 3D mask shape, and attaches it to the given edges, with the appropriate orientation to be
// `diff()`ed away. The mask shape should be vertically oriented (Z-aligned) with the back-right
// quadrant (X+Y+) shaped to be diffed away from the edge of parent attachable shape. For a more
// step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
// Figure: A Typical Edge Rounding Mask
// module roundit(l,r) difference() {
// translate([-1,-1,-l/2])
// cube([r+1,r+1,l]);
// translate([r,r])
// cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
// }
// roundit(l=30,r=10);
// Arguments:
// edges = Edges to mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: All edges.
// except = Edges to explicitly NOT mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: No edges.
@ -1252,10 +1357,10 @@ module edge_mask(edges=EDGES_ALL, except=[]) {
$attach_norot = true;
$tags = "mask";
rotang =
vec.z<0? [90,0,180+vang(point2d(vec))] :
vec.z==0 && sign(vec.x)==sign(vec.y)? 135+vang(point2d(vec)) :
vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+vang(point2d(vec))] :
[-90,0,180+vang(point2d(vec))];
vec.z<0? [90,0,180+v_theta(vec)] :
vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) :
vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] :
[-90,0,180+v_theta(vec)];
translate(anch[1]) rot(rotang) children();
}
}
@ -1296,8 +1401,8 @@ module corner_mask(corners=CORNERS_ALL, except=[]) {
$attach_norot = true;
$tags = "mask";
rotang = vec.z<0?
[ 0,0,180+vang(point2d(vec))-45] :
[180,0,-90+vang(point2d(vec))-45];
[ 0,0,180+v_theta(vec)-45] :
[180,0,-90+v_theta(vec)-45];
translate(anch[1]) rot(rotang) children();
}
}

View file

@ -1461,7 +1461,7 @@ function bezier_patch_flat(size=[100,100], N=4, spin=0, orient=UP, trans=[0,0,0]
patch = [
for (x=[0:1:N]) [
for (y=[0:1:N])
vmul(point3d(size), [x/N-0.5, 0.5-y/N, 0])
v_mul(point3d(size), [x/N-0.5, 0.5-y/N, 0])
]
],
m = move(trans) * rot(a=spin, from=UP, to=orient)

View file

@ -603,9 +603,9 @@ function generic_bottle_cap(
) = no_function("generic_bottle_cap");
// Module: thread_adapter_NC()
// Module: bottle_adapter_neck_to_cap()
// Usage:
// thread_adapter_NC(wall, <texture>);
// bottle_adapter_neck_to_cap(wall, <texture>);
// Description:
// Creates a threaded neck to cap adapter
// Arguments:
@ -628,8 +628,8 @@ function generic_bottle_cap(
// d = Distance between bottom of neck and top of cap
// taper_lead_in = Length to leave straight before tapering on tube between neck and cap if exists.
// Examples:
// thread_adapter_NC();
module thread_adapter_NC(
// bottle_adapter_neck_to_cap();
module bottle_adapter_neck_to_cap(
wall,
texture = "none",
cap_wall = 2,
@ -708,17 +708,17 @@ module thread_adapter_NC(
}
}
function thread_adapter_NC(
function bottle_adapter_neck_to_cap(
wall, texture, cap_wall, cap_h, cap_thread_od,
tolerance, cap_neck_od, cap_neck_id, cap_thread_taper,
cap_thread_pitch, neck_d, neck_id, neck_thread_od,
neck_h, neck_thread_pitch, neck_support_od, d, taper_lead_in
) = no_fuction("thread_adapter_NC");
) = no_fuction("bottle_adapter_neck_to_cap");
// Module: thread_adapter_CC()
// Module: bottle_adapter_cap_to_cap()
// Usage:
// thread_adapter_CC(wall, <texture>);
// bottle_adapter_cap_to_cap(wall, <texture>);
// Description:
// Creates a threaded cap to cap adapter.
// Arguments:
@ -738,8 +738,8 @@ function thread_adapter_NC(
// neck_id2 = Inner diameter of cutout in bottom cap.
// taper_lead_in = Length to leave straight before tapering on tube between caps if exists.
// Examples:
// thread_adapter_CC();
module thread_adapter_CC(
// bottle_adapter_cap_to_cap();
module bottle_adapter_cap_to_cap(
wall = 2,
texture = "none",
cap_h1 = 11.2,
@ -822,16 +822,16 @@ module thread_adapter_CC(
}
}
function thread_adapter_CC(
function bottle_adapter_cap_to_cap(
wall, texture, cap_h1, cap_thread_od1, tolerance,
cap_neck_od1, cap_thread_pitch1, cap_h2, cap_thread_od2,
cap_neck_od2, cap_thread_pitch2, d, neck_id1, neck_id2, taper_lead_in
) = no_function("thread_adapter_CC");
) = no_function("bottle_adapter_cap_to_cap");
// Module: thread_adapter_NN()
// Module: bottle_adapter_neck_to_neck()
// Usage:
// thread_adapter_NN();
// bottle_adapter_neck_to_neck();
// Description:
// Creates a threaded neck to neck adapter.
// Arguments:
@ -851,8 +851,8 @@ function thread_adapter_CC(
// taper_lead_in = Length to leave straight before tapering on tube between necks if exists.
// wall = Thickness of tube wall between necks. Leave undefined to match outer diameters with the neckODs/supportODs.
// Examples:
// thread_adapter_NN();
module thread_adapter_NN(
// bottle_adapter_neck_to_neck();
module bottle_adapter_neck_to_neck(
d = 0,
neck_od1 = 25,
neck_id1 = 21.4,
@ -939,12 +939,12 @@ module thread_adapter_NN(
}
}
function thread_adapter_NN(
function bottle_adapter_neck_to_neck(
d, neck_od1, neck_id1, thread_od1, height1,
support_od1, thread_pitch1, neck_od2, neck_id2,
thread_od2, height2, support_od2,
pitch2, taper_lead_in, wall
) = no_fuction("thread_adapter_NN");
) = no_fuction("bottle_adapter_neck_to_neck");

View file

@ -101,6 +101,66 @@ module cubetruss_segment(size, strut, bracing, anchor=CENTER, spin=0, orient=UP)
}
// Module: cubetruss_support()
// Usage:
// cubetruss_support(<size>, <strut>);
// Description:
// Creates a single cubetruss support.
// Arguments:
// size = The length of each side of the cubetruss cubes. Default: `$cubetruss_size` (usually 30)
// strut = The width of the struts on the cubetruss cubes. Default: `$cubetruss_strut_size` (usually 3)
// extents = If given as an integer, specifies the number of vertical segments for the support. If given as a list of 3 integers, specifies the number of segments in the X, Y, and Z directions. Default: 1.
// ---
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards. See [orient](attachments.scad#orient). Default: `UP`
// Topics: Attachable, Trusses
// Example(VPT=[0,0,0],VPD=150):
// cubetruss_support();
// Example(VPT=[0,0,0],VPD=200):
// cubetruss_support(extents=2);
// Example(VPT=[0,0,0],VPD=250):
// cubetruss_support(extents=3);
// Example(VPT=[0,0,0],VPD=350):
// cubetruss_support(extents=[2,2,3]);
// Example(VPT=[0,0,0],VPD=150):
// cubetruss_support(strut=4);
// Example(VPT=[0,0,0],VPD=260):
// cubetruss_support(extents=2) show_anchors();
module cubetruss_support(size, strut, extents=1, anchor=CENTER, spin=0, orient=UP) {
extents = is_num(extents)? [1,1,extents] : extents;
size = is_undef(size)? $cubetruss_size : size;
strut = is_undef(strut)? $cubetruss_strut_size : strut;
assert(is_int(extents.x) && extents.x > 0);
assert(is_int(extents.y) && extents.y > 0);
assert(is_int(extents.z) && extents.z > 0);
w = (size-strut) * extents.x + strut;
l = (size-strut) * extents.y + strut;
h = (size-strut) * extents.z + strut;
attachable(anchor,spin,orient, size=[w,l,h], size2=[l,0], shift=[0,l/2], axis=DOWN) {
xcopies(size-strut, n=extents.x) {
difference() {
half_of(BACK/extents.y + UP/extents.z, s=size*(max(extents)+1))
cube([size,l,h], center=true);
half_of(BACK/extents.y + UP/extents.z, cp=strut, s=size*(max(extents)+1)) {
ycopies(size-strut, n=extents.y) {
zcopies(size-strut, n=extents.z) {
cyl(h=size+1, d=size-2*strut, circum=true, realign=true, orient=RIGHT, $fn=8);
cyl(h=size+1, d=size-2*strut, circum=true, realign=true, $fn=8);
cube(size-2*strut, center=true);
}
}
}
zcopies(size-strut, n=extents.z) {
cyl(h=extents.y*size+1, d=size-2*strut, circum=true, realign=true, orient=BACK, $fn=8);
}
}
}
children();
}
}
// Module: cubetruss_clip()
// Usage:
// cubetruss_clip(extents, <size>, <strut>, <clipthick>);
@ -370,7 +430,7 @@ module cubetruss_uclip(dual=true, size, strut, clipthick, anchor=CENTER, spin=0,
// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards. See [orient](attachments.scad#orient). Default: `UP`
// Topics: Attachable, Trusses
// Examples(FlatSpin,VPD=444):
// Examples:
// cubetruss(extents=3);
// cubetruss(extents=3, clips=FRONT);
// cubetruss(extents=3, clips=[FRONT,BACK]);
@ -405,7 +465,7 @@ module cubetruss(extents=6, clips=[], bracing, size, strut, clipthick, anchor=CE
}
if (clipthick > 0) {
for (vec = clips) {
exts = vabs(rot(from=FWD, to=vec, p=extents));
exts = v_abs(rot(from=FWD, to=vec, p=extents));
rot(from=FWD,to=vec) {
for (zrow = [0:1:exts.z-1]) {
up((zrow-(exts.z-1)/2)*(size-strut)) {
@ -440,7 +500,7 @@ module cubetruss(extents=6, clips=[], bracing, size, strut, clipthick, anchor=CE
// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards. See [orient](attachments.scad#orient). Default: `UP`
// Topics: Attachable, Trusses
// Examples(FlatSpin):
// Examples:
// cubetruss_corner(extents=2);
// cubetruss_corner(extents=2, h=2);
// cubetruss_corner(extents=[3,3,0,0,2]);
@ -452,12 +512,12 @@ module cubetruss_corner(h=1, extents=[1,1,0,0,1], bracing, size, strut, clipthic
bracing = is_undef(bracing)? $cubetruss_bracing : bracing;
clipthick = is_undef(clipthick)? $cubetruss_clip_thickness : clipthick;
exts = is_vector(extents)? list_fit(extents,5,fill=0) : [extents, extents, 0, 0, extents];
s = [cubetruss_dist(1+exts[0]+exts[2],1), cubetruss_dist(1+exts[1]+exts[3],1), cubetruss_dist(h+exts[4],1)];
offset = [cubetruss_dist(exts[0]-exts[2],0), cubetruss_dist(exts[1]-exts[3],0), cubetruss_dist(exts[4],0)]/2;
s = [cubetruss_dist(exts[0]+1+exts[2],1), cubetruss_dist(exts[1]+1+exts[3],1), cubetruss_dist(h+exts[4],1)];
offset = [cubetruss_dist(exts[0]-exts[2],0), cubetruss_dist(exts[1]-exts[3],0), cubetruss_dist(h+exts[4]-1,0)]/2;
attachable(anchor,spin,orient, size=s, offset=offset) {
union() {
for (zcol = [0:h-1]) {
up((size-strut+0.01)*zcol) {
up((size-strut)*zcol) {
cubetruss_segment(size=size, strut=strut, bracing=bracing);
}
}

View file

@ -387,13 +387,24 @@ module show_anchors(s=10, std=true, custom=true) {
color("black")
noop($tags="anchor-arrow") {
xrot(two_d? 0 : 90) {
up(s/10) {
linear_extrude(height=0.01, convexity=12, center=true) {
text(text=anchor[0], size=s/4, halign="center", valign="center");
back(s/3) {
yrot_copies(n=2)
up(s/30) {
linear_extrude(height=0.01, convexity=12, center=true) {
text(text=anchor[0], size=s/4, halign="center", valign="center");
}
}
}
}
}
color([1, 1, 1, 0.4])
noop($tags="anchor-arrow") {
xrot(two_d? 0 : 90) {
back(s/3) {
zcopies(s/21) cube([s/4.5*len(anchor[0]), s/3, 0.01], center=true);
}
}
}
}
}
}

View file

@ -520,20 +520,20 @@ module grid2d(spacing, n, size, stagger=false, inside=undef)
) :
is_vector(spacing)? assert(len(spacing)==2) spacing :
size!=undef? (
is_num(n)? vdiv(size,(n-1)*[1,1]) :
is_vector(n)? assert(len(n)==2) vdiv(size,n-[1,1]) :
vdiv(size,(stagger==false? [1,1] : [2,2]))
is_num(n)? v_div(size,(n-1)*[1,1]) :
is_vector(n)? assert(len(n)==2) v_div(size,n-[1,1]) :
v_div(size,(stagger==false? [1,1] : [2,2]))
) :
undef;
n = is_num(n)? [n,n] :
is_vector(n)? assert(len(n)==2) n :
size!=undef && spacing!=undef? vfloor(vdiv(size,spacing))+[1,1] :
size!=undef && spacing!=undef? v_floor(v_div(size,spacing))+[1,1] :
[2,2];
offset = vmul(spacing, n-[1,1])/2;
offset = v_mul(spacing, n-[1,1])/2;
if (stagger == false) {
for (row = [0:1:n.y-1]) {
for (col = [0:1:n.x-1]) {
pos = vmul([col,row],spacing) - offset;
pos = v_mul([col,row],spacing) - offset;
if (
is_undef(inside) ||
(is_path(inside) && point_in_polygon(pos, inside)>=0) ||
@ -556,7 +556,7 @@ module grid2d(spacing, n, size, stagger=false, inside=undef)
if (rowcols > 0) {
for (col = [0:1:rowcols-1]) {
rowdx = (row%2 != staggermod)? spacing.x : 0;
pos = vmul([2*col,row],spacing) + [rowdx,0] - offset;
pos = v_mul([2*col,row],spacing) + [rowdx,0] - offset;
if (
is_undef(inside) ||
(is_path(inside) && point_in_polygon(pos, inside)>=0) ||
@ -616,7 +616,7 @@ module grid3d(xa=[0], ya=[0], za=[0], n=undef, spacing=undef)
for (yi = [0:1:n.y-1]) {
for (zi = [0:1:n.z-1]) {
$idx = [xi,yi,zi];
$pos = vmul(spacing, $idx - (n-[1,1,1])/2);
$pos = v_mul(spacing, $idx - (n-[1,1,1])/2);
translate($pos) children();
}
}
@ -989,7 +989,7 @@ module arc_of(
//
// Example:
// ovoid_spread(n=500, d=100, cone_ang=180)
// color(unit(point3d(vabs($pos))))
// color(unit(point3d(v_abs($pos))))
// cylinder(d=8, h=10, center=false);
module ovoid_spread(r=undef, d=undef, n=100, cone_ang=90, scale=[1,1,1], perp=true)
{
@ -1004,7 +1004,7 @@ module ovoid_spread(r=undef, d=undef, n=100, cone_ang=90, scale=[1,1,1], perp=tr
for ($idx = idx(theta_phis)) {
tp = theta_phis[$idx];
xyz = spherical_to_xyz(r, tp[0], tp[1]);
$pos = vmul(xyz,point3d(scale,1));
$pos = v_mul(xyz,point3d(scale,1));
$theta = tp[0];
$phi = tp[1];
$rad = r;

View file

@ -139,7 +139,7 @@ function _edge_set(v) =
str(v, " must be a vector, edge array, or one of ", valid_values)
) v
) :
let(nonz = sum(vabs(v)))
let(nonz = sum(v_abs(v)))
nonz==2? (v==v2) : // Edge: return matching edge.
let(
matches = count_true([

View file

@ -800,7 +800,6 @@ module spur_gear(
backlash = backlash,
interior = interior
);
circle(d=shaft_diam+4);
}
if (shaft_diam > 0) {
cylinder(h=2*thickness+1, r=shaft_diam/2, center=true, $fn=max(12,segs(shaft_diam/2)));
@ -929,7 +928,7 @@ function bevel_gear(
radcp = [0, midpr] + polar_to_xy(cutter_radius, 180+spiral_angle),
angC1 = law_of_cosines(a=cutter_radius, b=norm(radcp), c=ocone_rad),
angC2 = law_of_cosines(a=cutter_radius, b=norm(radcp), c=icone_rad),
radcpang = vang(radcp),
radcpang = v_theta(radcp),
sang = radcpang - (180-angC1),
eang = radcpang - (180-angC2),
profile = gear_tooth_profile(
@ -945,7 +944,7 @@ function bevel_gear(
verts1 = [
for (v = lerpn(0,1,slices+1)) let(
p = radcp + polar_to_xy(cutter_radius, lerp(sang,eang,v)),
ang = vang(p)-90,
ang = v_theta(p)-90,
dist = norm(p)
) [
let(

View file

@ -1,4 +1,4 @@
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// LibFile: geometry.scad
// Geometry helpers.
// Includes:
@ -2237,4 +2237,254 @@ function split_polygons_at_each_z(polys, zs, _i=0) =
);
// Section: Convex Sets
// Function: is_convex_polygon()
// Usage:
// is_convex_polygon(poly);
// Description:
// Returns true if the given 2D or 3D polygon is convex.
// The result is meaningless if the polygon is not simple (self-intersecting) or non coplanar.
// If the points are collinear an error is generated.
// Arguments:
// poly = Polygon to check.
// eps = Tolerance for the collinearity test. Default: EPSILON.
// Example:
// is_convex_polygon(circle(d=50)); // Returns: true
// is_convex_polygon(rot([50,120,30], p=path3d(circle(1,$fn=50)))); // Returns: true
// Example:
// spiral = [for (i=[0:36]) let(a=-i*10) (10+i)*[cos(a),sin(a)]];
// is_convex_polygon(spiral); // Returns: false
function is_convex_polygon(poly,eps=EPSILON) =
assert(is_path(poly), "The input should be a 2D or 3D polygon." )
let( lp = len(poly) )
assert( lp>=3 , "A polygon must have at least 3 points" )
let( crosses = [for(i=[0:1:lp-1]) cross(poly[(i+1)%lp]-poly[i], poly[(i+2)%lp]-poly[(i+1)%lp]) ] )
len(poly[0])==2
? assert( max(max(crosses),-min(crosses))>eps, "The points are collinear" )
min(crosses) >=0 || max(crosses)<=0
: let( prod = crosses*sum(crosses),
minc = min(prod),
maxc = max(prod) )
assert( max(maxc,-minc)>eps, "The points are collinear" )
minc>=0 || maxc<=0;
// Function: convex_distance()
// Usage:
// convex_distance(pts1, pts2,<eps=>);
// See also:
// convex_collision
// Description:
// Returns the smallest distance between a point in convex hull of `points1`
// and a point in the convex hull of `points2`. All the points in the lists
// should have the same dimension, either 2D or 3D.
// A zero result means the hulls intercept whithin a tolerance `eps`.
// Arguments:
// points1 - first list of 2d or 3d points.
// points2 - second list of 2d or 3d points.
// eps - tolerance in distance evaluations. Default: EPSILON.
// Example(2D):
// pts1 = move([-3,0], p=square(3,center=true));
// pts2 = rot(a=45, p=square(2,center=true));
// pts3 = [ [2,0], [1,2],[3,2], [3,-2], [1,-2] ];
// polygon(pts1);
// polygon(pts2);
// polygon(pts3);
// echo(convex_distance(pts1,pts2)); // Returns: 0.0857864
// echo(convex_distance(pts2,pts3)); // Returns: 0
// Example(3D):
// sphr1 = sphere(2,$fn=10);
// sphr2 = move([4,0,0], p=sphr1);
// sphr3 = move([4.5,0,0], p=sphr1);
// vnf_polyhedron(sphr1);
// vnf_polyhedron(sphr2);
// echo(convex_distance(sphr1[0], sphr2[0])); // Returns: 0
// echo(convex_distance(sphr1[0], sphr3[0])); // Returns: 0.5
function convex_distance(points1, points2, eps=EPSILON) =
assert(is_matrix(points1) && is_matrix(points2,undef,len(points1[0])),
"The input list should be a consistent non empty list of points of same dimension.")
assert(len(points1[0])==2 || len(points1[0])==3 ,
"The input points should be 2d or 3d points.")
let( d = points1[0]-points2[0] )
norm(d)<eps ? 0 :
let( v = _support_diff(points1,points2,-d) )
norm(_GJK_distance(points1, points2, eps, 0, v, [v]));
// Finds the vector difference between the hulls of the two pointsets by the GJK algorithm
// Based on:
// http://www.dtecta.com/papers/jgt98convex.pdf
function _GJK_distance(points1, points2, eps=EPSILON, lbd, d, simplex=[]) =
let( nrd = norm(d) ) // distance upper bound
nrd<eps ? d :
let(
v = _support_diff(points1,points2,-d),
lbd = max(lbd, d*v/nrd), // distance lower bound
close = (nrd-lbd <= eps*nrd)
)
// v already in the simplex is a degenerence due to numerical errors
// and may produce a non-stopping loop
close || [for(nv=norm(v), s=simplex) if(norm(s-v)<=eps*nv) 1]!=[] ? d :
let( newsplx = _closest_simplex(concat(simplex,[v]),eps) )
_GJK_distance(points1, points2, eps, lbd, newsplx[0], newsplx[1]);
// Function: convex_collision()
// Usage:
// convex_collision(pts1, pts2,<eps=>);
// See also:
// convex_distance
// Description:
// Returns `true` if the convex hull of `points1` intercepts the convex hull of `points2`
// otherwise, `false`.
// All the points in the lists should have the same dimension, either 2D or 3D.
// This function is tipically faster than `convex_distance` to find a non-collision.
// Arguments:
// points1 - first list of 2d or 3d points.
// points2 - second list of 2d or 3d points.
// eps - tolerance for the intersection tests. Default: EPSILON.
// Example(2D):
// pts1 = move([-3,0], p=square(3,center=true));
// pts2 = rot(a=45, p=square(2,center=true));
// pts3 = [ [2,0], [1,2],[3,2], [3,-2], [1,-2] ];
// polygon(pts1);
// polygon(pts2);
// polygon(pts3);
// echo(convex_collision(pts1,pts2)); // Returns: false
// echo(convex_collision(pts2,pts3)); // Returns: true
// Example(3D):
// sphr1 = sphere(2,$fn=10);
// sphr2 = move([4,0,0], p=sphr1);
// sphr3 = move([4.5,0,0], p=sphr1);
// vnf_polyhedron(sphr1);
// vnf_polyhedron(sphr2);
// echo(convex_collision(sphr1[0], sphr2[0])); // Returns: true
// echo(convex_collision(sphr1[0], sphr3[0])); // Returns: false
//
function convex_collision(points1, points2, eps=EPSILON) =
assert(is_matrix(points1) && is_matrix(points2,undef,len(points1[0])),
"The input list should be a consistent non empty list of points of same dimension.")
assert(len(points1[0])==2 || len(points1[0])==3 ,
"The input points should be 2d or 3d points.")
let( d = points1[0]-points2[0] )
norm(d)<eps ? true :
let( v = _support_diff(points1,points2,-d) )
_GJK_collide(points1, points2, v, [v], eps);
// Based on the GJK collision algorithms found in:
// http://uu.diva-portal.org/smash/get/diva2/FFULLTEXT01.pdf
// or
// http://www.dtecta.com/papers/jgt98convex.pdf
function _GJK_collide(points1, points2, d, simplex, eps=EPSILON) =
norm(d) < eps ? true : // does collide
let( v = _support_diff(points1,points2,-d) )
v*d > eps ? false : // no collision
let( newsplx = _closest_simplex(concat(simplex,[v]),eps) )
_GJK_collide(points1, points2, newsplx[0], newsplx[1], eps);
// given a simplex s, returns a pair:
// - the point of the s closest to the origin
// - the smallest sub-simplex of s that contains that point
function _closest_simplex(s,eps=EPSILON) =
assert(len(s)>=2 && len(s)<=4, "Internal error.")
len(s)==2 ? _closest_s1(s,eps) :
len(s)==3 ? _closest_s2(s,eps)
: _closest_s3(s,eps);
// find the closest to a 1-simplex
// Based on: http://uu.diva-portal.org/smash/get/diva2/FFULLTEXT01.pdf
function _closest_s1(s,eps=EPSILON) =
norm(s[1]-s[0])<eps*(norm(s[0])+norm(s[1]))/2 ? [ s[0], [s[0]] ] :
let(
c = s[1]-s[0],
t = -s[0]*c/(c*c)
)
t<0 ? [ s[0], [s[0]] ] :
t>1 ? [ s[1], [s[1]] ] :
[ s[0]+t*c, s ];
// find the closest to a 2-simplex
// Based on: http://uu.diva-portal.org/smash/get/diva2/FFULLTEXT01.pdf
function _closest_s2(s,eps=EPSILON) =
let(
dim = len(s[0]),
a = dim==3 ? s[0]: [ each s[0], 0] ,
b = dim==3 ? s[1]: [ each s[1], 0] ,
c = dim==3 ? s[2]: [ each s[2], 0] ,
ab = norm(a-b),
bc = norm(b-c),
ca = norm(c-a),
nr = cross(b-a,c-a)
)
norm(nr) <= eps*max(ab,bc,ca) // degenerate case
? let( i = max_index([ab, bc, ca]) )
_closest_s1([s[i],s[(i+1)%3]],eps)
// considering that s[2] was the last inserted vertex in s,
// the only possible outcomes are :
// s, [s[0],s[2]] and [s[1],s[2]]
: let(
class = (cross(nr,a-b)*a<0 ? 1 : 0 )
+ (cross(nr,c-a)*a<0 ? 2 : 0 )
+ (cross(nr,b-c)*b<0 ? 4 : 0 )
)
assert( class!=1, "Internal error" )
class==0 ? [ nr*(nr*a)/(nr*nr), s] : // origin projects (or is) on the tri
// class==1 ? _closest_s1([s[0],s[1]]) :
class==2 ? _closest_s1([s[0],s[2]],eps) :
class==4 ? _closest_s1([s[1],s[2]],eps) :
// class==3 ? a*(a-b)> 0 ? _closest_s1([s[0],s[1]]) : _closest_s1([s[0],s[2]]) :
class==3 ? _closest_s1([s[0],s[2]],eps) :
// class==5 ? b*(b-c)<=0 ? _closest_s1([s[0],s[1]]) : _closest_s1([s[1],s[2]]) :
class==5 ? _closest_s1([s[1],s[2]],eps) :
c*(c-a)>0 ? _closest_s1([s[0],s[2]],eps) : _closest_s1([s[1],s[2]],eps);
// find the closest to a 3-simplex
// it seems that degenerate 3-simplices are correctly manage without extra code
function _closest_s3(s,eps=EPSILON) =
assert( len(s[0])==3 && len(s)==4, "Internal error." )
let( nr = cross(s[1]-s[0],s[2]-s[0]),
sz = [ norm(s[1]-s[0]), norm(s[1]-s[2]), norm(s[2]-s[0]) ] )
norm(nr)<eps*max(sz)
? let( i = max_index(sz) )
_closest_s2([ s[i], s[(i+1)%3], s[3] ], eps) // degenerate case
// considering that s[3] was the last inserted vertex in s,
// the only possible outcomes will be:
// s or some of the 3 triangles of s containing s[3]
: let(
tris = [ [s[0], s[1], s[3]],
[s[1], s[2], s[3]],
[s[2], s[0], s[3]] ],
cntr = sum(s)/4,
// indicator of the tris facing the origin
facing = [for(i=[0:2])
let( nrm = _tri_normal(tris[i]) )
if( ((nrm*(s[i]-cntr))>0)==(nrm*s[i]<0) ) i ]
)
len(facing)==0 ? [ [0,0,0], s ] : // origin is inside the simplex
len(facing)==1 ? _closest_s2(tris[facing[0]], eps) :
let( // look for the origin-facing tri closest to the origin
closest = [for(i=facing) _closest_s2(tris[i], eps) ],
dist = [for(cl=closest) norm(cl[0]) ],
nearest = min_index(dist)
)
closest[nearest];
function _tri_normal(tri) = cross(tri[1]-tri[0],tri[2]-tri[0]);
function _support_diff(p1,p2,d) =
let( p1d = p1*d, p2d = p2*d )
p1[search(max(p1d),p1d,1)[0]] - p2[search(min(p2d),p2d,1)[0]];
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

109
math.scad
View file

@ -237,7 +237,7 @@ function u_sub(a,b) = is_undef(a) || is_undef(b)? undef : a - b;
// b = Second value.
function u_mul(a,b) =
is_undef(a) || is_undef(b)? undef :
is_vector(a) && is_vector(b)? vmul(a,b) :
is_vector(a) && is_vector(b)? v_mul(a,b) :
a * b;
@ -252,7 +252,7 @@ function u_mul(a,b) =
// b = Second value.
function u_div(a,b) =
is_undef(a) || is_undef(b)? undef :
is_vector(a) && is_vector(b)? vdiv(a,b) :
is_vector(a) && is_vector(b)? v_div(a,b) :
a / b;
@ -674,7 +674,7 @@ function _product(v, i=0, _tot) =
i>=len(v) ? _tot :
_product( v,
i+1,
( is_vector(v[i])? vmul(_tot,v[i]) : _tot*v[i] ) );
( is_vector(v[i])? v_mul(_tot,v[i]) : _tot*v[i] ) );
@ -711,7 +711,7 @@ function _cumprod_vec(v,_i=0,_acc=[]) =
v, _i+1,
concat(
_acc,
[_i==0 ? v[_i] : vmul(_acc[len(_acc)-1],v[_i])]
[_i==0 ? v[_i] : v_mul(_acc[len(_acc)-1],v[_i])]
)
);
@ -1261,8 +1261,10 @@ function compare_lists(a, b) =
// Function: any()
// Usage:
// b = any(l);
// b = any(l,func);
// bool = any(l);
// bool = any(l,func); // Requires OpenSCAD 2021.01 or later.
// Requirements:
// Requires OpenSCAD 2021.01 or later to use the `func=` argument.
// Description:
// Returns true if any item in list `l` evaluates as true.
// Arguments:
@ -1292,8 +1294,10 @@ function _any_bool(l, i=0, out=false) =
// Function: all()
// Usage:
// b = all(l);
// b = all(l,func);
// bool = all(l);
// bool = all(l,func); // Requires OpenSCAD 2021.01 or later.
// Requirements:
// Requires OpenSCAD 2021.01 or later to use the `func=` argument.
// Description:
// Returns true if all items in list `l` evaluate as true. If `func` is given a function liteal
// of signature (x), returning bool, then that function literal is evaluated for each list item.
@ -1325,8 +1329,10 @@ function _all_bool(l, i=0, out=true) =
// Function: count_true()
// Usage:
// n = count_true(l,<nmax=>)
// n = count_true(l,func,<nmax=>)
// n = count_true(l,<nmax=>);
// n = count_true(l,func,<nmax=>); // Requires OpenSCAD 2021.01 or later.
// Requirements:
// Requires OpenSCAD 2021.01 or later to use the `func=` argument.
// Description:
// Returns the number of items in `l` that evaluate as true.
// If `l` is a lists of lists, this is applied recursively to each
@ -1631,8 +1637,6 @@ function c_ident(n) = [for (i = [0:1:n-1]) [for (j = [0:1:n-1]) (i==j)?[1,0]:[0,
// Compute the norm of a complex number or vector.
function c_norm(z) = norm_fro(z);
// Section: Polynomials
// Function: quadratic_roots()
@ -1840,12 +1844,19 @@ function _poly_roots(p, pderiv, s, z, tol, i=0) =
// parts are zero. You can specify eps, in which case the test is
// z.y/(1+norm(z)) < eps. Because
// of poor convergence and higher error for repeated roots, such roots may
// be missed by the algorithm because their imaginary part is large.
// be missed by the algorithm because their imaginary part is large.
// Arguments:
// p = polynomial to solve as coefficient list, highest power term first
// eps = used to determine whether imaginary parts of roots are zero
// tol = tolerance for the complex polynomial root finder
// The algorithm is based on Brent's method and is a combination of
// bisection and inverse quadratic approximation, where bisection occurs
// at every step, with refinement using inverse quadratic approximation
// only when that approximation gives a good result. The detail
// of how to decide when to use the quadratic came from an article
// by Crenshaw on "The World's Best Root Finder".
// https://www.embedded.com/worlds-best-root-finder/
function real_roots(p,eps=undef,tol=1e-14) =
assert( is_vector(p), "Invalid polynomial." )
let( p = _poly_trim(p,eps=0) )
@ -1859,4 +1870,76 @@ function real_roots(p,eps=undef,tol=1e-14) =
? [for(z=roots) if (abs(z.y)/(1+norm(z))<eps) z.x]
: [for(i=idx(roots)) if (abs(roots[i].y)<=err[i]) roots[i].x];
// Section: Operations on Functions
// Function: root_find()
// Usage:
// x = root_find(f, x0, x1, [tol])
// Description:
// Find a root of the continuous function f where the sign of f(x0) is different
// from the sign of f(x1). The function f is a function literal accepting one
// argument. You must have a version of OpenSCAD that supports function literals
// (2021.01 or newer). The tolerance (tol) specifies the accuracy of the solution:
// abs(f(x)) < tol * yrange, where yrange is the range of observed function values.
// This function can only find roots that cross the x axis: it cannot find the
// the root of x^2.
// Arguments:
// f = function literal for a single variable function
// x0 = endpoint of interval to search for root
// x1 = second endpoint of interval to search for root
// tol = tolerance for solution. Default: 1e-15
function root_find(f,x0,x1,tol=1e-15) =
let(
y0 = f(x0),
y1 = f(x1),
yrange = y0<y1 ? [y0,y1] : [y1,y0]
)
// Check endpoints
y0==0 || _rfcheck(x0, y0,yrange,tol) ? x0 :
y1==0 || _rfcheck(x1, y1,yrange,tol) ? x1 :
assert(y0*y1<0, "Sign of function must be different at the interval endpoints")
_rootfind(f,[x0,x1],[y0,y1],yrange,tol);
function _rfcheck(x,y,range,tol) =
assert(is_finite(y), str("Function not finite at ",x))
abs(y) < tol*(range[1]-range[0]);
// xpts and ypts are arrays whose first two entries contain the
// interval bracketing the root. Extra entries are ignored.
// yrange is the total observed range of y values (used for the
// tolerance test).
function _rootfind(f, xpts, ypts, yrange, tol, i=0) =
assert(i<100, "root_find did not converge to a solution")
let(
xmid = (xpts[0]+xpts[1])/2,
ymid = f(xmid),
yrange = [min(ymid, yrange[0]), max(ymid, yrange[1])]
)
_rfcheck(xmid, ymid, yrange, tol) ? xmid :
let(
// Force root to be between x0 and midpoint
y = ymid * ypts[0] < 0 ? [ypts[0], ymid, ypts[1]]
: [ypts[1], ymid, ypts[0]],
x = ymid * ypts[0] < 0 ? [xpts[0], xmid, xpts[1]]
: [xpts[1], xmid, xpts[0]],
v = y[2]*(y[2]-y[0]) - 2*y[1]*(y[1]-y[0])
)
v <= 0 ? _rootfind(f,x,y,yrange,tol,i+1) // Root is between first two points, extra 3rd point doesn't hurt
:
let( // Do quadratic approximation
B = (x[1]-x[0]) / (y[1]-y[0]),
C = y*[-1,2,-1] / (y[2]-y[1]) / (y[2]-y[0]),
newx = x[0] - B * y[0] *(1-C*y[1]),
newy = f(newx),
new_yrange = [min(yrange[0],newy), max(yrange[1], newy)],
// select interval that contains the root by checking sign
yinterval = newy*y[0] < 0 ? [y[0],newy] : [newy,y[1]],
xinterval = newy*y[0] < 0 ? [x[0],newx] : [newx,x[1]]
)
_rfcheck(newx, newy, new_yrange, tol)
? newx
: _rootfind(f, xinterval, yinterval, new_yrange, tol, i+1);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -515,7 +515,7 @@ module path_extrude2d(path, caps=true) {
}
}
for (t=triplet(path)) {
ang = vang(t[2]-t[1]) - vang(t[1]-t[0]);
ang = v_theta(t[2]-t[1]) - v_theta(t[1]-t[0]);
delt = t[2] - t[1];
translate(t[1]) {
minkowski() {

View file

@ -44,7 +44,7 @@ function _partition_cutpath(l, h, cutsize, cutpath, gap) =
cplen = (cutsize.x+gap) * reps,
path = deduplicate(concat(
[[-l/2, cutpath[0].y*cutsize.y]],
[for (i=[0:1:reps-1], pt=cutpath) vmul(pt,cutsize)+[i*(cutsize.x+gap)+gap/2-cplen/2,0]],
[for (i=[0:1:reps-1], pt=cutpath) v_mul(pt,cutsize)+[i*(cutsize.x+gap)+gap/2-cplen/2,0]],
[[ l/2, cutpath[len(cutpath)-1].y*cutsize.y]]
))
) path;
@ -164,7 +164,7 @@ module partition(size=100, spread=10, cutsize=10, cutpath=undef, gap=0, spin=0)
{
size = is_vector(size)? size : [size,size,size];
cutsize = is_vector(cutsize)? cutsize : [cutsize*2, cutsize];
rsize = vabs(rot(spin,p=size));
rsize = v_abs(rot(spin,p=size));
vec = rot(spin,p=BACK)*spread/2;
move(vec) {
intersection() {

View file

@ -661,7 +661,7 @@ function path_self_intersections(path, closed=true, eps=EPSILON) =
path = cleanup_path(path, eps=eps),
plen = len(path)
) [
for (i = [0:1:plen-(closed?2:3)], j=[i+1:1:plen-(closed?1:2)]) let(
for (i = [0:1:plen-(closed?2:3)], j=[i+2:1:plen-(closed?1:2)]) let(
a1 = path[i],
a2 = path[(i+1)%plen],
b1 = path[j],
@ -675,15 +675,17 @@ function path_self_intersections(path, closed=true, eps=EPSILON) =
c = a1-a2,
d = b1-b2,
denom = (c.x*d.y)-(c.y*d.x)
) abs(denom)<eps? undef : let(
) abs(denom)<eps? undef :
let(
e = a1-b1,
t = ((e.x*d.y)-(e.y*d.x)) / denom,
u = ((e.x*c.y)-(e.y*c.x)) / denom
) [a1+t*(a2-a1), t, u]
) if (
(!closed || i!=0 || j!=plen-1) &&
isect != undef &&
isect[1]>eps && isect[1]<=1+eps &&
isect[2]>eps && isect[2]<=1+eps
isect[1]>=-eps && isect[1]<=1+eps &&
isect[2]>=-eps && isect[2]<=1+eps
) [isect[0], i, isect[1], j, isect[2]]
];
@ -1109,11 +1111,11 @@ module spiral_sweep(poly, h, r, twist=360, higbee, center, r1, r2, d, d1, d2, hi
// path = [ [0, 0, 0], [33, 33, 33], [66, 33, 40], [100, 0, 0], [150,0,0] ];
// path_extrude(path) circle(r=10, $fn=6);
module path_extrude(path, convexity=10, clipsize=100) {
function polyquats(path, q=Q_Ident(), v=[0,0,1], i=0) = let(
function polyquats(path, q=q_ident(), v=[0,0,1], i=0) = let(
v2 = path[i+1] - path[i],
ang = vector_angle(v,v2),
axis = ang>0.001? unit(cross(v,v2)) : [0,0,1],
newq = Q_Mul(Quat(axis, ang), q),
newq = q_mul(quat(axis, ang), q),
dist = norm(v2)
) i < (len(path)-2)?
concat([[dist, newq, ang]], polyquats(path, newq, v2, i+1)) :
@ -1129,7 +1131,7 @@ module path_extrude(path, convexity=10, clipsize=100) {
q = pquats[i][1];
difference() {
translate(pt1) {
Qrot(q) {
q_rot(q) {
down(clipsize/2/2) {
if ((dist+clipsize/2) > 0) {
linear_extrude(height=dist+clipsize/2, convexity=convexity) {
@ -1140,12 +1142,12 @@ module path_extrude(path, convexity=10, clipsize=100) {
}
}
translate(pt1) {
hq = (i > 0)? Q_Slerp(q, pquats[i-1][1], 0.5) : q;
Qrot(hq) down(clipsize/2+epsilon) cube(clipsize, center=true);
hq = (i > 0)? q_slerp(q, pquats[i-1][1], 0.5) : q;
q_rot(hq) down(clipsize/2+epsilon) cube(clipsize, center=true);
}
translate(pt2) {
hq = (i < ptcount-2)? Q_Slerp(q, pquats[i+1][1], 0.5) : q;
Qrot(hq) up(clipsize/2+epsilon) cube(clipsize, center=true);
hq = (i < ptcount-2)? q_slerp(q, pquats[i+1][1], 0.5) : q;
q_rot(hq) up(clipsize/2+epsilon) cube(clipsize, center=true);
}
}
}

View file

@ -377,7 +377,7 @@ function _point_ref(points, sign="both") =
unique([
for(i=[-1,1],j=[-1,1],k=[-1,1])
if (sign=="both" || sign=="even" && i*j*k>0 || sign=="odd" && i*j*k<0)
each [for(point=points) vmul(point,[i,j,k])]
each [for(point=points) v_mul(point,[i,j,k])]
]);
//
_tribonacci=(1+4*cosh(acosh(2+3/8)/3))/3;

View file

@ -139,7 +139,7 @@ function cube(size=1, center, anchor, spin=0, orient=UP) =
[-1,-1, 1],[1,-1, 1],[1,1, 1],[-1,1, 1],
]/2,
verts = is_num(size)? unscaled * size :
is_vector(size,3)? [for (p=unscaled) vmul(p,size)] :
is_vector(size,3)? [for (p=unscaled) v_mul(p,size)] :
assert(is_num(size) || is_vector(size,3)),
faces = [
[0,1,2], [0,2,3], //BOTTOM

View file

@ -16,190 +16,190 @@
// Internal
function _Quat(a,s,w) = [a[0]*s, a[1]*s, a[2]*s, w];
function _quat(a,s,w) = [a[0]*s, a[1]*s, a[2]*s, w];
function _Qvec(q) = [q.x,q.y,q.z];
function _qvec(q) = [q.x,q.y,q.z];
function _Qreal(q) = q[3];
function _qreal(q) = q[3];
function _Qset(v,r) = concat( v, r );
function _qset(v,r) = concat( v, r );
// normalizes without checking
function _Qnorm(q) = q/norm(q);
function _qnorm(q) = q/norm(q);
// Function: Q_is_quat()
// Function: is_quaternion()
// Usage:
// if(Q_is_quat(q)) a=0;
// if(is_quaternion(q)) a=0;
// Description: Return true if q is a valid non-zero quaternion.
// Arguments:
// q = object to check.
function Q_is_quat(q) = is_vector(q,4) && ! approx(norm(q),0) ;
function is_quaternion(q) = is_vector(q,4) && ! approx(norm(q),0) ;
// Function: Quat()
// Function: quat()
// Usage:
// Quat(ax, ang);
// quat(ax, ang);
// Description: Create a normalized Quaternion from axis and angle of rotation.
// Arguments:
// ax = Vector of axis of rotation.
// ang = Number of degrees to rotate around the axis counter-clockwise, when facing the origin.
function Quat(ax=[0,0,1], ang=0) =
function quat(ax=[0,0,1], ang=0) =
assert( is_vector(ax,3) && is_finite(ang), "Invalid input")
let( n = norm(ax) )
approx(n,0)
? _Quat([0,0,0], sin(ang/2), cos(ang/2))
: _Quat(ax/n, sin(ang/2), cos(ang/2));
? _quat([0,0,0], sin(ang/2), cos(ang/2))
: _quat(ax/n, sin(ang/2), cos(ang/2));
// Function: QuatX()
// Function: quat_x()
// Usage:
// QuatX(a);
// quat_x(a);
// Description: Create a normalized Quaternion for rotating around the X axis [1,0,0].
// Arguments:
// a = Number of degrees to rotate around the axis counter-clockwise, when facing the origin.
function QuatX(a=0) =
function quat_x(a=0) =
assert( is_finite(a), "Invalid angle" )
Quat([1,0,0],a);
quat([1,0,0],a);
// Function: QuatY()
// Function: quat_y()
// Usage:
// QuatY(a);
// quat_y(a);
// Description: Create a normalized Quaternion for rotating around the Y axis [0,1,0].
// Arguments:
// a = Number of degrees to rotate around the axis counter-clockwise, when facing the origin.
function QuatY(a=0) =
function quat_y(a=0) =
assert( is_finite(a), "Invalid angle" )
Quat([0,1,0],a);
quat([0,1,0],a);
// Function: QuatZ()
// Function: quat_z()
// Usage:
// QuatZ(a);
// quat_z(a);
// Description: Create a normalized Quaternion for rotating around the Z axis [0,0,1].
// Arguments:
// a = Number of degrees to rotate around the axis counter-clockwise, when facing the origin.
function QuatZ(a=0) =
function quat_z(a=0) =
assert( is_finite(a), "Invalid angle" )
Quat([0,0,1],a);
quat([0,0,1],a);
// Function: QuatXYZ()
// Function: quat_xyz()
// Usage:
// QuatXYZ([X,Y,Z])
// quat_xyz([X,Y,Z])
// Description:
// Creates a normalized quaternion from standard [X,Y,Z] rotation angles in degrees.
// Arguments:
// a = The triplet of rotation angles, [X,Y,Z]
function QuatXYZ(a=[0,0,0]) =
function quat_xyz(a=[0,0,0]) =
assert( is_vector(a,3), "Invalid angles")
let(
qx = QuatX(a[0]),
qy = QuatY(a[1]),
qz = QuatZ(a[2])
qx = quat_x(a[0]),
qy = quat_y(a[1]),
qz = quat_z(a[2])
)
Q_Mul(qz, Q_Mul(qy, qx));
q_mul(qz, q_mul(qy, qx));
// Function: Q_From_to()
// Function: q_from_to()
// Usage:
// q = Q_From_to(v1, v2);
// q = q_from_to(v1, v2);
// Description:
// Returns the normalized quaternion that rotates the non zero 3D vector v1
// to the non zero 3D vector v2.
function Q_From_to(v1, v2) =
function q_from_to(v1, v2) =
assert( is_vector(v1,3) && is_vector(v2,3)
&& ! approx(norm(v1),0) && ! approx(norm(v2),0)
, "Invalid vector(s)")
let( ax = cross(v1,v2),
n = norm(ax) )
approx(n, 0)
? v1*v2>0 ? Q_Ident() : Quat([ v1.y, -v1.x, 0], 180)
: Quat(ax, atan2( n , v1*v2 ));
? v1*v2>0 ? q_ident() : quat([ v1.y, -v1.x, 0], 180)
: quat(ax, atan2( n , v1*v2 ));
// Function: Q_Ident()
// Function: q_ident()
// Description: Returns the "Identity" zero-rotation Quaternion.
function Q_Ident() = [0, 0, 0, 1];
function q_ident() = [0, 0, 0, 1];
// Function: Q_Add_S()
// Function: q_add_s()
// Usage:
// Q_Add_S(q, s)
// q_add_s(q, s)
// Description:
// Adds a scalar value `s` to the W part of a quaternion `q`.
// The returned quaternion is usually not normalized.
function Q_Add_S(q, s) =
function q_add_s(q, s) =
assert( is_finite(s), "Invalid scalar" )
q+[0,0,0,s];
// Function: Q_Sub_S()
// Function: q_sub_s()
// Usage:
// Q_Sub_S(q, s)
// q_sub_s(q, s)
// Description:
// Subtracts a scalar value `s` from the W part of a quaternion `q`.
// The returned quaternion is usually not normalized.
function Q_Sub_S(q, s) =
function q_sub_s(q, s) =
assert( is_finite(s), "Invalid scalar" )
q-[0,0,0,s];
// Function: Q_Mul_S()
// Function: q_mul_s()
// Usage:
// Q_Mul_S(q, s)
// q_mul_s(q, s)
// Description:
// Multiplies each part of a quaternion `q` by a scalar value `s`.
// The returned quaternion is usually not normalized.
function Q_Mul_S(q, s) =
function q_mul_s(q, s) =
assert( is_finite(s), "Invalid scalar" )
q*s;
// Function: Q_Div_S()
// Function: q_div_s()
// Usage:
// Q_Div_S(q, s)
// q_div_s(q, s)
// Description:
// Divides each part of a quaternion `q` by a scalar value `s`.
// The returned quaternion is usually not normalized.
function Q_Div_S(q, s) =
function q_div_s(q, s) =
assert( is_finite(s) && ! approx(s,0) , "Invalid scalar" )
q/s;
// Function: Q_Add()
// Function: q_add()
// Usage:
// Q_Add(a, b)
// q_add(a, b)
// Description:
// Adds each part of two quaternions together.
// The returned quaternion is usually not normalized.
function Q_Add(a, b) =
assert( Q_is_quat(a) && Q_is_quat(a), "Invalid quaternion(s)")
function q_add(a, b) =
assert( is_quaternion(a) && is_quaternion(a), "Invalid quaternion(s)")
assert( ! approx(norm(a+b),0), "Quaternions cannot be opposed" )
a+b;
// Function: Q_Sub()
// Function: q_sub()
// Usage:
// Q_Sub(a, b)
// q_sub(a, b)
// Description:
// Subtracts each part of quaternion `b` from quaternion `a`.
// The returned quaternion is usually not normalized.
function Q_Sub(a, b) =
assert( Q_is_quat(a) && Q_is_quat(a), "Invalid quaternion(s)")
function q_sub(a, b) =
assert( is_quaternion(a) && is_quaternion(a), "Invalid quaternion(s)")
assert( ! approx(a,b), "Quaternions cannot be equal" )
a-b;
// Function: Q_Mul()
// Function: q_mul()
// Usage:
// Q_Mul(a, b)
// q_mul(a, b)
// Description:
// Multiplies quaternion `a` by quaternion `b`.
// The returned quaternion is normalized if both `a` and `b` are normalized
function Q_Mul(a, b) =
assert( Q_is_quat(a) && Q_is_quat(b), "Invalid quaternion(s)")
function q_mul(a, b) =
assert( is_quaternion(a) && is_quaternion(b), "Invalid quaternion(s)")
[
a[3]*b.x + a.x*b[3] + a.y*b.z - a.z*b.y,
a[3]*b.y - a.x*b.z + a.y*b[3] + a.z*b.x,
@ -208,94 +208,94 @@ function Q_Mul(a, b) =
];
// Function: Q_Cumulative()
// Function: q_cumulative()
// Usage:
// Q_Cumulative(v);
// q_cumulative(v);
// Description:
// Given a list of Quaternions, cumulatively multiplies them, returning a list
// of each cumulative Quaternion product. It starts with the first quaternion
// given in the list, and applies successive quaternion rotations in list order.
// The quaternion in the returned list are normalized if each quaternion in v
// is normalized.
function Q_Cumulative(v, _i=0, _acc=[]) =
function q_cumulative(v, _i=0, _acc=[]) =
_i==len(v) ? _acc :
Q_Cumulative(
q_cumulative(
v, _i+1,
concat(
_acc,
[_i==0 ? v[_i] : Q_Mul(v[_i], last(_acc))]
[_i==0 ? v[_i] : q_mul(v[_i], last(_acc))]
)
);
// Function: Q_Dot()
// Function: q_dot()
// Usage:
// Q_Dot(a, b)
// q_dot(a, b)
// Description: Calculates the dot product between quaternions `a` and `b`.
function Q_Dot(a, b) =
assert( Q_is_quat(a) && Q_is_quat(b), "Invalid quaternion(s)" )
function q_dot(a, b) =
assert( is_quaternion(a) && is_quaternion(b), "Invalid quaternion(s)" )
a*b;
// Function: Q_Neg()
// Function: q_neg()
// Usage:
// Q_Neg(q)
// q_neg(q)
// Description: Returns the negative of quaternion `q`.
function Q_Neg(q) =
assert( Q_is_quat(q), "Invalid quaternion" )
function q_neg(q) =
assert( is_quaternion(q), "Invalid quaternion" )
-q;
// Function: Q_Conj()
// Function: q_conj()
// Usage:
// Q_Conj(q)
// q_conj(q)
// Description: Returns the conjugate of quaternion `q`.
function Q_Conj(q) =
assert( Q_is_quat(q), "Invalid quaternion" )
function q_conj(q) =
assert( is_quaternion(q), "Invalid quaternion" )
[-q.x, -q.y, -q.z, q[3]];
// Function: Q_Inverse()
// Function: q_inverse()
// Usage:
// qc = Q_Inverse(q)
// qc = q_inverse(q)
// Description: Returns the multiplication inverse of quaternion `q` that is normalized only if `q` is normalized.
function Q_Inverse(q) =
assert( Q_is_quat(q), "Invalid quaternion" )
let(q = _Qnorm(q) )
function q_inverse(q) =
assert( is_quaternion(q), "Invalid quaternion" )
let(q = _qnorm(q) )
[-q.x, -q.y, -q.z, q[3]];
// Function: Q_Norm()
// Function: q_norm()
// Usage:
// Q_Norm(q)
// q_norm(q)
// Description:
// Returns the `norm()` "length" of quaternion `q`.
// Normalized quaternions have unitary norm.
function Q_Norm(q) =
assert( Q_is_quat(q), "Invalid quaternion" )
function q_norm(q) =
assert( is_quaternion(q), "Invalid quaternion" )
norm(q);
// Function: Q_Normalize()
// Function: q_normalize()
// Usage:
// Q_Normalize(q)
// q_normalize(q)
// Description: Normalizes quaternion `q`, so that norm([W,X,Y,Z]) == 1.
function Q_Normalize(q) =
assert( Q_is_quat(q) , "Invalid quaternion" )
function q_normalize(q) =
assert( is_quaternion(q) , "Invalid quaternion" )
q/norm(q);
// Function: Q_Dist()
// Function: q_dist()
// Usage:
// Q_Dist(q1, q2)
// q_dist(q1, q2)
// Description: Returns the "distance" between two quaternions.
function Q_Dist(q1, q2) =
assert( Q_is_quat(q1) && Q_is_quat(q2), "Invalid quaternion(s)" )
function q_dist(q1, q2) =
assert( is_quaternion(q1) && is_quaternion(q2), "Invalid quaternion(s)" )
norm(q2-q1);
// Function: Q_Slerp()
// Function: q_slerp()
// Usage:
// Q_Slerp(q1, q2, u);
// q_slerp(q1, q2, u);
// Description:
// Returns a quaternion that is a spherical interpolation between two quaternions.
// Arguments:
@ -303,45 +303,45 @@ function Q_Dist(q1, q2) =
// q2 = The second quaternion. (u=1)
// u = The proportional value, from 0 to 1, of what part of the interpolation to return.
// Example(3D): Giving `u` as a Scalar
// a = QuatY(-135);
// b = QuatXYZ([0,-30,30]);
// a = quat_y(-135);
// b = quat_xyz([0,-30,30]);
// for (u=[0:0.1:1])
// Qrot(Q_Slerp(a, b, u))
// q_rot(q_slerp(a, b, u))
// right(80) cube([10,10,1]);
// #sphere(r=80);
// Example(3D): Giving `u` as a Range
// a = QuatZ(-135);
// b = QuatXYZ([90,0,-45]);
// for (q = Q_Slerp(a, b, [0:0.1:1]))
// Qrot(q) right(80) cube([10,10,1]);
// a = quat_z(-135);
// b = quat_xyz([90,0,-45]);
// for (q = q_slerp(a, b, [0:0.1:1]))
// q_rot(q) right(80) cube([10,10,1]);
// #sphere(r=80);
function Q_Slerp(q1, q2, u, _dot) =
function q_slerp(q1, q2, u, _dot) =
is_undef(_dot)
? assert(is_finite(u) || is_range(u) || is_vector(u), "Invalid interpolation coefficient(s)")
assert(Q_is_quat(q1) && Q_is_quat(q2), "Invalid quaternion(s)" )
assert(is_quaternion(q1) && is_quaternion(q2), "Invalid quaternion(s)" )
let(
_dot = q1*q2,
q1 = q1/norm(q1),
q2 = _dot<0 ? -q2/norm(q2) : q2/norm(q2),
dot = abs(_dot)
)
! is_finite(u) ? [for (uu=u) Q_Slerp(q1, q2, uu, dot)] :
Q_Slerp(q1, q2, u, dot)
! is_finite(u) ? [for (uu=u) q_slerp(q1, q2, uu, dot)] :
q_slerp(q1, q2, u, dot)
: _dot>0.9995
? _Qnorm(q1 + u*(q2-q1))
? _qnorm(q1 + u*(q2-q1))
: let( theta = u*acos(_dot),
q3 = _Qnorm(q2 - _dot*q1)
q3 = _qnorm(q2 - _dot*q1)
)
_Qnorm(q1*cos(theta) + q3*sin(theta));
_qnorm(q1*cos(theta) + q3*sin(theta));
// Function: Q_Matrix3()
// Function: q_matrix3()
// Usage:
// Q_Matrix3(q);
// q_matrix3(q);
// Description:
// Returns the 3x3 rotation matrix for the given normalized quaternion q.
function Q_Matrix3(q) =
let( q = Q_Normalize(q) )
function q_matrix3(q) =
let( q = q_normalize(q) )
[
[1-2*q[1]*q[1]-2*q[2]*q[2], 2*q[0]*q[1]-2*q[2]*q[3], 2*q[0]*q[2]+2*q[1]*q[3]],
[ 2*q[0]*q[1]+2*q[2]*q[3], 1-2*q[0]*q[0]-2*q[2]*q[2], 2*q[1]*q[2]-2*q[0]*q[3]],
@ -349,13 +349,13 @@ function Q_Matrix3(q) =
];
// Function: Q_Matrix4()
// Function: q_matrix4()
// Usage:
// Q_Matrix4(q);
// q_matrix4(q);
// Description:
// Returns the 4x4 rotation matrix for the given normalized quaternion q.
function Q_Matrix4(q) =
let( q = Q_Normalize(q) )
function q_matrix4(q) =
let( q = q_normalize(q) )
[
[1-2*q[1]*q[1]-2*q[2]*q[2], 2*q[0]*q[1]-2*q[2]*q[3], 2*q[0]*q[2]+2*q[1]*q[3], 0],
[ 2*q[0]*q[1]+2*q[2]*q[3], 1-2*q[0]*q[0]-2*q[2]*q[2], 2*q[1]*q[2]-2*q[0]*q[3], 0],
@ -364,115 +364,115 @@ function Q_Matrix4(q) =
];
// Function: Q_Axis()
// Function: q_axis()
// Usage:
// Q_Axis(q)
// q_axis(q)
// Description:
// Returns the axis of rotation of a normalized quaternion `q`.
// The input doesn't need to be normalized.
function Q_Axis(q) =
assert( Q_is_quat(q) , "Invalid quaternion" )
let( d = norm(_Qvec(q)) )
approx(d,0)? [0,0,1] : _Qvec(q)/d;
function q_axis(q) =
assert( is_quaternion(q) , "Invalid quaternion" )
let( d = norm(_qvec(q)) )
approx(d,0)? [0,0,1] : _qvec(q)/d;
// Function: Q_Angle()
// Function: q_angle()
// Usage:
// a = Q_Angle(q)
// a12 = Q_Angle(q1,q2);
// a = q_angle(q)
// a12 = q_angle(q1,q2);
// Description:
// If only q1 is given, returns the angle of rotation (in degrees) of that quaternion.
// If both q1 and q2 are given, returns the angle (in degrees) between them.
// The input quaternions don't need to be normalized.
function Q_Angle(q1,q2) =
assert(Q_is_quat(q1) && (is_undef(q2) || Q_is_quat(q2)), "Invalid quaternion(s)" )
let( n1 = is_undef(q2)? norm(_Qvec(q1)): norm(q1) )
function q_angle(q1,q2) =
assert(is_quaternion(q1) && (is_undef(q2) || is_quaternion(q2)), "Invalid quaternion(s)" )
let( n1 = is_undef(q2)? norm(_qvec(q1)): norm(q1) )
is_undef(q2)
? 2 * atan2(n1,_Qreal(q1))
? 2 * atan2(n1,_qreal(q1))
: let( q1 = q1/norm(q1),
q2 = q2/norm(q2) )
4 * atan2(norm(q1 - q2), norm(q1 + q2));
// Function&Module: Qrot()
// Function&Module: q_rot()
// Usage: As Module
// Qrot(q) ...
// q_rot(q) ...
// Usage: As Function
// pts = Qrot(q,p);
// pts = q_rot(q,p);
// Description:
// When called as a module, rotates all children by the rotation stored in quaternion `q`.
// When called as a function with a `p` argument, rotates the point or list of points in `p` by the rotation stored in quaternion `q`.
// When called as a function without a `p` argument, returns the affine3d rotation matrix for the rotation stored in quaternion `q`.
// Example(FlatSpin,VPD=225,VPT=[71,-26,16]):
// module shape() translate([80,0,0]) cube([10,10,1]);
// q = QuatXYZ([90,-15,-45]);
// Qrot(q) shape();
// q = quat_xyz([90,-15,-45]);
// q_rot(q) shape();
// #shape();
// Example(NORENDER):
// q = QuatXYZ([45,35,10]);
// mat4x4 = Qrot(q);
// q = quat_xyz([45,35,10]);
// mat4x4 = q_rot(q);
// Example(NORENDER):
// q = QuatXYZ([45,35,10]);
// pt = Qrot(q, p=[4,5,6]);
// q = quat_xyz([45,35,10]);
// pt = q_rot(q, p=[4,5,6]);
// Example(NORENDER):
// q = QuatXYZ([45,35,10]);
// pts = Qrot(q, p=[[2,3,4], [4,5,6], [9,2,3]]);
module Qrot(q) {
multmatrix(Q_Matrix4(q)) {
// q = quat_xyz([45,35,10]);
// pts = q_rot(q, p=[[2,3,4], [4,5,6], [9,2,3]]);
module q_rot(q) {
multmatrix(q_matrix4(q)) {
children();
}
}
function Qrot(q,p) =
is_undef(p)? Q_Matrix4(q) :
is_vector(p)? Qrot(q,[p])[0] :
apply(Q_Matrix4(q), p);
function q_rot(q,p) =
is_undef(p)? q_matrix4(q) :
is_vector(p)? q_rot(q,[p])[0] :
apply(q_matrix4(q), p);
// Module: Qrot_copies()
// Module: q_rot_copies()
// Usage:
// Qrot_copies(quats) ...
// q_rot_copies(quats) ...
// Description:
// For each quaternion given in the list `quats`, rotates to that orientation and creates a copy
// of all children. This is equivalent to `for (q=quats) Qrot(q) ...`.
// of all children. This is equivalent to `for (q=quats) q_rot(q) ...`.
// Arguments:
// quats = A list containing all quaternions to rotate to and create copies of all children for.
// Example:
// a = QuatZ(-135);
// b = QuatXYZ([0,-30,30]);
// Qrot_copies(Q_Slerp(a, b, [0:0.1:1]))
// a = quat_z(-135);
// b = quat_xyz([0,-30,30]);
// q_rot_copies(q_slerp(a, b, [0:0.1:1]))
// right(80) cube([10,10,1]);
// #sphere(r=80);
module Qrot_copies(quats) for (q=quats) Qrot(q) children();
module q_rot_copies(quats) for (q=quats) q_rot(q) children();
// Function: Q_Rotation()
// Function: q_rotation()
// Usage:
// Q_Rotation(R)
// q_rotation(R)
// Description:
// Returns a normalized quaternion corresponding to the rotation matrix R.
// R may be a 3x3 rotation matrix or a homogeneous 4x4 rotation matrix.
// The last row and last column of R are ignored for 4x4 matrices.
// It doesn't check whether R is in fact a rotation matrix.
// If R is not a rotation, the returned quaternion is an unpredictable quaternion .
function Q_Rotation(R) =
function q_rotation(R) =
assert( is_matrix(R,3,3) || is_matrix(R,4,4) ,
"Matrix is neither 3x3 nor 4x4")
let( tr = R[0][0]+R[1][1]+R[2][2] ) // R trace
tr>0
? let( r = 1+tr )
_Qnorm( _Qset([ R[1][2]-R[2][1], R[2][0]-R[0][2], R[0][1]-R[1][0] ], -r ) )
_qnorm( _qset([ R[1][2]-R[2][1], R[2][0]-R[0][2], R[0][1]-R[1][0] ], -r ) )
: let( i = max_index([ R[0][0], R[1][1], R[2][2] ]),
r = 1 + 2*R[i][i] -R[0][0] -R[1][1] -R[2][2] )
i==0 ? _Qnorm( _Qset( [ 4*r, (R[1][0]+R[0][1]), (R[0][2]+R[2][0]) ], (R[2][1]-R[1][2])) ):
i==1 ? _Qnorm( _Qset( [ (R[1][0]+R[0][1]), 4*r, (R[2][1]+R[1][2]) ], (R[0][2]-R[2][0])) ):
_Qnorm( _Qset( [ (R[2][0]+R[0][2]), (R[1][2]+R[2][1]), 4*r ], (R[1][0]-R[0][1])) ) ;
i==0 ? _qnorm( _qset( [ 4*r, (R[1][0]+R[0][1]), (R[0][2]+R[2][0]) ], (R[2][1]-R[1][2])) ):
i==1 ? _qnorm( _qset( [ (R[1][0]+R[0][1]), 4*r, (R[2][1]+R[1][2]) ], (R[0][2]-R[2][0])) ):
_qnorm( _qset( [ (R[2][0]+R[0][2]), (R[1][2]+R[2][1]), 4*r ], (R[1][0]-R[0][1])) ) ;
// Function&Module: Q_Rotation_path()
// Function&Module: q_rotation_path()
// Usage: As a function
// path = Q_Rotation_path(q1, n, q2);
// path = Q_Rotation_path(q1, n);
// path = q_rotation_path(q1, n, q2);
// path = q_rotation_path(q1, n);
// Usage: As a module
// Q_Rotation_path(q1, n, q2) ...
// q_rotation_path(q1, n, q2) ...
// Description:
// If q2 is undef and it is called as a function, the path, with length n+1 (n>=1), will be the
// cumulative multiplications of the matrix rotation of q1 by itself.
@ -488,50 +488,50 @@ function Q_Rotation(R) =
// q2 = The quaternion of the last rotation.
// n = An integer defining the path length ( path length = n+1).
// Example(3D): as a function
// a = QuatY(-135);
// b = QuatXYZ([0,-30,30]);
// for (M=Q_Rotation_path(a, 10, b))
// a = quat_y(-135);
// b = quat_xyz([0,-30,30]);
// for (M=q_rotation_path(a, 10, b))
// multmatrix(M)
// right(80) cube([10,10,1]);
// #sphere(r=80);
// Example(3D): as a module
// a = QuatY(-135);
// b = QuatXYZ([0,-30,30]);
// Q_Rotation_path(a, 10, b)
// a = quat_y(-135);
// b = quat_xyz([0,-30,30]);
// q_rotation_path(a, 10, b)
// right(80) cube([10,10,1]);
// #sphere(r=80);
// Example(3D): as a function
// a = QuatY(5);
// for (M=Q_Rotation_path(a, 10))
// a = quat_y(5);
// for (M=q_rotation_path(a, 10))
// multmatrix(M)
// right(80) cube([10,10,1]);
// #sphere(r=80);
// Example(3D): as a module
// a = QuatY(5);
// Q_Rotation_path(a, 10)
// a = quat_y(5);
// q_rotation_path(a, 10)
// right(80) cube([10,10,1]);
// #sphere(r=80);
function Q_Rotation_path(q1, n=1, q2) =
assert( Q_is_quat(q1) && (is_undef(q2) || Q_is_quat(q2) ), "Invalid quaternion(s)" )
function q_rotation_path(q1, n=1, q2) =
assert( is_quaternion(q1) && (is_undef(q2) || is_quaternion(q2) ), "Invalid quaternion(s)" )
assert( is_finite(n) && n>=1 && n==floor(n), "Invalid integer" )
assert( is_undef(q2) || ! approx(norm(q1+q2),0), "Quaternions cannot be opposed" )
is_undef(q2)
? [for( i=0, dR=Q_Matrix4(q1), R=dR; i<=n; i=i+1, R=dR*R ) R]
: let( q2 = Q_Normalize( q1*q2<0 ? -q2: q2 ),
dq = Q_pow( Q_Mul( q2, Q_Inverse(q1) ), 1/n ),
dR = Q_Matrix4(dq) )
[for( i=0, R=Q_Matrix4(q1); i<=n; i=i+1, R=dR*R ) R];
? [for( i=0, dR=q_matrix4(q1), R=dR; i<=n; i=i+1, R=dR*R ) R]
: let( q2 = q_normalize( q1*q2<0 ? -q2: q2 ),
dq = q_pow( q_mul( q2, q_inverse(q1) ), 1/n ),
dR = q_matrix4(dq) )
[for( i=0, R=q_matrix4(q1); i<=n; i=i+1, R=dR*R ) R];
module Q_Rotation_path(q1, n=1, q2) {
for(Mi=Q_Rotation_path(q1, n, q2))
module q_rotation_path(q1, n=1, q2) {
for(Mi=q_rotation_path(q1, n, q2))
multmatrix(Mi)
children();
}
// Function: Q_Nlerp()
// Function: q_nlerp()
// Usage:
// q = Q_Nlerp(q1, q2, u);
// q = q_nlerp(q1, q2, u);
// Description:
// Returns a quaternion that is a normalized linear interpolation between two quaternions
// when u is a number.
@ -543,33 +543,33 @@ module Q_Rotation_path(q1, n=1, q2) {
// q2 = The second quaternion. (u=1)
// u = A value (or a list of values), between 0 and 1, of the proportion(s) of each quaternion in the interpolation.
// Example(3D): Giving `u` as a Scalar
// a = QuatY(-135);
// b = QuatXYZ([0,-30,30]);
// a = quat_y(-135);
// b = quat_xyz([0,-30,30]);
// for (u=[0:0.1:1])
// Qrot(Q_Nlerp(a, b, u))
// q_rot(q_nlerp(a, b, u))
// right(80) cube([10,10,1]);
// #sphere(r=80);
// Example(3D): Giving `u` as a Range
// a = QuatZ(-135);
// b = QuatXYZ([90,0,-45]);
// for (q = Q_Nlerp(a, b, [0:0.1:1]))
// Qrot(q) right(80) cube([10,10,1]);
// a = quat_z(-135);
// b = quat_xyz([90,0,-45]);
// for (q = q_nlerp(a, b, [0:0.1:1]))
// q_rot(q) right(80) cube([10,10,1]);
// #sphere(r=80);
function Q_Nlerp(q1,q2,u) =
function q_nlerp(q1,q2,u) =
assert(is_finite(u) || is_range(u) || is_vector(u) ,
"Invalid interpolation coefficient(s)" )
assert(Q_is_quat(q1) && Q_is_quat(q2), "Invalid quaternion(s)" )
assert(is_quaternion(q1) && is_quaternion(q2), "Invalid quaternion(s)" )
assert( ! approx(norm(q1+q2),0), "Quaternions cannot be opposed" )
let( q1 = Q_Normalize(q1),
q2 = Q_Normalize(q2) )
let( q1 = q_normalize(q1),
q2 = q_normalize(q2) )
is_num(u)
? _Qnorm((1-u)*q1 + u*q2 )
: [for (ui=u) _Qnorm((1-ui)*q1 + ui*q2 ) ];
? _qnorm((1-u)*q1 + u*q2 )
: [for (ui=u) _qnorm((1-ui)*q1 + ui*q2 ) ];
// Function: Q_Squad()
// Function: q_squad()
// Usage:
// qn = Q_Squad(q1,q2,q3,q4,u);
// qn = q_squad(q1,q2,q3,q4,u);
// Description:
// Returns a quaternion that is a cubic spherical interpolation of the quaternions
// q1 and q4 taking the other two quaternions, q2 and q3, as parameter of a cubic
@ -586,71 +586,72 @@ function Q_Nlerp(q1,q2,u) =
// q4 = The end quaternion. (u=1)
// u = A value (or a list of values), of the proportion(s) of each quaternion in the cubic interpolation.
// Example(3D): Giving `u` as a Scalar
// a = QuatY(-135);
// b = QuatXYZ([-50,-50,120]);
// c = QuatXYZ([-50,-40,30]);
// d = QuatY(-45);
// a = quat_y(-135);
// b = quat_xyz([-50,-50,120]);
// c = quat_xyz([-50,-40,30]);
// d = quat_y(-45);
// color("red"){
// Qrot(b) right(80) cube([10,10,1]);
// Qrot(c) right(80) cube([10,10,1]);
// q_rot(b) right(80) cube([10,10,1]);
// q_rot(c) right(80) cube([10,10,1]);
// }
// for (u=[0:0.05:1])
// Qrot(Q_Squad(a, b, c, d, u))
// q_rot(q_squad(a, b, c, d, u))
// right(80) cube([10,10,1]);
// #sphere(r=80);
// Example(3D): Giving `u` as a Range
// a = QuatY(-135);
// b = QuatXYZ([-50,-50,120]);
// c = QuatXYZ([-50,-40,30]);
// d = QuatY(-45);
// for (q = Q_Squad(a, b, c, d, [0:0.05:1]))
// Qrot(q) right(80) cube([10,10,1]);
// a = quat_y(-135);
// b = quat_xyz([-50,-50,120]);
// c = quat_xyz([-50,-40,30]);
// d = quat_y(-45);
// for (q = q_squad(a, b, c, d, [0:0.05:1]))
// q_rot(q) right(80) cube([10,10,1]);
// #sphere(r=80);
function Q_Squad(q1,q2,q3,q4,u) =
function q_squad(q1,q2,q3,q4,u) =
assert(is_finite(u) || is_range(u) || is_vector(u) ,
"Invalid interpolation coefficient(s)" )
is_num(u)
? Q_Slerp( Q_Slerp(q1,q4,u), Q_Slerp(q2,q3,u), 2*u*(1-u))
: [for(ui=u) Q_Slerp( Q_Slerp(q1,q4,ui), Q_Slerp(q2,q3,ui), 2*ui*(1-ui) ) ];
? q_slerp( q_slerp(q1,q4,u), q_slerp(q2,q3,u), 2*u*(1-u))
: [for(ui=u) q_slerp( q_slerp(q1,q4,ui), q_slerp(q2,q3,ui), 2*ui*(1-ui) ) ];
// Function: Q_exp()
// Function: q_exp()
// Usage:
// q2 = Q_exp(q);
// q2 = q_exp(q);
// Description:
// Returns the quaternion that is the exponential of the quaternion q in base e
// The returned quaternion is usually not normalized.
function Q_exp(q) =
function q_exp(q) =
assert( is_vector(q,4), "Input is not a valid quaternion")
let( nv = norm(_Qvec(q)) ) // q may be equal to zero here!
exp(_Qreal(q))*Quat(_Qvec(q),2*nv);
let( nv = norm(_qvec(q)) ) // q may be equal to zero here!
exp(_qreal(q))*quat(_qvec(q),2*nv);
// Function: Q_ln()
// Function: q_ln()
// Usage:
// q2 = Q_ln(q);
// q2 = q_ln(q);
// Description:
// Returns the quaternion that is the natural logarithm of the quaternion q.
// The returned quaternion is usually not normalized and may be zero.
function Q_ln(q) =
assert(Q_is_quat(q), "Input is not a valid quaternion")
let( nq = norm(q),
nv = norm(_Qvec(q)) )
approx(nv,0) ? _Qset([0,0,0] , ln(nq) ) :
_Qset(_Qvec(q)*atan2(nv,_Qreal(q))/nv, ln(nq));
function q_ln(q) =
assert(is_quaternion(q), "Input is not a valid quaternion")
let(
nq = norm(q),
nv = norm(_qvec(q))
)
approx(nv,0) ? _qset([0,0,0] , ln(nq) ) :
_qset(_qvec(q)*atan2(nv,_qreal(q))/nv, ln(nq));
// Function: Q_pow()
// Function: q_pow()
// Usage:
// q2 = Q_pow(q, r);
// q2 = q_pow(q, r);
// Description:
// Returns the quaternion that is the power of the quaternion q to the real exponent r.
// The returned quaternion is normalized if `q` is normalized.
function Q_pow(q,r=1) =
assert( Q_is_quat(q) && is_finite(r),
"Invalid inputs")
let( theta = 2*atan2(norm(_Qvec(q)),_Qreal(q)) )
Quat(_Qvec(q), r*theta); // Q_exp(r*Q_ln(q));
function q_pow(q,r=1) =
assert( is_quaternion(q) && is_finite(r), "Invalid inputs")
let( theta = 2*atan2(norm(_qvec(q)),_qreal(q)) )
quat(_qvec(q), r*theta); // q_exp(r*q_ln(q));

View file

@ -1,15 +1,17 @@
#!/bin/sh
#!/bin/bash
VERFILE="version.scad"
vernums=$(grep ^BOSL_VERSION "$VERFILE" | sed 's/^.*[[]\([0-9,]*\)[]].*$/\1/')
major=$(echo "$vernums" | awk -F, '{print $1}')
minor=$(echo "$vernums" | awk -F, '{print $2}')
revision=$(echo "$vernums" | awk -F, '{print $3}')
if [[ "$(cat "$VERFILE")" =~ BOSL_VERSION.*=.*\[([0-9]+),\ *([0-9]+),\ *([0-9]+)\]\; ]]; then
major=${BASH_REMATCH[1]} minor=${BASH_REMATCH[2]} revision=${BASH_REMATCH[3]}
new_revision=$(( revision+1 ))
newrev=$(($revision+1))
echo "Current Version: $major.$minor.$revision"
echo "New Version: $major.$minor.$newrev"
sed -i '' 's/^BOSL_VERSION = .*$/BOSL_VERSION = ['"$major,$minor,$newrev];/g" $VERFILE
echo "Current Version: $major.$minor.$revision"
echo "New Version: $major.$minor.$new_revision"
sed -i.bak -e 's/^BOSL_VERSION = .*$/BOSL_VERSION = ['"$major,$minor,$new_revision];/g" "$VERFILE"
rm "$VERFILE".bak
else
echo "Could not extract version number from $VERFILE" >&2
exit 1
fi

View file

@ -1,17 +1,12 @@
#!/bin/bash
lib_comment_lines=$(grep '^// ' *.scad | wc -l)
lib_code_lines=$(grep '^ *[^ /]' *.scad | wc -l)
script_code_lines=$(grep '^ *[^ /]' scripts/*.sh scripts/*.py | wc -l)
example_code_lines=$(grep '^ *[^ /]' examples/*.scad | wc -l)
test_code_lines=$(grep '^ *[^ /]' tests/*.scad | wc -l)
tutorial_lines=$(grep '^ *[^ /]' tutorials/*.md | wc -l)
lib_comment_lines=$(cat -- *.scad | grep -c '^// ')
tutorial_lines=$(cat tutorials/*.md | grep -c '^ *[^ /]')
y=$(printf "%06d" 13)
printf "Documentation Lines : %6d\n" $(($lib_comment_lines+$tutorial_lines))
printf "Example Code Lines : %6d\n" $example_code_lines
printf "Library Code Lines : %6d\n" $lib_code_lines
printf "Support Script Lines: %6d\n" $script_code_lines
printf "Test Code Lines : %6d\n" $test_code_lines
printf '%-20s: %6d\n' \
'Documentation Lines' "$(( lib_comment_lines + tutorial_lines ))" \
'Example Code Lines' "$(cat examples/*.scad | grep -c '^ *[^ /]')" \
'Library Code Lines' "$(cat -- *.scad | grep -c '^ *[^ /]')" \
'Support Script Lines' "$(cat scripts/*.sh scripts/*.py | grep -c '^ *[^ /]')" \
'Test Code Lines' "$(cat tests/*.scad | grep -c '^ *[^ /]')"

View file

@ -1,40 +1,38 @@
#!/bin/bash
FORCED=""
FILES=""
DISPMD=""
DISPMD=0
GEN_ARGS=()
FILES=()
for opt in "$@" ; do
case $opt in
-f ) FORCED=$opt ;;
-d ) DISPMD=$opt ;;
-* ) echo "Unknown option $opt"; exit -1 ;;
* ) FILES="$FILES $opt" ;;
case "$opt" in
-f ) GEN_ARGS+=(-f) ;;
-d ) DISPMD=1 ;;
-* ) echo "Unknown option $opt" >&2; exit 1 ;;
* ) FILES+=("$opt") ;;
esac
done
if [[ "$FILES" != "" ]]; then
PREVIEW_LIBS="$FILES"
else
PREVIEW_LIBS="Shapes2d Shapes3d Transforms Distributors Mutators Attachments Paths FractalTree"
if (( ${#FILES[@]} == 0 )); then
FILES=(Shapes2d Shapes3d Transforms Distributors Mutators Attachments Paths FractalTree)
fi
dir="$(basename $PWD)"
if [ "$dir" = "BOSL2" ]; then
cd BOSL2.wiki
elif [ "$dir" != "BOSL2.wiki" ]; then
echo "Must run this script from the BOSL2 or BOSL2/BOSL2.wiki directories."
# Try to cd to the BOSL2.wiki directory if run from the BOSL2 root
if [[ "$(basename "$PWD")" != "BOSL2.wiki" ]]; then
if ! cd BOSL2.wiki; then
echo "BOSL2.wiki directory not found, try running from the BOSL2 or BOSL2/BOSL2.wiki directory" >&2
exit 1
fi
fi
rm -f tmp_*.scad
for base in $PREVIEW_LIBS; do
base="$(basename $base .md)"
for base in "${FILES[@]}"; do
base="$(basename "$base" .md)"
mkdir -p images/tutorials
rm -f images/tutorials/${base}_*.png images/tutorials/${base}_*.gif
echo "$base.md"
../scripts/tutorial_gen.py ../tutorials/$base.md -o Tutorial-$base.md $FORCED -I images/tutorials/ || exit 1
if [ "$DISPMD" != "" ]; then
open -a Typora Tutorial-$base.md
rm -f "images/tutorials/${base}"_*.png "images/tutorials/${base}"_*.gif
echo "${base}.md"
../scripts/tutorial_gen.py "../tutorials/${base}.md" -o "Tutorial-${base}.md" "${GEN_ARGS[@]}" -I images/tutorials/ || exit 1
if (( DISPMD )); then
open -a Typora "Tutorial-${base}.md"
fi
done

View file

@ -1,16 +1,16 @@
#!/bin/bash
if [[ ! -d BOSL2.wiki/.git ]] ; then
echo "Must be run from the BOSL2 directory, with the BOSL2.wiki repo inside."
exit -1
echo "Must be run from above the BOSL2.wiki repo." >&2
exit 1
fi
set -e # short-circuit if any command fails
cd BOSL2.wiki
rm -rf .git
git init
git add .
git commit -m "Purged wiki history."
git config pull.rebase false
git remote add origin git@github.com:revarbat/BOSL2.wiki.git
git push -u --force origin master
cd ..

View file

@ -1,35 +1,31 @@
#!/bin/bash
if [ "$(uname -s)" != "Darwin" ]; then
OPENSCAD=openscad
else
OPENSCAD=openscad
if [ "$(uname -s)" == "Darwin" ]; then
OPENSCAD=/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD
fi
if [ "$*" != "" ] ; then
INFILES="$*"
else
INFILES="tests/test_*.scad"
INFILES=("$@")
if (( ${#INFILES[@]} == 0 )); then
INFILES=(tests/test_*.scad)
fi
OUTCODE=0
for testscript in $INFILES ; do
repname="$(basename $testscript | sed 's/^test_//')"
testfile="tests/test_$repname"
if [ -f "$testfile" ] ; then
${OPENSCAD} -o out.echo --hardwarnings --check-parameters true --check-parameter-ranges true $testfile 2>&1
for testfile in "${INFILES[@]}"; do
if [[ -f "$testfile" ]] ; then
repname="$(basename "$testfile" | sed 's/^test_//')"
"${OPENSCAD}" -o out.echo --hardwarnings --check-parameters true --check-parameter-ranges true "$testfile" 2>&1
retcode=$?
res=$(cat out.echo)
if [ $retcode -eq 0 ] && [ "$res" = "" ] ; then
output=$(cat out.echo)
if (( retcode == 0 )) && [[ "$output" = "" ]]; then
echo "$repname: PASS"
else
echo "$repname: FAIL!"
cat out.echo
echo
OUTCODE=-1
echo "$output"
OUTCODE=1
fi
rm -f out.echo
fi
done
exit $OUTCODE
exit "$OUTCODE"

View file

@ -57,17 +57,41 @@
// Example: Rounded Edges, Untrimmed Corners
// cuboid([30,40,50], rounding=10, trimcorners=false);
// Example: Chamferring Selected Edges
// cuboid([30,40,50], chamfer=5, edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT], $fn=24);
// cuboid(
// [30,40,50], chamfer=5,
// edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT],
// $fn=24
// );
// Example: Rounding Selected Edges
// cuboid([30,40,50], rounding=5, edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT], $fn=24);
// cuboid(
// [30,40,50], rounding=5,
// edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT],
// $fn=24
// );
// Example: Negative Chamferring
// cuboid([30,40,50], chamfer=-5, edges=[TOP,BOT], except_edges=RIGHT, $fn=24);
// cuboid(
// [30,40,50], chamfer=-5,
// edges=[TOP,BOT], except_edges=RIGHT,
// $fn=24
// );
// Example: Negative Chamferring, Untrimmed Corners
// cuboid([30,40,50], chamfer=-5, edges=[TOP,BOT], except_edges=RIGHT, trimcorners=false, $fn=24);
// cuboid(
// [30,40,50], chamfer=-5,
// edges=[TOP,BOT], except_edges=RIGHT,
// trimcorners=false, $fn=24
// );
// Example: Negative Rounding
// cuboid([30,40,50], rounding=-5, edges=[TOP,BOT], except_edges=RIGHT, $fn=24);
// cuboid(
// [30,40,50], rounding=-5,
// edges=[TOP,BOT], except_edges=RIGHT,
// $fn=24
// );
// Example: Negative Rounding, Untrimmed Corners
// cuboid([30,40,50], rounding=-5, edges=[TOP,BOT], except_edges=RIGHT, trimcorners=false, $fn=24);
// cuboid(
// [30,40,50], rounding=-5,
// edges=[TOP,BOT], except_edges=RIGHT,
// trimcorners=false, $fn=24
// );
// Example: Standard Connectors
// cuboid(40) show_anchors();
module cuboid(
@ -87,9 +111,9 @@ module cuboid(
cnt = sum(e);
r = first_defined([chamfer, rounding, 0]);
c = [min(r,size.x/2), min(r,size.y/2), min(r,size.z/2)];
c2 = vmul(corner,c/2);
c2 = v_mul(corner,c/2);
$fn = is_finite(chamfer)? 4 : segs(r);
translate(vmul(corner, size/2-c)) {
translate(v_mul(corner, size/2-c)) {
if (cnt == 0 || approx(r,0)) {
translate(c2) cube(c, center=true);
} else if (cnt == 1) {
@ -130,6 +154,7 @@ module cuboid(
size = scalar_vec3(size);
edges = edges(edges, except=except_edges);
assert(is_vector(size,3));
assert(all_positive(size));
assert(is_undef(chamfer) || is_finite(chamfer));
assert(is_undef(rounding) || is_finite(rounding));
assert(is_undef(p1) || is_vector(p1));
@ -138,7 +163,7 @@ module cuboid(
if (!is_undef(p1)) {
if (!is_undef(p2)) {
translate(pointlist_bounds([p1,p2])[0]) {
cuboid(size=vabs(p2-p1), chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=ALLNEG) children();
cuboid(size=v_abs(p2-p1), chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=ALLNEG) children();
}
} else {
translate(p1) {
@ -184,7 +209,7 @@ module cuboid(
for (i = [0:3], axis=[0:1]) {
if (edges[axis][i]>0) {
vec = EDGE_OFFSETS[axis][i];
translate(vmul(vec/2, size+[ach,ach,-ach])) {
translate(v_mul(vec/2, size+[ach,ach,-ach])) {
rotate(majrots[axis]) {
cube([ach, ach, size[axis]], center=true);
}
@ -197,7 +222,7 @@ module cuboid(
for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
ce = corner_edges(edges, [xa,ya,za]);
if (ce.x + ce.y > 1) {
translate(vmul([xa,ya,za]/2, size+[ach-0.01,ach-0.01,-ach])) {
translate(v_mul([xa,ya,za]/2, size+[ach-0.01,ach-0.01,-ach])) {
cube([ach+0.01,ach+0.01,ach], center=true);
}
}
@ -209,7 +234,7 @@ module cuboid(
for (i = [0:3], axis=[0:1]) {
if (edges[axis][i]>0) {
vec = EDGE_OFFSETS[axis][i];
translate(vmul(vec/2, size+[2*ach,2*ach,-2*ach])) {
translate(v_mul(vec/2, size+[2*ach,2*ach,-2*ach])) {
rotate(majrots[axis]) {
zrot(45) cube([ach*sqrt(2), ach*sqrt(2), size[axis]+2.1*ach], center=true);
}
@ -271,7 +296,7 @@ module cuboid(
for (i = [0:3], axis=[0:1]) {
if (edges[axis][i]>0) {
vec = EDGE_OFFSETS[axis][i];
translate(vmul(vec/2, size+[ard,ard,-ard])) {
translate(v_mul(vec/2, size+[ard,ard,-ard])) {
rotate(majrots[axis]) {
cube([ard, ard, size[axis]], center=true);
}
@ -284,7 +309,7 @@ module cuboid(
for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
ce = corner_edges(edges, [xa,ya,za]);
if (ce.x + ce.y > 1) {
translate(vmul([xa,ya,za]/2, size+[ard-0.01,ard-0.01,-ard])) {
translate(v_mul([xa,ya,za]/2, size+[ard-0.01,ard-0.01,-ard])) {
cube([ard+0.01,ard+0.01,ard], center=true);
}
}
@ -296,7 +321,7 @@ module cuboid(
for (i = [0:3], axis=[0:1]) {
if (edges[axis][i]>0) {
vec = EDGE_OFFSETS[axis][i];
translate(vmul(vec/2, size+[2*ard,2*ard,-2*ard])) {
translate(v_mul(vec/2, size+[2*ard,2*ard,-2*ard])) {
rotate(majrots[axis]) {
cyl(l=size[axis]+2.1*ard, r=ard);
}
@ -363,10 +388,6 @@ function cuboid(
// Creates a rectangular prismoid shape with optional roundovers and chamfering.
// You can only round or chamfer the vertical(ish) edges. For those edges, you can
// specify rounding and/or chamferring per-edge, and for top and bottom separately.
// Note: if using chamfers or rounding, you **must** also include the hull.scad file:
// ```
// include <BOSL2/hull.scad>
// ```
//
// Arguments:
// size1 = [width, length] of the bottom end of the prism.
@ -374,12 +395,12 @@ function cuboid(
// h|l = Height of the prism.
// shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
// ---
// rounding = The roundover radius for the vertical-ish edges of the prismoid. Requires including hull.scad. If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding)
// rounding1 = The roundover radius for the bottom of the vertical-ish edges of the prismoid. Requires including hull.scad. If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
// rounding2 = The roundover radius for the top of the vertical-ish edges of the prismoid. Requires including hull.scad. If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
// chamfer = The chamfer size for the vertical-ish edges of the prismoid. Requires including hull.scad. If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no chamfer)
// chamfer1 = The chamfer size for the bottom of the vertical-ish edges of the prismoid. Requires including hull.scad. If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
// chamfer2 = The chamfer size for the top of the vertical-ish edges of the prismoid. Requires including hull.scad. If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
// rounding = The roundover radius for the vertical-ish edges of the prismoid. If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding)
// rounding1 = The roundover radius for the bottom of the vertical-ish edges of the prismoid. If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
// rounding2 = The roundover radius for the top of the vertical-ish edges of the prismoid. If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
// chamfer = The chamfer size for the vertical-ish edges of the prismoid. If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no chamfer)
// chamfer1 = The chamfer size for the bottom of the vertical-ish edges of the prismoid. If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
// chamfer2 = The chamfer size for the top of the vertical-ish edges of the prismoid. If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
@ -401,25 +422,22 @@ function cuboid(
// Example(FlatSpin,VPD=160,VPT=[0,0,10]): Shifting/Skewing
// prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5]);
// Example: Rounding
// include <BOSL2/hull.scad>
// prismoid(100, 80, rounding=10, h=30);
// Example: Outer Chamfer Only
// include <BOSL2/hull.scad>
// prismoid(100, 80, chamfer=5, h=30);
// Example: Gradiant Rounding
// include <BOSL2/hull.scad>
// prismoid(100, 80, rounding1=10, rounding2=0, h=30);
// Example: Per Corner Rounding
// include <BOSL2/hull.scad>
// prismoid(100, 80, rounding=[0,5,10,15], h=30);
// Example: Per Corner Chamfer
// include <BOSL2/hull.scad>
// prismoid(100, 80, chamfer=[0,5,10,15], h=30);
// Example: Mixing Chamfer and Rounding
// include <BOSL2/hull.scad>
// prismoid(100, 80, chamfer=[0,5,0,10], rounding=[5,0,10,0], h=30);
// prismoid(
// 100, 80, h=30,
// chamfer=[0,5,0,10],
// rounding=[5,0,10,0]
// );
// Example: Really Mixing It Up
// include <BOSL2/hull.scad>
// prismoid(
// size1=[100,80], size2=[80,60], h=20,
// chamfer1=[0,5,0,10], chamfer2=[5,0,10,0],
@ -448,6 +466,10 @@ module prismoid(
eps = pow(2,-14);
size1 = is_num(size1)? [size1,size1] : size1;
size2 = is_num(size2)? [size2,size2] : size2;
assert(all_nonnegative(size1));
assert(all_nonnegative(size2));
assert(size1.x + size2.x > 0);
assert(size1.y + size2.y > 0);
s1 = [max(size1.x, eps), max(size1.y, eps)];
s2 = [max(size2.x, eps), max(size2.y, eps)];
rounding1 = default(rounding1, rounding);
@ -499,8 +521,8 @@ function prismoid(
let(
corners = [[1,1],[1,-1],[-1,-1],[-1,1]] * 0.5,
points = [
for (p=corners) point3d(vmul(s2,p), +h/2) + shiftby,
for (p=corners) point3d(vmul(s1,p), -h/2)
for (p=corners) point3d(v_mul(s2,p), +h/2) + shiftby,
for (p=corners) point3d(v_mul(s1,p), -h/2)
],
faces=[
[0,1,2], [0,2,3], [0,4,5], [0,5,1],
@ -551,10 +573,6 @@ function prismoid(
// You can only round or chamfer the vertical(ish) edges. For those edges, you can
// specify rounding and/or chamferring per-edge, and for top and bottom, inside and
// outside separately.
// Note: if using chamfers or rounding, you **must** also include the hull.scad file:
// ```
// include <BOSL2/hull.scad>
// ```
// Arguments:
// h|l = The height or length of the rectangular tube. Default: 1
// size = The outer [X,Y] size of the rectangular tube.
@ -588,33 +606,42 @@ function prismoid(
// rect_tube(isize=[60,80], wall=5, h=30);
// rect_tube(size=[100,60], isize=[90,50], h=30);
// rect_tube(size1=[100,60], size2=[70,40], wall=5, h=30);
// rect_tube(size1=[100,60], size2=[70,40], isize1=[40,20], isize2=[65,35], h=15);
// Example:
// rect_tube(
// size1=[100,60], size2=[70,40],
// isize1=[40,20], isize2=[65,35], h=15
// );
// Example: Outer Rounding Only
// include <BOSL2/hull.scad>
// rect_tube(size=100, wall=5, rounding=10, irounding=0, h=30);
// Example: Outer Chamfer Only
// include <BOSL2/hull.scad>
// rect_tube(size=100, wall=5, chamfer=5, ichamfer=0, h=30);
// Example: Outer Rounding, Inner Chamfer
// include <BOSL2/hull.scad>
// rect_tube(size=100, wall=5, rounding=10, ichamfer=8, h=30);
// Example: Inner Rounding, Outer Chamfer
// include <BOSL2/hull.scad>
// rect_tube(size=100, wall=5, chamfer=10, irounding=8, h=30);
// Example: Gradiant Rounding
// include <BOSL2/hull.scad>
// rect_tube(size1=100, size2=80, wall=5, rounding1=10, rounding2=0, irounding1=8, irounding2=0, h=30);
// rect_tube(
// size1=100, size2=80, wall=5, h=30,
// rounding1=10, rounding2=0,
// irounding1=8, irounding2=0
// );
// Example: Per Corner Rounding
// include <BOSL2/hull.scad>
// rect_tube(size=100, wall=10, rounding=[0,5,10,15], irounding=0, h=30);
// rect_tube(
// size=100, wall=10, h=30,
// rounding=[0,5,10,15], irounding=0
// );
// Example: Per Corner Chamfer
// include <BOSL2/hull.scad>
// rect_tube(size=100, wall=10, chamfer=[0,5,10,15], ichamfer=0, h=30);
// rect_tube(
// size=100, wall=10, h=30,
// chamfer=[0,5,10,15], ichamfer=0
// );
// Example: Mixing Chamfer and Rounding
// include <BOSL2/hull.scad>
// rect_tube(size=100, wall=10, chamfer=[0,5,0,10], ichamfer=0, rounding=[5,0,10,0], irounding=0, h=30);
// rect_tube(
// size=100, wall=10, h=30,
// chamfer=[0,5,0,10], ichamfer=0,
// rounding=[5,0,10,0], irounding=0
// );
// Example: Really Mixing It Up
// include <BOSL2/hull.scad>
// rect_tube(
// size1=[100,80], size2=[80,60],
// isize1=[50,30], isize2=[70,50], h=20,
@ -838,7 +865,11 @@ function right_triangle(size=[1,1,1], center, anchor, spin=0, orient=UP) =
// }
//
// Example: Putting it all together
// cyl(l=40, d1=25, d2=15, chamfer1=10, chamfang1=30, from_end=true, rounding2=5);
// cyl(
// l=40, d1=25, d2=15,
// chamfer1=10, chamfang1=30,
// from_end=true, rounding2=5
// );
//
// Example: External Chamfers
// cyl(l=50, r=30, chamfer=-5, chamfang=30, $fa=1, $fs=1);
@ -1504,6 +1535,9 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or
// Usage: Typical
// teardrop(h|l, r, <ang>, <cap_h>, ...);
// teardrop(h|l, d=, <ang=>, <cap_h=>, ...);
// Usage: Psuedo-Conical
// teardrop(h|l, r1=, r2=, <ang=>, <cap_h1=>, <cap_h2=>, ...);
// teardrop(h|l, d1=, d2=, <ang=>, <cap_h1=>, <cap_h2=>, ...);
// Usage: Attaching Children
// teardrop(h|l, r, ...) <attachments>;
//
@ -1513,33 +1547,71 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or
// ang = Angle of hat walls from the Z axis. Default: 45 degrees
// cap_h = If given, height above center where the shape will be truncated. Default: `undef` (no truncation)
// ---
// d = Diameter of circular portion of bottom. (Use instead of r)
// r1 = Radius of circular portion of the front end of the teardrop shape.
// r2 = Radius of circular portion of the back end of the teardrop shape.
// d = Diameter of circular portion of the teardrop shape.
// d1 = Diameter of circular portion of the front end of the teardrop shape.
// d2 = Diameter of circular portion of the back end of the teardrop shape.
// cap_h1 = If given, height above center where the shape will be truncated, on the front side. Default: `undef` (no truncation)
// cap_h2 = If given, height above center where the shape will be truncated, on the back side. Default: `undef` (no truncation)
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
//
// Extra Anchors:
// cap = The center of the top of the cap, oriented with the cap face normal.
// cap_fwd = The front edge of the cap.
// cap_back = The back edge of the cap.
//
// Example: Typical Shape
// teardrop(r=30, h=10, ang=30);
// Example: Crop Cap
// teardrop(r=30, h=10, ang=30, cap_h=40);
// Example: Close Crop
// teardrop(r=30, h=10, ang=30, cap_h=20);
// Example: Standard Connectors
// teardrop(r=30, h=10, ang=30) show_anchors();
module teardrop(h, r, ang=45, cap_h, d, l, anchor=CENTER, spin=0, orient=UP)
// Example: Psuedo-Conical
// teardrop(r1=20, r2=30, h=40, cap_h1=25, cap_h2=35);
// Example: Standard Conical Connectors
// teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16)
// show_anchors(custom=false);
// Example(Spin,VPD=275): 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)
{
r = get_radius(r=r, d=d, dflt=1);
r1 = get_radius(r=r, r1=r1, d=d, d1=d1, dflt=1);
r2 = get_radius(r=r, r1=r2, d=d, d1=d2, dflt=1);
l = first_defined([l, h, 1]);
tip_y = adj_ang_to_hyp(r, 90-ang);
cap_h = min(default(cap_h,tip_y), tip_y);
tip_y1 = adj_ang_to_hyp(r1, 90-ang);
tip_y2 = adj_ang_to_hyp(r2, 90-ang);
cap_h1 = min(first_defined([cap_h1, cap_h, tip_y1]), tip_y1);
cap_h2 = min(first_defined([cap_h2, cap_h, tip_y2]), tip_y2);
capvec = unit([0, cap_h1-cap_h2, l]);
anchors = [
["cap", [0,0,cap_h], UP, 0]
anchorpt("cap", [0,0,(cap_h1+cap_h2)/2], capvec),
anchorpt("cap_fwd", [0,-l/2,cap_h1], unit((capvec+FWD)/2)),
anchorpt("cap_back", [0,+l/2,cap_h2], unit((capvec+BACK)/2), 180),
];
attachable(anchor,spin,orient, r=r, l=l, axis=BACK, anchors=anchors) {
attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK, anchors=anchors) {
rot(from=UP,to=FWD) {
if (l > 0) {
linear_extrude(height=l, center=true, slices=2) {
teardrop2d(r=r, ang=ang, cap_h=cap_h);
if (r1 == r2) {
linear_extrude(height=l, center=true, slices=2) {
teardrop2d(r=r1, ang=ang, cap_h=cap_h);
}
} else {
hull() {
up(l/2-0.001) {
linear_extrude(height=0.001, center=false) {
teardrop2d(r=r1, ang=ang, cap_h=cap_h1);
}
}
down(l/2) {
linear_extrude(height=0.001, center=false) {
teardrop2d(r=r2, ang=ang, cap_h=cap_h2);
}
}
}
}
}
}
@ -1702,9 +1774,15 @@ module pie_slice(
//
// Example:
// union() {
// translate([0,2,-4]) cube([20, 4, 24], anchor=BOTTOM);
// translate([0,-10,-4]) cube([20, 20, 4], anchor=BOTTOM);
// color("green") interior_fillet(l=20, r=10, spin=180, orient=RIGHT);
// translate([0,2,-4])
// cube([20, 4, 24], anchor=BOTTOM);
// translate([0,-10,-4])
// cube([20, 20, 4], anchor=BOTTOM);
// color("green")
// interior_fillet(
// l=20, r=10,
// spin=180, orient=RIGHT
// );
// }
//
// Example:
@ -1762,21 +1840,30 @@ module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, anchor=FRONT+LEFT, spi
// orient = Vector to rotate top towards. See [orient](attachments.scad#orient). Default: `UP`
// Example:
// heightfield(size=[100,100], bottom=-20, data=[
// for (y=[-180:4:180]) [for(x=[-180:4:180]) 10*cos(3*norm([x,y]))]
// for (y=[-180:4:180]) [
// for(x=[-180:4:180])
// 10*cos(3*norm([x,y]))
// ]
// ]);
// Example:
// intersection() {
// heightfield(size=[100,100], data=[
// for (y=[-180:5:180]) [for(x=[-180:5:180]) 10+5*cos(3*x)*sin(3*y)]
// for (y=[-180:5:180]) [
// for(x=[-180:5:180])
// 10+5*cos(3*x)*sin(3*y)
// ]
// ]);
// cylinder(h=50,d=100);
// }
// Example(NORENDER): Heightfield by Function
// Example: Heightfield by Function
// fn = function (x,y) 10*sin(x*360)*cos(y*360);
// heightfield(size=[100,100], data=fn);
// Example(NORENDER): Heightfield by Function, with Specific Ranges
// Example: Heightfield by Function, with Specific Ranges
// fn = function (x,y) 2*cos(5*norm([x,y]));
// heightfield(size=[100,100], bottom=-20, data=fn, xrange=[-180:2:180], yrange=[-180:2:180]);
// heightfield(
// 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);

View file

@ -307,15 +307,15 @@ module stroke(
multmatrix(mat) polygon(endcap_shape2);
}
} else {
quatsums = Q_Cumulative([
quatsums = q_cumulative([
for (i = idx(path2,e=-2)) let(
vec1 = i==0? UP : unit(path2[i]-path2[i-1], UP),
vec2 = unit(path2[i+1]-path2[i], UP),
axis = vector_axis(vec1,vec2),
ang = vector_angle(vec1,vec2)
) Quat(axis,ang)
) quat(axis,ang)
]);
rotmats = [for (q=quatsums) Q_Matrix4(q)];
rotmats = [for (q=quatsums) q_matrix4(q)];
sides = [
for (i = idx(path2,e=-2))
quantup(segs(max(widths[i],widths[i+1])/2),4)
@ -959,7 +959,7 @@ function rect(size=1, center, rounding=0, chamfer=0, anchor, spin=0) =
[-size.x/2, size.y/2],
[ size.x/2, size.y/2]
]
) rot(spin, p=move(-vmul(anchor,size/2), p=path)) :
) rot(spin, p=move(-v_mul(anchor,size/2), p=path)) :
let(
chamfer = is_list(chamfer)? chamfer : [for (i=[0:3]) chamfer],
rounding = is_list(rounding)? rounding : [for (i=[0:3]) rounding],
@ -978,7 +978,7 @@ function rect(size=1, center, rounding=0, chamfer=0, anchor, spin=0) =
quad = quadorder[i],
inset = insets[quad],
cverts = quant(segs(inset),4)/4,
cp = vmul(size/2-[inset,inset], quadpos[quad]),
cp = v_mul(size/2-[inset,inset], quadpos[quad]),
step = 90/cverts,
angs =
chamfer[quad] > 0? [0,-90]-90*[i,i] :

View file

@ -812,24 +812,30 @@ function _skin_tangent_match(poly1, poly2) =
newbig = polygon_shift(big, shift),
repeat_counts = [for(i=[0:len(small)-1]) posmod(cutpts[i]-select(cutpts,i-1),len(big))],
newsmall = repeat_entries(small,repeat_counts)
)
assert(len(newsmall)==len(newbig), "Tangent alignment failed, probably because of insufficient points or a concave curve")
swap ? [newbig, newsmall] : [newsmall, newbig];
)
assert(len(newsmall)==len(newbig), "Tangent alignment failed, probably because of insufficient points or a concave curve")
swap ? [newbig, newsmall] : [newsmall, newbig];
function _find_one_tangent(curve, edge, curve_offset=[0,0,0], closed=true) =
let(
angles =
[for(i=[0:len(curve)-(closed?1:2)])
let(
plane = plane3pt( edge[0], edge[1], curve[i]),
tangent = [curve[i], select(curve,i+1)]
)
plane_line_angle(plane,tangent)],
zero_cross = [for(i=[0:len(curve)-(closed?1:2)]) if (sign(angles[i]) != sign(select(angles,i+1))) i],
d = [for(i=zero_cross) distance_from_line(edge, curve[i]+curve_offset)]
)
zero_cross[min_index(d)];
let(
angles = [
for (i = [0:len(curve)-(closed?1:2)])
let(
plane = plane3pt( edge[0], edge[1], curve[i]),
tangent = [curve[i], select(curve,i+1)]
) plane_line_angle(plane,tangent)
],
zero_cross = [
for (i = [0:len(curve)-(closed?1:2)])
if (sign(angles[i]) != sign(select(angles,i+1)))
i
],
d = [
for (i = zero_cross)
point_line_distance(curve[i]+curve_offset, edge)
]
) zero_cross[min_index(d)];
// Function: associate_vertices()

View file

@ -27,6 +27,7 @@ include <quaternions.scad>
include <affine.scad>
include <coords.scad>
include <geometry.scad>
include <hull.scad>
include <regions.scad>
include <strings.scad>
include <skin.scad>

View file

@ -1264,4 +1264,36 @@ module test_poly_add(){
}
test_poly_add();
module test_root_find(){
flist = [
function(x) x*x*x-2*x-5,
function(x) 1-1/x/x,
function(x) pow(x-3,3),
function(x) pow(x-2,5),
function(x) (let(xi=0.61489) -3062*(1-xi)*exp(-x)/(xi+(1-xi)*exp(-x)) -1013 + 1628/x),
function(x) exp(x)-2-.01/x/x + .000002/x/x/x,
];
fint=[
[0,4],
[1e-4, 4],
[0,6],
[0,4],
[1e-4,5],
[-1,4]
];
answers = [2.094551481542328,
1,
3,
2,
1.037536033287040,
0.7032048403631350
];
roots = [for(i=idx(flist)) root_find(flist[i], fint[i][0], fint[i][1])];
assert_approx(roots, answers, 1e-10);
}
test_root_find();
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -1,403 +1,384 @@
include <../std.scad>
include <../strings.scad>
function rec_cmp(a,b,eps=1e-9) =
typeof(a)!=typeof(b)? false :
is_num(a)? approx(a,b,eps=eps) :
is_list(a)? len(a)==len(b) && all([for (i=idx(a)) rec_cmp(a[i],b[i],eps=eps)]) :
a == b;
function Qstandard(q) = sign([for(qi=q) if( ! approx(qi,0)) qi,0 ][0])*q;
function _q_standard(q) = sign([for(qi=q) if( ! approx(qi,0)) qi,0 ][0])*q;
module verify_f(actual,expected) {
if (!rec_cmp(actual,expected)) {
echo(str("Expected: ",fmt_float(expected,10)));
echo(str(" : ",expected));
echo(str("Actual : ",fmt_float(actual,10)));
echo(str(" : ",actual));
echo(str("Delta : ",fmt_float(expected-actual,10)));
echo(str(" : ",expected-actual));
assert(approx(expected,actual));
}
module test_is_quaternion() {
assert_approx(is_quaternion([0]),false);
assert_approx(is_quaternion([0,0,0,0]),false);
assert_approx(is_quaternion([1,0,2,0]),true);
assert_approx(is_quaternion([1,0,2,0,0]),false);
}
test_is_quaternion();
module test_Q_is_quat() {
verify_f(Q_is_quat([0]),false);
verify_f(Q_is_quat([0,0,0,0]),false);
verify_f(Q_is_quat([1,0,2,0]),true);
verify_f(Q_is_quat([1,0,2,0,0]),false);
module test_quat() {
assert_approx(quat(UP,0),[0,0,0,1]);
assert_approx(quat(FWD,0),[0,0,0,1]);
assert_approx(quat(LEFT,0),[0,0,0,1]);
assert_approx(quat(UP,45),[0,0,0.3826834324,0.9238795325]);
assert_approx(quat(LEFT,45),[-0.3826834324, 0, 0, 0.9238795325]);
assert_approx(quat(BACK,45),[0,0.3826834323,0,0.9238795325]);
assert_approx(quat(FWD+RIGHT,30),[0.1830127019, -0.1830127019, 0, 0.9659258263]);
}
test_Q_is_quat();
test_quat();
module test_Quat() {
verify_f(Quat(UP,0),[0,0,0,1]);
verify_f(Quat(FWD,0),[0,0,0,1]);
verify_f(Quat(LEFT,0),[0,0,0,1]);
verify_f(Quat(UP,45),[0,0,0.3826834324,0.9238795325]);
verify_f(Quat(LEFT,45),[-0.3826834324, 0, 0, 0.9238795325]);
verify_f(Quat(BACK,45),[0,0.3826834323,0,0.9238795325]);
verify_f(Quat(FWD+RIGHT,30),[0.1830127019, -0.1830127019, 0, 0.9659258263]);
module test_quat_x() {
assert_approx(quat_x(0),[0,0,0,1]);
assert_approx(quat_x(35),[0.3007057995,0,0,0.9537169507]);
assert_approx(quat_x(45),[0.3826834324,0,0,0.9238795325]);
}
test_Quat();
test_quat_x();
module test_QuatX() {
verify_f(QuatX(0),[0,0,0,1]);
verify_f(QuatX(35),[0.3007057995,0,0,0.9537169507]);
verify_f(QuatX(45),[0.3826834324,0,0,0.9238795325]);
module test_quat_y() {
assert_approx(quat_y(0),[0,0,0,1]);
assert_approx(quat_y(35),[0,0.3007057995,0,0.9537169507]);
assert_approx(quat_y(45),[0,0.3826834323,0,0.9238795325]);
}
test_QuatX();
test_quat_y();
module test_QuatY() {
verify_f(QuatY(0),[0,0,0,1]);
verify_f(QuatY(35),[0,0.3007057995,0,0.9537169507]);
verify_f(QuatY(45),[0,0.3826834323,0,0.9238795325]);
module test_quat_z() {
assert_approx(quat_z(0),[0,0,0,1]);
assert_approx(quat_z(36),[0,0,0.3090169944,0.9510565163]);
assert_approx(quat_z(45),[0,0,0.3826834324,0.9238795325]);
}
test_QuatY();
test_quat_z();
module test_QuatZ() {
verify_f(QuatZ(0),[0,0,0,1]);
verify_f(QuatZ(36),[0,0,0.3090169944,0.9510565163]);
verify_f(QuatZ(45),[0,0,0.3826834324,0.9238795325]);
module test_quat_xyz() {
assert_approx(quat_xyz([0,0,0]), [0,0,0,1]);
assert_approx(quat_xyz([30,0,0]), [0.2588190451, 0, 0, 0.9659258263]);
assert_approx(quat_xyz([90,0,0]), [0.7071067812, 0, 0, 0.7071067812]);
assert_approx(quat_xyz([-270,0,0]), [-0.7071067812, 0, 0, -0.7071067812]);
assert_approx(quat_xyz([180,0,0]), [1,0,0,0]);
assert_approx(quat_xyz([270,0,0]), [0.7071067812, 0, 0, -0.7071067812]);
assert_approx(quat_xyz([-90,0,0]), [-0.7071067812, 0, 0, 0.7071067812]);
assert_approx(quat_xyz([360,0,0]), [0,0,0,-1]);
assert_approx(quat_xyz([0,0,0]), [0,0,0,1]);
assert_approx(quat_xyz([0,30,0]), [0, 0.2588190451, 0, 0.9659258263]);
assert_approx(quat_xyz([0,90,0]), [0, 0.7071067812, 0, 0.7071067812]);
assert_approx(quat_xyz([0,-270,0]), [0, -0.7071067812, 0, -0.7071067812]);
assert_approx(quat_xyz([0,180,0]), [0,1,0,0]);
assert_approx(quat_xyz([0,270,0]), [0, 0.7071067812, 0, -0.7071067812]);
assert_approx(quat_xyz([0,-90,0]), [0, -0.7071067812, 0, 0.7071067812]);
assert_approx(quat_xyz([0,360,0]), [0,0,0,-1]);
assert_approx(quat_xyz([0,0,0]), [0,0,0,1]);
assert_approx(quat_xyz([0,0,30]), [0, 0, 0.2588190451, 0.9659258263]);
assert_approx(quat_xyz([0,0,90]), [0, 0, 0.7071067812, 0.7071067812]);
assert_approx(quat_xyz([0,0,-270]), [0, 0, -0.7071067812, -0.7071067812]);
assert_approx(quat_xyz([0,0,180]), [0,0,1,0]);
assert_approx(quat_xyz([0,0,270]), [0, 0, 0.7071067812, -0.7071067812]);
assert_approx(quat_xyz([0,0,-90]), [0, 0, -0.7071067812, 0.7071067812]);
assert_approx(quat_xyz([0,0,360]), [0,0,0,-1]);
assert_approx(quat_xyz([30,30,30]), [0.1767766953, 0.3061862178, 0.1767766953, 0.9185586535]);
assert_approx(quat_xyz([12,34,56]), [-0.04824789229, 0.3036636044, 0.4195145429, 0.8540890495]);
}
test_QuatZ();
test_quat_xyz();
module test_QuatXYZ() {
verify_f(QuatXYZ([0,0,0]), [0,0,0,1]);
verify_f(QuatXYZ([30,0,0]), [0.2588190451, 0, 0, 0.9659258263]);
verify_f(QuatXYZ([90,0,0]), [0.7071067812, 0, 0, 0.7071067812]);
verify_f(QuatXYZ([-270,0,0]), [-0.7071067812, 0, 0, -0.7071067812]);
verify_f(QuatXYZ([180,0,0]), [1,0,0,0]);
verify_f(QuatXYZ([270,0,0]), [0.7071067812, 0, 0, -0.7071067812]);
verify_f(QuatXYZ([-90,0,0]), [-0.7071067812, 0, 0, 0.7071067812]);
verify_f(QuatXYZ([360,0,0]), [0,0,0,-1]);
verify_f(QuatXYZ([0,0,0]), [0,0,0,1]);
verify_f(QuatXYZ([0,30,0]), [0, 0.2588190451, 0, 0.9659258263]);
verify_f(QuatXYZ([0,90,0]), [0, 0.7071067812, 0, 0.7071067812]);
verify_f(QuatXYZ([0,-270,0]), [0, -0.7071067812, 0, -0.7071067812]);
verify_f(QuatXYZ([0,180,0]), [0,1,0,0]);
verify_f(QuatXYZ([0,270,0]), [0, 0.7071067812, 0, -0.7071067812]);
verify_f(QuatXYZ([0,-90,0]), [0, -0.7071067812, 0, 0.7071067812]);
verify_f(QuatXYZ([0,360,0]), [0,0,0,-1]);
verify_f(QuatXYZ([0,0,0]), [0,0,0,1]);
verify_f(QuatXYZ([0,0,30]), [0, 0, 0.2588190451, 0.9659258263]);
verify_f(QuatXYZ([0,0,90]), [0, 0, 0.7071067812, 0.7071067812]);
verify_f(QuatXYZ([0,0,-270]), [0, 0, -0.7071067812, -0.7071067812]);
verify_f(QuatXYZ([0,0,180]), [0,0,1,0]);
verify_f(QuatXYZ([0,0,270]), [0, 0, 0.7071067812, -0.7071067812]);
verify_f(QuatXYZ([0,0,-90]), [0, 0, -0.7071067812, 0.7071067812]);
verify_f(QuatXYZ([0,0,360]), [0,0,0,-1]);
verify_f(QuatXYZ([30,30,30]), [0.1767766953, 0.3061862178, 0.1767766953, 0.9185586535]);
verify_f(QuatXYZ([12,34,56]), [-0.04824789229, 0.3036636044, 0.4195145429, 0.8540890495]);
module test_q_from_to() {
assert_approx(q_mul(q_from_to([1,2,3], [4,5,2]),q_from_to([4,5,2], [1,2,3])), q_ident());
assert_approx(q_matrix4(q_from_to([1,2,3], [4,5,2])), rot(from=[1,2,3],to=[4,5,2]));
assert_approx(q_rot(q_from_to([1,2,3], -[1,2,3]),[1,2,3]), -[1,2,3]);
assert_approx(unit(q_rot(q_from_to([1,2,3], [4,5,2]),[1,2,3])), unit([4,5,2]));
}
test_QuatXYZ();
test_q_from_to();
module test_Q_From_to() {
verify_f(Q_Mul(Q_From_to([1,2,3], [4,5,2]),Q_From_to([4,5,2], [1,2,3])), Q_Ident());
verify_f(Q_Matrix4(Q_From_to([1,2,3], [4,5,2])), rot(from=[1,2,3],to=[4,5,2]));
verify_f(Qrot(Q_From_to([1,2,3], -[1,2,3]),[1,2,3]), -[1,2,3]);
verify_f(unit(Qrot(Q_From_to([1,2,3], [4,5,2]),[1,2,3])), unit([4,5,2]));
module test_q_ident() {
assert_approx(q_ident(), [0,0,0,1]);
}
test_Q_From_to();
test_q_ident();
module test_Q_Ident() {
verify_f(Q_Ident(), [0,0,0,1]);
module test_q_add_s() {
assert_approx(q_add_s([0,0,0,1],3),[0,0,0,4]);
assert_approx(q_add_s([0,0,1,0],3),[0,0,1,3]);
assert_approx(q_add_s([0,1,0,0],3),[0,1,0,3]);
assert_approx(q_add_s([1,0,0,0],3),[1,0,0,3]);
assert_approx(q_add_s(quat(LEFT+FWD,23),1),[-0.1409744184, -0.1409744184, 0, 1.979924705]);
}
test_Q_Ident();
test_q_add_s();
module test_Q_Add_S() {
verify_f(Q_Add_S([0,0,0,1],3),[0,0,0,4]);
verify_f(Q_Add_S([0,0,1,0],3),[0,0,1,3]);
verify_f(Q_Add_S([0,1,0,0],3),[0,1,0,3]);
verify_f(Q_Add_S([1,0,0,0],3),[1,0,0,3]);
verify_f(Q_Add_S(Quat(LEFT+FWD,23),1),[-0.1409744184, -0.1409744184, 0, 1.979924705]);
module test_q_sub_s() {
assert_approx(q_sub_s([0,0,0,1],3),[0,0,0,-2]);
assert_approx(q_sub_s([0,0,1,0],3),[0,0,1,-3]);
assert_approx(q_sub_s([0,1,0,0],3),[0,1,0,-3]);
assert_approx(q_sub_s([1,0,0,0],3),[1,0,0,-3]);
assert_approx(q_sub_s(quat(LEFT+FWD,23),1),[-0.1409744184, -0.1409744184, 0, -0.02007529538]);
}
test_Q_Add_S();
test_q_sub_s();
module test_Q_Sub_S() {
verify_f(Q_Sub_S([0,0,0,1],3),[0,0,0,-2]);
verify_f(Q_Sub_S([0,0,1,0],3),[0,0,1,-3]);
verify_f(Q_Sub_S([0,1,0,0],3),[0,1,0,-3]);
verify_f(Q_Sub_S([1,0,0,0],3),[1,0,0,-3]);
verify_f(Q_Sub_S(Quat(LEFT+FWD,23),1),[-0.1409744184, -0.1409744184, 0, -0.02007529538]);
module test_q_mul_s() {
assert_approx(q_mul_s([0,0,0,1],3),[0,0,0,3]);
assert_approx(q_mul_s([0,0,1,0],3),[0,0,3,0]);
assert_approx(q_mul_s([0,1,0,0],3),[0,3,0,0]);
assert_approx(q_mul_s([1,0,0,0],3),[3,0,0,0]);
assert_approx(q_mul_s([1,0,0,1],3),[3,0,0,3]);
assert_approx(q_mul_s(quat(LEFT+FWD,23),4),[-0.5638976735, -0.5638976735, 0, 3.919698818]);
}
test_Q_Sub_S();
test_q_mul_s();
module test_Q_Mul_S() {
verify_f(Q_Mul_S([0,0,0,1],3),[0,0,0,3]);
verify_f(Q_Mul_S([0,0,1,0],3),[0,0,3,0]);
verify_f(Q_Mul_S([0,1,0,0],3),[0,3,0,0]);
verify_f(Q_Mul_S([1,0,0,0],3),[3,0,0,0]);
verify_f(Q_Mul_S([1,0,0,1],3),[3,0,0,3]);
verify_f(Q_Mul_S(Quat(LEFT+FWD,23),4),[-0.5638976735, -0.5638976735, 0, 3.919698818]);
module test_q_div_s() {
assert_approx(q_div_s([0,0,0,1],3),[0,0,0,1/3]);
assert_approx(q_div_s([0,0,1,0],3),[0,0,1/3,0]);
assert_approx(q_div_s([0,1,0,0],3),[0,1/3,0,0]);
assert_approx(q_div_s([1,0,0,0],3),[1/3,0,0,0]);
assert_approx(q_div_s([1,0,0,1],3),[1/3,0,0,1/3]);
assert_approx(q_div_s(quat(LEFT+FWD,23),4),[-0.03524360459, -0.03524360459, 0, 0.2449811762]);
}
test_Q_Mul_S();
test_q_div_s();
module test_Q_Div_S() {
verify_f(Q_Div_S([0,0,0,1],3),[0,0,0,1/3]);
verify_f(Q_Div_S([0,0,1,0],3),[0,0,1/3,0]);
verify_f(Q_Div_S([0,1,0,0],3),[0,1/3,0,0]);
verify_f(Q_Div_S([1,0,0,0],3),[1/3,0,0,0]);
verify_f(Q_Div_S([1,0,0,1],3),[1/3,0,0,1/3]);
verify_f(Q_Div_S(Quat(LEFT+FWD,23),4),[-0.03524360459, -0.03524360459, 0, 0.2449811762]);
module test_q_add() {
assert_approx(q_add([2,3,4,5],[-1,-1,-1,-1]),[1,2,3,4]);
assert_approx(q_add([2,3,4,5],[-3,-3,-3,-3]),[-1,0,1,2]);
assert_approx(q_add([2,3,4,5],[0,0,0,0]),[2,3,4,5]);
assert_approx(q_add([2,3,4,5],[1,1,1,1]),[3,4,5,6]);
assert_approx(q_add([2,3,4,5],[1,0,0,0]),[3,3,4,5]);
assert_approx(q_add([2,3,4,5],[0,1,0,0]),[2,4,4,5]);
assert_approx(q_add([2,3,4,5],[0,0,1,0]),[2,3,5,5]);
assert_approx(q_add([2,3,4,5],[0,0,0,1]),[2,3,4,6]);
assert_approx(q_add([2,3,4,5],[2,1,2,1]),[4,4,6,6]);
assert_approx(q_add([2,3,4,5],[1,2,1,2]),[3,5,5,7]);
}
test_Q_Div_S();
test_q_add();
module test_Q_Add() {
verify_f(Q_Add([2,3,4,5],[-1,-1,-1,-1]),[1,2,3,4]);
verify_f(Q_Add([2,3,4,5],[-3,-3,-3,-3]),[-1,0,1,2]);
verify_f(Q_Add([2,3,4,5],[0,0,0,0]),[2,3,4,5]);
verify_f(Q_Add([2,3,4,5],[1,1,1,1]),[3,4,5,6]);
verify_f(Q_Add([2,3,4,5],[1,0,0,0]),[3,3,4,5]);
verify_f(Q_Add([2,3,4,5],[0,1,0,0]),[2,4,4,5]);
verify_f(Q_Add([2,3,4,5],[0,0,1,0]),[2,3,5,5]);
verify_f(Q_Add([2,3,4,5],[0,0,0,1]),[2,3,4,6]);
verify_f(Q_Add([2,3,4,5],[2,1,2,1]),[4,4,6,6]);
verify_f(Q_Add([2,3,4,5],[1,2,1,2]),[3,5,5,7]);
module test_q_sub() {
assert_approx(q_sub([2,3,4,5],[-1,-1,-1,-1]),[3,4,5,6]);
assert_approx(q_sub([2,3,4,5],[-3,-3,-3,-3]),[5,6,7,8]);
assert_approx(q_sub([2,3,4,5],[0,0,0,0]),[2,3,4,5]);
assert_approx(q_sub([2,3,4,5],[1,1,1,1]),[1,2,3,4]);
assert_approx(q_sub([2,3,4,5],[1,0,0,0]),[1,3,4,5]);
assert_approx(q_sub([2,3,4,5],[0,1,0,0]),[2,2,4,5]);
assert_approx(q_sub([2,3,4,5],[0,0,1,0]),[2,3,3,5]);
assert_approx(q_sub([2,3,4,5],[0,0,0,1]),[2,3,4,4]);
assert_approx(q_sub([2,3,4,5],[2,1,2,1]),[0,2,2,4]);
assert_approx(q_sub([2,3,4,5],[1,2,1,2]),[1,1,3,3]);
}
test_Q_Add();
test_q_sub();
module test_Q_Sub() {
verify_f(Q_Sub([2,3,4,5],[-1,-1,-1,-1]),[3,4,5,6]);
verify_f(Q_Sub([2,3,4,5],[-3,-3,-3,-3]),[5,6,7,8]);
verify_f(Q_Sub([2,3,4,5],[0,0,0,0]),[2,3,4,5]);
verify_f(Q_Sub([2,3,4,5],[1,1,1,1]),[1,2,3,4]);
verify_f(Q_Sub([2,3,4,5],[1,0,0,0]),[1,3,4,5]);
verify_f(Q_Sub([2,3,4,5],[0,1,0,0]),[2,2,4,5]);
verify_f(Q_Sub([2,3,4,5],[0,0,1,0]),[2,3,3,5]);
verify_f(Q_Sub([2,3,4,5],[0,0,0,1]),[2,3,4,4]);
verify_f(Q_Sub([2,3,4,5],[2,1,2,1]),[0,2,2,4]);
verify_f(Q_Sub([2,3,4,5],[1,2,1,2]),[1,1,3,3]);
module test_q_mul() {
assert_approx(q_mul(quat_z(30),quat_x(57)),[0.4608999698, 0.1234977747, 0.2274546059, 0.8488721457]);
assert_approx(q_mul(quat_y(30),quat_z(23)),[0.05160021841, 0.2536231763, 0.1925746368, 0.94653458]);
}
test_Q_Sub();
test_q_mul();
module test_Q_Mul() {
verify_f(Q_Mul(QuatZ(30),QuatX(57)),[0.4608999698, 0.1234977747, 0.2274546059, 0.8488721457]);
verify_f(Q_Mul(QuatY(30),QuatZ(23)),[0.05160021841, 0.2536231763, 0.1925746368, 0.94653458]);
module test_q_cumulative() {
assert_approx(q_cumulative([quat_z(30),quat_x(57),quat_y(18)]),[[0, 0, 0.2588190451, 0.9659258263], [0.4608999698, -0.1234977747, 0.2274546059, 0.8488721457], [0.4908072659, 0.01081554785, 0.1525536221, 0.8577404293]]);
}
test_Q_Mul();
test_q_cumulative();
module test_Q_Cumulative() {
verify_f(Q_Cumulative([QuatZ(30),QuatX(57),QuatY(18)]),[[0, 0, 0.2588190451, 0.9659258263], [0.4608999698, -0.1234977747, 0.2274546059, 0.8488721457], [0.4908072659, 0.01081554785, 0.1525536221, 0.8577404293]]);
module test_q_dot() {
assert_approx(q_dot(quat_z(30),quat_x(57)),0.8488721457);
assert_approx(q_dot(quat_y(30),quat_z(23)),0.94653458);
}
test_Q_Cumulative();
test_q_dot();
module test_Q_Dot() {
verify_f(Q_Dot(QuatZ(30),QuatX(57)),0.8488721457);
verify_f(Q_Dot(QuatY(30),QuatZ(23)),0.94653458);
module test_q_neg() {
assert_approx(q_neg([1,0,0,1]),[-1,0,0,-1]);
assert_approx(q_neg([0,1,1,0]),[0,-1,-1,0]);
assert_approx(q_neg(quat_xyz([23,45,67])),[0.0533818345,-0.4143703268,-0.4360652669,-0.7970537592]);
}
test_Q_Dot();
test_q_neg();
module test_Q_Neg() {
verify_f(Q_Neg([1,0,0,1]),[-1,0,0,-1]);
verify_f(Q_Neg([0,1,1,0]),[0,-1,-1,0]);
verify_f(Q_Neg(QuatXYZ([23,45,67])),[0.0533818345,-0.4143703268,-0.4360652669,-0.7970537592]);
module test_q_conj() {
assert_approx(q_conj([1,0,0,1]),[-1,0,0,1]);
assert_approx(q_conj([0,1,1,0]),[0,-1,-1,0]);
assert_approx(q_conj(quat_xyz([23,45,67])),[0.0533818345, -0.4143703268, -0.4360652669, 0.7970537592]);
}
test_Q_Neg();
test_q_conj();
module test_Q_Conj() {
verify_f(Q_Conj([1,0,0,1]),[-1,0,0,1]);
verify_f(Q_Conj([0,1,1,0]),[0,-1,-1,0]);
verify_f(Q_Conj(QuatXYZ([23,45,67])),[0.0533818345, -0.4143703268, -0.4360652669, 0.7970537592]);
module test_q_inverse() {
assert_approx(q_inverse([1,0,0,1]),[-1,0,0,1]/sqrt(2));
assert_approx(q_inverse([0,1,1,0]),[0,-1,-1,0]/sqrt(2));
assert_approx(q_inverse(quat_xyz([23,45,67])),q_conj(quat_xyz([23,45,67])));
assert_approx(q_mul(q_inverse(quat_xyz([23,45,67])),quat_xyz([23,45,67])),q_ident());
}
test_Q_Conj();
test_q_inverse();
module test_Q_Inverse() {
verify_f(Q_Inverse([1,0,0,1]),[-1,0,0,1]/sqrt(2));
verify_f(Q_Inverse([0,1,1,0]),[0,-1,-1,0]/sqrt(2));
verify_f(Q_Inverse(QuatXYZ([23,45,67])),Q_Conj(QuatXYZ([23,45,67])));
verify_f(Q_Mul(Q_Inverse(QuatXYZ([23,45,67])),QuatXYZ([23,45,67])),Q_Ident());
module test_q_Norm() {
assert_approx(q_norm([1,0,0,1]),1.414213562);
assert_approx(q_norm([0,1,1,0]),1.414213562);
assert_approx(q_norm(quat_xyz([23,45,67])),1);
}
test_Q_Inverse();
test_q_Norm();
module test_Q_Norm() {
verify_f(Q_Norm([1,0,0,1]),1.414213562);
verify_f(Q_Norm([0,1,1,0]),1.414213562);
verify_f(Q_Norm(QuatXYZ([23,45,67])),1);
module test_q_normalize() {
assert_approx(q_normalize([1,0,0,1]),[0.7071067812, 0, 0, 0.7071067812]);
assert_approx(q_normalize([0,1,1,0]),[0, 0.7071067812, 0.7071067812, 0]);
assert_approx(q_normalize(quat_xyz([23,45,67])),[-0.0533818345, 0.4143703268, 0.4360652669, 0.7970537592]);
}
test_Q_Norm();
test_q_normalize();
module test_Q_Normalize() {
verify_f(Q_Normalize([1,0,0,1]),[0.7071067812, 0, 0, 0.7071067812]);
verify_f(Q_Normalize([0,1,1,0]),[0, 0.7071067812, 0.7071067812, 0]);
verify_f(Q_Normalize(QuatXYZ([23,45,67])),[-0.0533818345, 0.4143703268, 0.4360652669, 0.7970537592]);
module test_q_dist() {
assert_approx(q_dist(quat_xyz([23,45,67]),quat_xyz([23,45,67])),0);
assert_approx(q_dist(quat_xyz([23,45,67]),quat_xyz([12,34,56])),0.1257349854);
}
test_Q_Normalize();
test_q_dist();
module test_Q_Dist() {
verify_f(Q_Dist(QuatXYZ([23,45,67]),QuatXYZ([23,45,67])),0);
verify_f(Q_Dist(QuatXYZ([23,45,67]),QuatXYZ([12,34,56])),0.1257349854);
module test_q_slerp() {
assert_approx(q_slerp(quat_x(45),quat_y(30),0.0),quat_x(45));
assert_approx(q_slerp(quat_x(45),quat_y(30),0.5),[0.1967063121, 0.1330377423, 0, 0.9713946602]);
assert_approx(q_slerp(quat_x(45),quat_y(30),1.0),quat_y(30));
}
test_Q_Dist();
test_q_slerp();
module test_Q_Slerp() {
verify_f(Q_Slerp(QuatX(45),QuatY(30),0.0),QuatX(45));
verify_f(Q_Slerp(QuatX(45),QuatY(30),0.5),[0.1967063121, 0.1330377423, 0, 0.9713946602]);
verify_f(Q_Slerp(QuatX(45),QuatY(30),1.0),QuatY(30));
module test_q_matrix3() {
assert_approx(q_matrix3(quat_z(37)),rot(37,planar=true));
assert_approx(q_matrix3(quat_z(-49)),rot(-49,planar=true));
}
test_Q_Slerp();
test_q_matrix3();
module test_Q_Matrix3() {
verify_f(Q_Matrix3(QuatZ(37)),rot(37,planar=true));
verify_f(Q_Matrix3(QuatZ(-49)),rot(-49,planar=true));
module test_q_matrix4() {
assert_approx(q_matrix4(quat_z(37)),rot(37));
assert_approx(q_matrix4(quat_z(-49)),rot(-49));
assert_approx(q_matrix4(quat_x(37)),rot([37,0,0]));
assert_approx(q_matrix4(quat_y(37)),rot([0,37,0]));
assert_approx(q_matrix4(quat_xyz([12,34,56])),rot([12,34,56]));
}
test_Q_Matrix3();
test_q_matrix4();
module test_Q_Matrix4() {
verify_f(Q_Matrix4(QuatZ(37)),rot(37));
verify_f(Q_Matrix4(QuatZ(-49)),rot(-49));
verify_f(Q_Matrix4(QuatX(37)),rot([37,0,0]));
verify_f(Q_Matrix4(QuatY(37)),rot([0,37,0]));
verify_f(Q_Matrix4(QuatXYZ([12,34,56])),rot([12,34,56]));
module test_q_axis() {
assert_approx(q_axis(quat_x(37)),RIGHT);
assert_approx(q_axis(quat_x(-37)),LEFT);
assert_approx(q_axis(quat_y(37)),BACK);
assert_approx(q_axis(quat_y(-37)),FWD);
assert_approx(q_axis(quat_z(37)),UP);
assert_approx(q_axis(quat_z(-37)),DOWN);
}
test_Q_Matrix4();
test_q_axis();
module test_Q_Axis() {
verify_f(Q_Axis(QuatX(37)),RIGHT);
verify_f(Q_Axis(QuatX(-37)),LEFT);
verify_f(Q_Axis(QuatY(37)),BACK);
verify_f(Q_Axis(QuatY(-37)),FWD);
verify_f(Q_Axis(QuatZ(37)),UP);
verify_f(Q_Axis(QuatZ(-37)),DOWN);
module test_q_angle() {
assert_approx(q_angle(quat_x(0)),0);
assert_approx(q_angle(quat_y(0)),0);
assert_approx(q_angle(quat_z(0)),0);
assert_approx(q_angle(quat_x(37)),37);
assert_approx(q_angle(quat_x(-37)),37);
assert_approx(q_angle(quat_y(37)),37);
assert_approx(q_angle(quat_y(-37)),37);
assert_approx(q_angle(quat_z(37)),37);
assert_approx(q_angle(quat_z(-37)),37);
assert_approx(q_angle(quat_z(-37),quat_z(-37)), 0);
assert_approx(q_angle(quat_z( 37.123),quat_z(-37.123)), 74.246);
assert_approx(q_angle(quat_x( 37),quat_y(-37)), 51.86293283);
}
test_Q_Axis();
test_q_angle();
module test_Q_Angle() {
verify_f(Q_Angle(QuatX(0)),0);
verify_f(Q_Angle(QuatY(0)),0);
verify_f(Q_Angle(QuatZ(0)),0);
verify_f(Q_Angle(QuatX(37)),37);
verify_f(Q_Angle(QuatX(-37)),37);
verify_f(Q_Angle(QuatY(37)),37);
verify_f(Q_Angle(QuatY(-37)),37);
verify_f(Q_Angle(QuatZ(37)),37);
verify_f(Q_Angle(QuatZ(-37)),37);
verify_f(Q_Angle(QuatZ(-37),QuatZ(-37)), 0);
verify_f(Q_Angle(QuatZ( 37.123),QuatZ(-37.123)), 74.246);
verify_f(Q_Angle(QuatX( 37),QuatY(-37)), 51.86293283);
module test_q_rot() {
assert_approx(q_rot(quat_xyz([12,34,56])),rot([12,34,56]));
assert_approx(q_rot(quat_xyz([12,34,56]),p=[2,3,4]),rot([12,34,56],p=[2,3,4]));
assert_approx(q_rot(quat_xyz([12,34,56]),p=[[2,3,4],[4,9,6]]),rot([12,34,56],p=[[2,3,4],[4,9,6]]));
}
test_Q_Angle();
test_q_rot();
module test_Qrot() {
verify_f(Qrot(QuatXYZ([12,34,56])),rot([12,34,56]));
verify_f(Qrot(QuatXYZ([12,34,56]),p=[2,3,4]),rot([12,34,56],p=[2,3,4]));
verify_f(Qrot(QuatXYZ([12,34,56]),p=[[2,3,4],[4,9,6]]),rot([12,34,56],p=[[2,3,4],[4,9,6]]));
module test_q_rotation() {
assert_approx(_q_standard(q_rotation(q_matrix3(quat([12,34,56],33)))),_q_standard(quat([12,34,56],33)));
assert_approx(q_matrix3(q_rotation(q_matrix3(quat_xyz([12,34,56])))),
q_matrix3(quat_xyz([12,34,56])));
}
test_Qrot();
test_q_rotation();
module test_Q_Rotation() {
verify_f(Qstandard(Q_Rotation(Q_Matrix3(Quat([12,34,56],33)))),Qstandard(Quat([12,34,56],33)));
verify_f(Q_Matrix3(Q_Rotation(Q_Matrix3(QuatXYZ([12,34,56])))),
Q_Matrix3(QuatXYZ([12,34,56])));
}
test_Q_Rotation();
module test_q_rotation_path() {
assert_approx(q_rotation_path(quat_x(135), 5, quat_y(13.5))[0] , q_matrix4(quat_x(135)));
assert_approx(q_rotation_path(quat_x(135), 11, quat_y(13.5))[11] , yrot(13.5));
assert_approx(q_rotation_path(quat_x(135), 16, quat_y(13.5))[8] , q_rotation_path(quat_x(135), 8, quat_y(13.5))[4]);
assert_approx(q_rotation_path(quat_x(135), 16, quat_y(13.5))[7] ,
q_rotation_path(quat_y(13.5),16, quat_x(135))[9]);
module test_Q_Rotation_path() {
verify_f(Q_Rotation_path(QuatX(135), 5, QuatY(13.5))[0] , Q_Matrix4(QuatX(135)));
verify_f(Q_Rotation_path(QuatX(135), 11, QuatY(13.5))[11] , yrot(13.5));
verify_f(Q_Rotation_path(QuatX(135), 16, QuatY(13.5))[8] , Q_Rotation_path(QuatX(135), 8, QuatY(13.5))[4]);
verify_f(Q_Rotation_path(QuatX(135), 16, QuatY(13.5))[7] ,
Q_Rotation_path(QuatY(13.5),16, QuatX(135))[9]);
verify_f(Q_Rotation_path(QuatX(11), 5)[0] , xrot(11));
verify_f(Q_Rotation_path(QuatX(11), 5)[4] , xrot(55));
assert_approx(q_rotation_path(quat_x(11), 5)[0] , xrot(11));
assert_approx(q_rotation_path(quat_x(11), 5)[4] , xrot(55));
}
test_Q_Rotation_path();
test_q_rotation_path();
module test_Q_Nlerp() {
verify_f(Q_Nlerp(QuatX(45),QuatY(30),0.0),QuatX(45));
verify_f(Q_Nlerp(QuatX(45),QuatY(30),0.5),[0.1967063121, 0.1330377423, 0, 0.9713946602]);
verify_f(Q_Rotation_path(QuatX(135), 16, QuatY(13.5))[8] , Q_Matrix4(Q_Nlerp(QuatX(135), QuatY(13.5),0.5)));
verify_f(Q_Nlerp(QuatX(45),QuatY(30),1.0),QuatY(30));
module test_q_nlerp() {
assert_approx(q_nlerp(quat_x(45),quat_y(30),0.0),quat_x(45));
assert_approx(q_nlerp(quat_x(45),quat_y(30),0.5),[0.1967063121, 0.1330377423, 0, 0.9713946602]);
assert_approx(q_rotation_path(quat_x(135), 16, quat_y(13.5))[8] , q_matrix4(q_nlerp(quat_x(135), quat_y(13.5),0.5)));
assert_approx(q_nlerp(quat_x(45),quat_y(30),1.0),quat_y(30));
}
test_Q_Nlerp();
test_q_nlerp();
module test_Q_Squad() {
verify_f(Q_Squad(QuatX(45),QuatZ(30),QuatX(90),QuatY(30),0.0),QuatX(45));
verify_f(Q_Squad(QuatX(45),QuatZ(30),QuatX(90),QuatY(30),1.0),QuatY(30));
verify_f(Q_Squad(QuatX(0),QuatX(30),QuatX(90),QuatX(120),0.5),
Q_Slerp(QuatX(0),QuatX(120),0.5));
verify_f(Q_Squad(QuatY(0),QuatY(0),QuatX(120),QuatX(120),0.3),
Q_Slerp(QuatY(0),QuatX(120),0.3));
module test_q_squad() {
assert_approx(q_squad(quat_x(45),quat_z(30),quat_x(90),quat_y(30),0.0),quat_x(45));
assert_approx(q_squad(quat_x(45),quat_z(30),quat_x(90),quat_y(30),1.0),quat_y(30));
assert_approx(q_squad(quat_x(0),quat_x(30),quat_x(90),quat_x(120),0.5),
q_slerp(quat_x(0),quat_x(120),0.5));
assert_approx(q_squad(quat_y(0),quat_y(0),quat_x(120),quat_x(120),0.3),
q_slerp(quat_y(0),quat_x(120),0.3));
}
test_Q_Squad();
test_q_squad();
module test_Q_exp() {
verify_f(Q_exp(Q_Ident()), exp(1)*Q_Ident());
verify_f(Q_exp([0,0,0,33.7]), exp(33.7)*Q_Ident());
verify_f(Q_exp(Q_ln(Q_Ident())), Q_Ident());
verify_f(Q_exp(Q_ln([1,2,3,0])), [1,2,3,0]);
verify_f(Q_exp(Q_ln(QuatXYZ([31,27,34]))), QuatXYZ([31,27,34]));
let(q=QuatXYZ([12,23,34]))
verify_f(Q_exp(q+Q_Inverse(q)),Q_Mul(Q_exp(q),Q_exp(Q_Inverse(q))));
module test_q_exp() {
assert_approx(q_exp(q_ident()), exp(1)*q_ident());
assert_approx(q_exp([0,0,0,33.7]), exp(33.7)*q_ident());
assert_approx(q_exp(q_ln(q_ident())), q_ident());
assert_approx(q_exp(q_ln([1,2,3,0])), [1,2,3,0]);
assert_approx(q_exp(q_ln(quat_xyz([31,27,34]))), quat_xyz([31,27,34]));
let(q=quat_xyz([12,23,34]))
assert_approx(q_exp(q+q_inverse(q)),q_mul(q_exp(q),q_exp(q_inverse(q))));
}
test_Q_exp();
test_q_exp();
module test_Q_ln() {
verify_f(Q_ln([1,2,3,0]), [24.0535117721, 48.1070235442, 72.1605353164, 1.31952866481]);
verify_f(Q_ln(Q_Ident()), [0,0,0,0]);
verify_f(Q_ln(5.5*Q_Ident()), [0,0,0,ln(5.5)]);
verify_f(Q_ln(Q_exp(QuatXYZ([13,37,43]))), QuatXYZ([13,37,43]));
verify_f(Q_ln(QuatXYZ([12,23,34]))+Q_ln(Q_Inverse(QuatXYZ([12,23,34]))), [0,0,0,0]);
module test_q_ln() {
assert_approx(q_ln([1,2,3,0]), [24.0535117721, 48.1070235442, 72.1605353164, 1.31952866481]);
assert_approx(q_ln(q_ident()), [0,0,0,0]);
assert_approx(q_ln(5.5*q_ident()), [0,0,0,ln(5.5)]);
assert_approx(q_ln(q_exp(quat_xyz([13,37,43]))), quat_xyz([13,37,43]));
assert_approx(q_ln(quat_xyz([12,23,34]))+q_ln(q_inverse(quat_xyz([12,23,34]))), [0,0,0,0]);
}
test_Q_ln();
test_q_ln();
module test_Q_pow() {
q = Quat([1,2,3],77);
verify_f(Q_pow(q,1), q);
verify_f(Q_pow(q,0), Q_Ident());
verify_f(Q_pow(q,-1), Q_Inverse(q));
verify_f(Q_pow(q,2), Q_Mul(q,q));
verify_f(Q_pow(q,3), Q_Mul(q,Q_pow(q,2)));
verify_f(Q_Mul(Q_pow(q,0.456),Q_pow(q,0.544)), q);
verify_f(Q_Mul(Q_pow(q,0.335),Q_Mul(Q_pow(q,.552),Q_pow(q,.113))), q);
module test_q_pow() {
q = quat([1,2,3],77);
assert_approx(q_pow(q,1), q);
assert_approx(q_pow(q,0), q_ident());
assert_approx(q_pow(q,-1), q_inverse(q));
assert_approx(q_pow(q,2), q_mul(q,q));
assert_approx(q_pow(q,3), q_mul(q,q_pow(q,2)));
assert_approx(q_mul(q_pow(q,0.456),q_pow(q,0.544)), q);
assert_approx(q_mul(q_pow(q,0.335),q_mul(q_pow(q,.552),q_pow(q,.113))), q);
}
test_Q_pow();
test_q_pow();

View file

@ -9,17 +9,17 @@ module test_prismoid() {
assert_approx(prismoid([100,80],[40,50],h=50,anchor=BOT), [[[20,25,50],[20,-25,50],[-20,-25,50],[-20,25,50],[50,40,0],[50,-40,0],[-50,-40,0],[-50,40,0]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[4,7,6],[4,6,5]]]);
assert_approx(prismoid([100,80],[40,50],h=50,anchor=TOP+RIGHT), [[[0,25,0],[0,-25,0],[-40,-25,0],[-40,25,0],[30,40,-50],[30,-40,-50],[-70,-40,-50],[-70,40,-50]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[4,7,6],[4,6,5]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5]), [[[30,30,50],[30,-20,50],[-10,-20,50],[-10,30,50],[50,40,0],[50,-40,0],[-50,-40,0],[-50,40,0]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[4,7,6],[4,6,5]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],chamfer=5), [[[50,-35,0],[45,-40,0],[-45,-40,0],[-50,-35,0],[-50,35,0],[-45,40,0],[45,40,0],[50,35,0],[30,-15,50],[25,-20,50],[-5,-20,50],[-10,-15,50],[-10,25,50],[-5,30,50],[25,30,50],[30,25,50]],[[4,1,0],[1,4,2],[2,4,3],[4,0,5],[14,5,6],[5,0,6],[14,6,7],[6,0,7],[0,1,8],[7,0,8],[1,2,9],[8,1,9],[14,8,9],[2,3,10],[9,2,10],[14,9,10],[14,10,11],[3,4,11],[10,3,11],[4,5,12],[14,11,12],[11,4,12],[5,14,13],[14,12,13],[12,5,13],[14,7,15],[7,8,15],[8,14,15]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],chamfer1=5),[[[50,-35,0],[45,-40,0],[-45,-40,0],[-50,-35,0],[-50,35,0],[-45,40,0],[45,40,0],[50,35,0],[30,-20,50],[-10,-20,50],[-10,30,50],[30,30,50]],[[4,1,0],[1,4,2],[2,4,3],[4,0,5],[11,5,6],[5,0,6],[0,11,7],[11,6,7],[6,0,7],[11,0,8],[0,1,8],[1,2,8],[3,4,9],[2,3,9],[8,2,9],[11,8,9],[4,5,10],[5,11,10],[11,9,10],[9,4,10]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],chamfer2=5), [[[50,-40,0],[-50,-40,0],[-50,40,0],[50,40,0],[30,-15,50],[25,-20,50],[-5,-20,50],[-10,-15,50],[-10,25,50],[-5,30,50],[25,30,50],[30,25,50]],[[2,1,0],[10,2,3],[2,0,3],[3,0,4],[10,4,5],[0,1,5],[4,0,5],[10,5,6],[5,1,6],[1,2,7],[6,1,7],[10,6,7],[10,7,8],[7,2,8],[2,10,9],[10,8,9],[8,2,9],[10,3,11],[3,4,11],[4,10,11]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],chamfer1=5,chamfer2=10), [[[50,-35,0],[45,-40,0],[-45,-40,0],[-50,-35,0],[-50,35,0],[-45,40,0],[45,40,0],[50,35,0],[30,-10,50],[20,-20,50],[0,-20,50],[-10,-10,50],[-10,20,50],[0,30,50],[20,30,50],[30,20,50]],[[4,1,0],[1,4,2],[2,4,3],[4,0,5],[14,5,6],[5,0,6],[14,6,7],[6,0,7],[0,1,8],[7,0,8],[1,2,9],[8,1,9],[14,8,9],[2,3,10],[9,2,10],[14,9,10],[14,10,11],[3,4,11],[10,3,11],[4,5,12],[14,11,12],[11,4,12],[5,14,13],[14,12,13],[12,5,13],[14,7,15],[7,8,15],[8,14,15]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],rounding=5), [[[50,-35,0],[49.8296291314,-36.2940952255,0],[49.3301270189,-37.5,0],[48.5355339059,-38.5355339059,0],[47.5,-39.3301270189,0],[46.2940952255,-39.8296291314,0],[45,-40,0],[-45,-40,0],[-46.2940952255,-39.8296291314,0],[-47.5,-39.3301270189,0],[-48.5355339059,-38.5355339059,0],[-49.3301270189,-37.5,0],[-49.8296291314,-36.2940952255,0],[-50,-35,0],[-50,35,0],[-49.8296291314,36.2940952255,0],[-49.3301270189,37.5,0],[-48.5355339059,38.5355339059,0],[-47.5,39.3301270189,0],[-46.2940952255,39.8296291314,0],[-45,40,0],[45,40,0],[46.2940952255,39.8296291314,0],[47.5,39.3301270189,0],[48.5355339059,38.5355339059,0],[49.3301270189,37.5,0],[49.8296291314,36.2940952255,0],[50,35,0],[30,-15,50],[29.8296291314,-16.2940952255,50],[29.3301270189,-17.5,50],[28.5355339059,-18.5355339059,50],[27.5,-19.3301270189,50],[26.2940952255,-19.8296291314,50],[25,-20,50],[-5,-20,50],[-6.29409522551,-19.8296291314,50],[-7.5,-19.3301270189,50],[-8.53553390593,-18.5355339059,50],[-9.33012701892,-17.5,50],[-9.82962913145,-16.2940952255,50],[-10,-15,50],[-10,25,50],[-9.82962913145,26.2940952255,50],[-9.33012701892,27.5,50],[-8.53553390593,28.5355339059,50],[-7.5,29.3301270189,50],[-6.29409522551,29.8296291314,50],[-5,30,50],[25,30,50],[26.2940952255,29.8296291314,50],[27.5,29.3301270189,50],[28.5355339059,28.5355339059,50],[29.3301270189,27.5,50],[29.8296291314,26.2940952255,50],[30,25,50]],[[16,1,0],[1,16,2],[2,16,3],[3,16,4],[4,16,5],[5,16,6],[6,16,7],[7,16,8],[8,16,9],[9,16,10],[10,16,11],[11,16,12],[12,16,13],[13,16,14],[14,16,15],[16,0,17],[17,0,18],[18,0,19],[19,0,20],[20,0,21],[21,0,22],[51,22,23],[22,0,23],[51,23,24],[23,0,24],[24,0,25],[25,0,26],[26,0,27],[0,1,28],[27,0,28],[1,2,29],[28,1,29],[51,28,29],[2,3,30],[29,2,30],[51,29,30],[3,4,31],[30,3,31],[51,30,31],[4,5,32],[31,4,32],[51,31,32],[5,6,33],[32,5,33],[51,32,33],[51,33,34],[6,7,34],[33,6,34],[51,34,35],[7,8,35],[34,7,35],[51,35,36],[8,9,36],[35,8,36],[51,36,37],[9,10,37],[36,9,37],[51,37,38],[10,11,38],[37,10,38],[51,38,39],[11,12,39],[38,11,39],[51,39,40],[12,13,40],[39,12,40],[51,40,41],[13,14,41],[40,13,41],[51,41,42],[14,15,42],[41,14,42],[51,42,43],[15,16,43],[42,15,43],[51,43,44],[16,17,44],[43,16,44],[17,18,45],[44,17,45],[51,44,45],[18,19,46],[45,18,46],[51,45,46],[19,20,47],[46,19,47],[51,46,47],[20,21,48],[47,20,48],[51,47,48],[21,22,49],[51,48,49],[48,21,49],[22,51,50],[51,49,50],[49,22,50],[51,24,52],[24,25,52],[28,51,52],[25,26,53],[52,25,53],[28,52,53],[26,27,54],[53,26,54],[28,53,54],[27,28,55],[28,54,55],[54,27,55]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],rounding1=5), [[[50,-35,0],[49.8296291314,-36.2940952255,0],[49.3301270189,-37.5,0],[48.5355339059,-38.5355339059,0],[47.5,-39.3301270189,0],[46.2940952255,-39.8296291314,0],[45,-40,0],[-45,-40,0],[-46.2940952255,-39.8296291314,0],[-47.5,-39.3301270189,0],[-48.5355339059,-38.5355339059,0],[-49.3301270189,-37.5,0],[-49.8296291314,-36.2940952255,0],[-50,-35,0],[-50,35,0],[-49.8296291314,36.2940952255,0],[-49.3301270189,37.5,0],[-48.5355339059,38.5355339059,0],[-47.5,39.3301270189,0],[-46.2940952255,39.8296291314,0],[-45,40,0],[45,40,0],[46.2940952255,39.8296291314,0],[47.5,39.3301270189,0],[48.5355339059,38.5355339059,0],[49.3301270189,37.5,0],[49.8296291314,36.2940952255,0],[50,35,0],[30,-20,50],[-10,-20,50],[-10,30,50],[30,30,50]],[[16,1,0],[1,16,2],[2,16,3],[3,16,4],[4,16,5],[5,16,6],[6,16,7],[7,16,8],[8,16,9],[9,16,10],[10,16,11],[11,16,12],[12,16,13],[13,16,14],[14,16,15],[16,0,17],[17,0,18],[18,0,19],[19,0,20],[31,20,21],[20,0,21],[31,21,22],[21,0,22],[31,22,23],[22,0,23],[31,23,24],[23,0,24],[31,24,25],[24,0,25],[31,25,26],[25,0,26],[0,31,27],[31,26,27],[26,0,27],[31,0,28],[0,1,28],[1,2,28],[2,3,28],[3,4,28],[4,5,28],[5,6,28],[6,7,28],[13,14,29],[7,8,29],[28,7,29],[8,9,29],[9,10,29],[10,11,29],[11,12,29],[12,13,29],[31,28,29],[17,18,30],[18,19,30],[19,20,30],[20,31,30],[15,16,30],[14,15,30],[29,14,30],[16,17,30],[31,29,30]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],rounding2=5), [[[50,-40,0],[-50,-40,0],[-50,40,0],[50,40,0],[30,-15,50],[29.8296291314,-16.2940952255,50],[29.3301270189,-17.5,50],[28.5355339059,-18.5355339059,50],[27.5,-19.3301270189,50],[26.2940952255,-19.8296291314,50],[25,-20,50],[-5,-20,50],[-6.29409522551,-19.8296291314,50],[-7.5,-19.3301270189,50],[-8.53553390593,-18.5355339059,50],[-9.33012701892,-17.5,50],[-9.82962913145,-16.2940952255,50],[-10,-15,50],[-10,25,50],[-9.82962913145,26.2940952255,50],[-9.33012701892,27.5,50],[-8.53553390593,28.5355339059,50],[-7.5,29.3301270189,50],[-6.29409522551,29.8296291314,50],[-5,30,50],[25,30,50],[26.2940952255,29.8296291314,50],[27.5,29.3301270189,50],[28.5355339059,28.5355339059,50],[29.3301270189,27.5,50],[29.8296291314,26.2940952255,50],[30,25,50]],[[2,1,0],[2,0,3],[3,0,4],[28,4,5],[4,0,5],[28,5,6],[5,0,6],[28,6,7],[6,0,7],[28,7,8],[7,0,8],[28,8,9],[8,0,9],[28,9,10],[0,1,10],[9,0,10],[10,1,11],[28,10,11],[11,1,12],[28,11,12],[12,1,13],[28,12,13],[13,1,14],[28,13,14],[14,1,15],[28,14,15],[15,1,16],[28,15,16],[1,2,17],[16,1,17],[28,16,17],[28,17,18],[17,2,18],[28,18,19],[18,2,19],[28,19,20],[19,2,20],[28,20,21],[20,2,21],[28,21,22],[21,2,22],[22,2,23],[28,22,23],[2,3,24],[23,2,24],[28,23,24],[28,24,25],[24,3,25],[28,25,26],[25,3,26],[3,28,27],[28,26,27],[26,3,27],[28,3,29],[4,28,29],[4,29,30],[29,3,30],[3,4,31],[4,30,31],[30,3,31]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],rounding1=5,rounding2=10), [[[50,-35,0],[49.8296291314,-36.2940952255,0],[49.3301270189,-37.5,0],[48.5355339059,-38.5355339059,0],[47.5,-39.3301270189,0],[46.2940952255,-39.8296291314,0],[45,-40,0],[-45,-40,0],[-46.2940952255,-39.8296291314,0],[-47.5,-39.3301270189,0],[-48.5355339059,-38.5355339059,0],[-49.3301270189,-37.5,0],[-49.8296291314,-36.2940952255,0],[-50,-35,0],[-50,35,0],[-49.8296291314,36.2940952255,0],[-49.3301270189,37.5,0],[-48.5355339059,38.5355339059,0],[-47.5,39.3301270189,0],[-46.2940952255,39.8296291314,0],[-45,40,0],[45,40,0],[46.2940952255,39.8296291314,0],[47.5,39.3301270189,0],[48.5355339059,38.5355339059,0],[49.3301270189,37.5,0],[49.8296291314,36.2940952255,0],[50,35,0],[30,-10,50],[29.6592582629,-12.588190451,50],[28.6602540378,-15,50],[27.0710678119,-17.0710678119,50],[25,-18.6602540378,50],[22.588190451,-19.6592582629,50],[20,-20,50],[0,-20,50],[-2.58819045103,-19.6592582629,50],[-5,-18.6602540378,50],[-7.07106781187,-17.0710678119,50],[-8.66025403784,-15,50],[-9.65925826289,-12.588190451,50],[-10,-10,50],[-10,20,50],[-9.65925826289,22.588190451,50],[-8.66025403784,25,50],[-7.07106781187,27.0710678119,50],[-5,28.6602540378,50],[-2.58819045103,29.6592582629,50],[0,30,50],[20,30,50],[22.588190451,29.6592582629,50],[25,28.6602540378,50],[27.0710678119,27.0710678119,50],[28.6602540378,25,50],[29.6592582629,22.588190451,50],[30,20,50]],[[16,1,0],[1,16,2],[2,16,3],[3,16,4],[4,16,5],[5,16,6],[6,16,7],[7,16,8],[8,16,9],[9,16,10],[10,16,11],[11,16,12],[12,16,13],[13,16,14],[14,16,15],[16,0,17],[17,0,18],[18,0,19],[19,0,20],[20,0,21],[21,0,22],[51,22,23],[22,0,23],[51,23,24],[23,0,24],[24,0,25],[25,0,26],[26,0,27],[0,1,28],[27,0,28],[1,2,29],[28,1,29],[51,28,29],[2,3,30],[29,2,30],[51,29,30],[3,4,31],[30,3,31],[51,30,31],[4,5,32],[31,4,32],[51,31,32],[5,6,33],[32,5,33],[51,32,33],[51,33,34],[6,7,34],[33,6,34],[51,34,35],[7,8,35],[34,7,35],[51,35,36],[8,9,36],[35,8,36],[51,36,37],[9,10,37],[36,9,37],[51,37,38],[10,11,38],[37,10,38],[51,38,39],[11,12,39],[38,11,39],[51,39,40],[12,13,40],[39,12,40],[51,40,41],[13,14,41],[40,13,41],[51,41,42],[14,15,42],[41,14,42],[51,42,43],[15,16,43],[42,15,43],[51,43,44],[16,17,44],[43,16,44],[51,44,45],[17,18,45],[44,17,45],[51,45,46],[18,19,46],[45,18,46],[19,20,47],[46,19,47],[51,46,47],[20,21,48],[47,20,48],[51,47,48],[21,22,49],[51,48,49],[48,21,49],[22,51,50],[51,49,50],[49,22,50],[51,24,52],[24,25,52],[28,51,52],[25,26,53],[52,25,53],[28,52,53],[26,27,54],[53,26,54],[28,53,54],[27,28,55],[28,54,55],[54,27,55]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],rounding1=5,chamfer2=10), [[[50,-35,0],[49.8296291314,-36.2940952255,0],[49.3301270189,-37.5,0],[48.5355339059,-38.5355339059,0],[47.5,-39.3301270189,0],[46.2940952255,-39.8296291314,0],[45,-40,0],[-45,-40,0],[-46.2940952255,-39.8296291314,0],[-47.5,-39.3301270189,0],[-48.5355339059,-38.5355339059,0],[-49.3301270189,-37.5,0],[-49.8296291314,-36.2940952255,0],[-50,-35,0],[-50,35,0],[-49.8296291314,36.2940952255,0],[-49.3301270189,37.5,0],[-48.5355339059,38.5355339059,0],[-47.5,39.3301270189,0],[-46.2940952255,39.8296291314,0],[-45,40,0],[45,40,0],[46.2940952255,39.8296291314,0],[47.5,39.3301270189,0],[48.5355339059,38.5355339059,0],[49.3301270189,37.5,0],[49.8296291314,36.2940952255,0],[50,35,0],[30,-10,50],[20,-20,50],[0,-20,50],[-10,-10,50],[-10,20,50],[0,30,50],[20,30,50],[30,20,50]],[[0,16,9],[28,0,1],[0,9,1],[28,1,2],[1,9,2],[28,2,3],[2,9,3],[3,9,4],[4,9,5],[5,9,6],[6,9,7],[7,9,8],[9,16,10],[10,16,11],[11,16,12],[12,16,13],[13,16,14],[14,16,15],[16,0,17],[17,0,18],[18,0,19],[19,0,20],[20,0,21],[21,0,22],[22,0,23],[23,0,24],[24,0,25],[25,0,26],[0,28,27],[26,0,27],[28,3,29],[3,4,29],[4,5,29],[5,6,29],[6,7,29],[8,9,30],[7,8,30],[29,7,30],[9,10,30],[28,29,30],[28,30,31],[10,11,31],[30,10,31],[11,12,31],[12,13,31],[13,14,31],[28,31,32],[15,16,32],[14,15,32],[31,14,32],[16,17,32],[20,21,33],[28,32,33],[19,20,33],[17,18,33],[32,17,33],[18,19,33],[23,24,34],[28,33,34],[21,22,34],[33,21,34],[22,23,34],[26,27,35],[27,28,35],[25,26,35],[28,34,35],[24,25,35],[34,24,35]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],chamfer=[0,5,10,15]), [[[50,-25,0],[35,-40,0],[-40,-40,0],[-50,-30,0],[-50,35,0],[-45,40,0],[50,40,0],[30,-5,50],[15,-20,50],[0,-20,50],[-10,-10,50],[-10,25,50],[-5,30,50],[30,30,50]],[[4,1,0],[1,4,2],[2,4,3],[4,0,5],[0,13,6],[13,5,6],[5,0,6],[13,0,7],[0,1,7],[1,2,8],[7,1,8],[13,7,8],[13,8,9],[2,3,9],[8,2,9],[13,9,10],[3,4,10],[9,3,10],[4,5,11],[13,10,11],[10,4,11],[5,13,12],[13,11,12],[11,5,12]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],chamfer1=[15,10,5,0], rounding2=[0,5,10,15]), [[[50,-40,0],[-45,-40,0],[-50,-35,0],[-50,30,0],[-40,40,0],[35,40,0],[50,25,0],[30,-5,50],[29.4888873943,-8.88228567654,50],[27.9903810568,-12.5,50],[25.6066017178,-15.6066017178,50],[22.5,-17.9903810568,50],[18.8822856765,-19.4888873943,50],[15,-20,50],[0,-20,50],[-2.58819045103,-19.6592582629,50],[-5,-18.6602540378,50],[-7.07106781187,-17.0710678119,50],[-8.66025403784,-15,50],[-9.65925826289,-12.588190451,50],[-10,-10,50],[-10,25,50],[-9.82962913145,26.2940952255,50],[-9.33012701892,27.5,50],[-8.53553390593,28.5355339059,50],[-7.5,29.3301270189,50],[-6.29409522551,29.8296291314,50],[-5,30,50],[30,30,50]],[[3,1,0],[1,3,2],[3,0,4],[28,4,5],[4,0,5],[0,28,6],[28,5,6],[5,0,6],[28,0,7],[7,0,8],[28,7,8],[28,8,9],[8,0,9],[28,9,10],[9,0,10],[28,10,11],[10,0,11],[28,11,12],[11,0,12],[28,12,13],[0,1,13],[12,0,13],[28,13,14],[13,1,14],[28,14,15],[14,1,15],[28,15,16],[15,1,16],[28,16,17],[1,2,17],[16,1,17],[28,17,18],[17,2,18],[28,18,19],[18,2,19],[28,19,20],[2,3,20],[19,2,20],[28,20,21],[20,3,21],[21,3,22],[28,21,22],[22,3,23],[28,22,23],[3,4,24],[23,3,24],[28,23,24],[28,24,25],[24,4,25],[28,25,26],[25,4,26],[4,28,27],[28,26,27],[26,4,27]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],chamfer=5), [[[50,-35,0],[45,-40,0],[-45,-40,0],[-50,-35,0],[-50,35,0],[-45,40,0],[45,40,0],[50,35,0],[30,-15,50],[25,-20,50],[-5,-20,50],[-10,-15,50],[-10,25,50],[-5,30,50],[25,30,50],[30,25,50]],[[14,7,15],[7,8,15],[8,14,15],[5,14,13],[14,12,13],[12,5,13],[14,11,12],[11,4,12],[4,5,12],[14,10,11],[3,4,11],[10,3,11],[2,3,10],[9,2,10],[14,9,10],[14,8,9],[1,2,9],[8,1,9],[7,0,8],[0,1,8],[14,6,7],[6,0,7],[14,5,6],[5,0,6],[4,0,5],[2,4,3],[1,4,2],[4,1,0]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],chamfer1=5), [[[50,-35,0],[45,-40,0],[-45,-40,0],[-50,-35,0],[-50,35,0],[-45,40,0],[45,40,0],[50,35,0],[30,-20,50],[-10,-20,50],[-10,30,50],[30,30,50]],[[11,9,10],[9,4,10],[4,5,10],[5,11,10],[2,3,9],[8,2,9],[11,8,9],[3,4,9],[1,2,8],[11,0,8],[0,1,8],[0,11,7],[11,6,7],[6,0,7],[11,5,6],[5,0,6],[4,0,5],[2,4,3],[1,4,2],[4,1,0]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],chamfer2=5), [[[50,-40,0],[-50,-40,0],[-50,40,0],[50,40,0],[30,-15,50],[25,-20,50],[-5,-20,50],[-10,-15,50],[-10,25,50],[-5,30,50],[25,30,50],[30,25,50]],[[10,3,11],[3,4,11],[4,10,11],[2,10,9],[10,8,9],[8,2,9],[10,7,8],[7,2,8],[1,2,7],[6,1,7],[10,6,7],[10,5,6],[5,1,6],[10,4,5],[0,1,5],[4,0,5],[3,0,4],[10,2,3],[2,0,3],[2,1,0]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],chamfer1=5,chamfer2=10), [[[50,-35,0],[45,-40,0],[-45,-40,0],[-50,-35,0],[-50,35,0],[-45,40,0],[45,40,0],[50,35,0],[30,-10,50],[20,-20,50],[0,-20,50],[-10,-10,50],[-10,20,50],[0,30,50],[20,30,50],[30,20,50]],[[14,7,15],[7,8,15],[8,14,15],[5,14,13],[14,12,13],[12,5,13],[14,11,12],[11,4,12],[4,5,12],[14,10,11],[3,4,11],[10,3,11],[2,3,10],[9,2,10],[14,9,10],[14,8,9],[1,2,9],[8,1,9],[7,0,8],[0,1,8],[14,6,7],[6,0,7],[14,5,6],[5,0,6],[4,0,5],[2,4,3],[1,4,2],[4,1,0]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],rounding=5), [[[50,-35,0],[49.8296291314,-36.2940952255,0],[49.3301270189,-37.5,0],[48.5355339059,-38.5355339059,0],[47.5,-39.3301270189,0],[46.2940952255,-39.8296291314,0],[45,-40,0],[-45,-40,0],[-46.2940952255,-39.8296291314,0],[-47.5,-39.3301270189,0],[-48.5355339059,-38.5355339059,0],[-49.3301270189,-37.5,0],[-49.8296291314,-36.2940952255,0],[-50,-35,0],[-50,35,0],[-49.8296291314,36.2940952255,0],[-49.3301270189,37.5,0],[-48.5355339059,38.5355339059,0],[-47.5,39.3301270189,0],[-46.2940952255,39.8296291314,0],[-45,40,0],[45,40,0],[46.2940952255,39.8296291314,0],[47.5,39.3301270189,0],[48.5355339059,38.5355339059,0],[49.3301270189,37.5,0],[49.8296291314,36.2940952255,0],[50,35,0],[30,-15,50],[29.8296291314,-16.2940952255,50],[29.3301270189,-17.5,50],[28.5355339059,-18.5355339059,50],[27.5,-19.3301270189,50],[26.2940952255,-19.8296291314,50],[25,-20,50],[-5,-20,50],[-6.29409522551,-19.8296291314,50],[-7.5,-19.3301270189,50],[-8.53553390593,-18.5355339059,50],[-9.33012701892,-17.5,50],[-9.82962913145,-16.2940952255,50],[-10,-15,50],[-10,25,50],[-9.82962913145,26.2940952255,50],[-9.33012701892,27.5,50],[-8.53553390593,28.5355339059,50],[-7.5,29.3301270189,50],[-6.29409522551,29.8296291314,50],[-5,30,50],[25,30,50],[26.2940952255,29.8296291314,50],[27.5,29.3301270189,50],[28.5355339059,28.5355339059,50],[29.3301270189,27.5,50],[29.8296291314,26.2940952255,50],[30,25,50]],[[27,28,55],[28,54,55],[54,27,55],[28,53,54],[26,27,54],[53,26,54],[28,52,53],[25,26,53],[52,25,53],[28,51,52],[51,24,52],[24,25,52],[22,51,50],[51,49,50],[49,22,50],[51,48,49],[48,21,49],[21,22,49],[20,21,48],[47,20,48],[51,47,48],[19,20,47],[46,19,47],[51,46,47],[51,45,46],[18,19,46],[45,18,46],[51,44,45],[17,18,45],[44,17,45],[16,17,44],[43,16,44],[51,43,44],[15,16,43],[42,15,43],[51,42,43],[14,15,42],[41,14,42],[51,41,42],[13,14,41],[40,13,41],[51,40,41],[12,13,40],[39,12,40],[51,39,40],[11,12,39],[38,11,39],[51,38,39],[10,11,38],[37,10,38],[51,37,38],[9,10,37],[36,9,37],[51,36,37],[8,9,36],[35,8,36],[51,35,36],[7,8,35],[34,7,35],[51,34,35],[6,7,34],[33,6,34],[51,33,34],[51,32,33],[5,6,33],[32,5,33],[51,31,32],[4,5,32],[31,4,32],[51,30,31],[3,4,31],[30,3,31],[51,29,30],[2,3,30],[29,2,30],[51,28,29],[1,2,29],[28,1,29],[27,0,28],[0,1,28],[26,0,27],[25,0,26],[24,0,25],[51,23,24],[23,0,24],[51,22,23],[22,0,23],[21,0,22],[20,0,21],[19,0,20],[18,0,19],[17,0,18],[16,0,17],[14,16,15],[13,16,14],[12,16,13],[11,16,12],[10,16,11],[9,16,10],[8,16,9],[7,16,8],[6,16,7],[5,16,6],[4,16,5],[3,16,4],[2,16,3],[1,16,2],[16,1,0]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],rounding1=5), [[[50,-35,0],[49.8296291314,-36.2940952255,0],[49.3301270189,-37.5,0],[48.5355339059,-38.5355339059,0],[47.5,-39.3301270189,0],[46.2940952255,-39.8296291314,0],[45,-40,0],[-45,-40,0],[-46.2940952255,-39.8296291314,0],[-47.5,-39.3301270189,0],[-48.5355339059,-38.5355339059,0],[-49.3301270189,-37.5,0],[-49.8296291314,-36.2940952255,0],[-50,-35,0],[-50,35,0],[-49.8296291314,36.2940952255,0],[-49.3301270189,37.5,0],[-48.5355339059,38.5355339059,0],[-47.5,39.3301270189,0],[-46.2940952255,39.8296291314,0],[-45,40,0],[45,40,0],[46.2940952255,39.8296291314,0],[47.5,39.3301270189,0],[48.5355339059,38.5355339059,0],[49.3301270189,37.5,0],[49.8296291314,36.2940952255,0],[50,35,0],[30,-20,50],[-10,-20,50],[-10,30,50],[30,30,50]],[[16,17,30],[31,29,30],[15,16,30],[14,15,30],[29,14,30],[19,20,30],[20,31,30],[18,19,30],[17,18,30],[12,13,29],[31,28,29],[11,12,29],[10,11,29],[9,10,29],[8,9,29],[7,8,29],[28,7,29],[13,14,29],[6,7,28],[5,6,28],[4,5,28],[3,4,28],[2,3,28],[1,2,28],[31,0,28],[0,1,28],[0,31,27],[31,26,27],[26,0,27],[31,25,26],[25,0,26],[31,24,25],[24,0,25],[31,23,24],[23,0,24],[31,22,23],[22,0,23],[31,21,22],[21,0,22],[31,20,21],[20,0,21],[19,0,20],[18,0,19],[17,0,18],[16,0,17],[14,16,15],[13,16,14],[12,16,13],[11,16,12],[10,16,11],[9,16,10],[8,16,9],[7,16,8],[6,16,7],[5,16,6],[4,16,5],[3,16,4],[2,16,3],[1,16,2],[16,1,0]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],rounding2=5), [[[50,-40,0],[-50,-40,0],[-50,40,0],[50,40,0],[30,-15,50],[29.8296291314,-16.2940952255,50],[29.3301270189,-17.5,50],[28.5355339059,-18.5355339059,50],[27.5,-19.3301270189,50],[26.2940952255,-19.8296291314,50],[25,-20,50],[-5,-20,50],[-6.29409522551,-19.8296291314,50],[-7.5,-19.3301270189,50],[-8.53553390593,-18.5355339059,50],[-9.33012701892,-17.5,50],[-9.82962913145,-16.2940952255,50],[-10,-15,50],[-10,25,50],[-9.82962913145,26.2940952255,50],[-9.33012701892,27.5,50],[-8.53553390593,28.5355339059,50],[-7.5,29.3301270189,50],[-6.29409522551,29.8296291314,50],[-5,30,50],[25,30,50],[26.2940952255,29.8296291314,50],[27.5,29.3301270189,50],[28.5355339059,28.5355339059,50],[29.3301270189,27.5,50],[29.8296291314,26.2940952255,50],[30,25,50]],[[3,4,31],[4,30,31],[30,3,31],[4,29,30],[29,3,30],[28,3,29],[4,28,29],[3,28,27],[28,26,27],[26,3,27],[28,25,26],[25,3,26],[28,24,25],[24,3,25],[2,3,24],[23,2,24],[28,23,24],[22,2,23],[28,22,23],[28,21,22],[21,2,22],[28,20,21],[20,2,21],[28,19,20],[19,2,20],[28,18,19],[18,2,19],[28,17,18],[17,2,18],[1,2,17],[16,1,17],[28,16,17],[15,1,16],[28,15,16],[14,1,15],[28,14,15],[13,1,14],[28,13,14],[12,1,13],[28,12,13],[11,1,12],[28,11,12],[10,1,11],[28,10,11],[0,1,10],[9,0,10],[28,9,10],[8,0,9],[28,8,9],[28,7,8],[7,0,8],[28,6,7],[6,0,7],[28,5,6],[5,0,6],[28,4,5],[4,0,5],[3,0,4],[2,0,3],[2,1,0]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],rounding1=5,rounding2=10), [[[50,-35,0],[49.8296291314,-36.2940952255,0],[49.3301270189,-37.5,0],[48.5355339059,-38.5355339059,0],[47.5,-39.3301270189,0],[46.2940952255,-39.8296291314,0],[45,-40,0],[-45,-40,0],[-46.2940952255,-39.8296291314,0],[-47.5,-39.3301270189,0],[-48.5355339059,-38.5355339059,0],[-49.3301270189,-37.5,0],[-49.8296291314,-36.2940952255,0],[-50,-35,0],[-50,35,0],[-49.8296291314,36.2940952255,0],[-49.3301270189,37.5,0],[-48.5355339059,38.5355339059,0],[-47.5,39.3301270189,0],[-46.2940952255,39.8296291314,0],[-45,40,0],[45,40,0],[46.2940952255,39.8296291314,0],[47.5,39.3301270189,0],[48.5355339059,38.5355339059,0],[49.3301270189,37.5,0],[49.8296291314,36.2940952255,0],[50,35,0],[30,-10,50],[29.6592582629,-12.588190451,50],[28.6602540378,-15,50],[27.0710678119,-17.0710678119,50],[25,-18.6602540378,50],[22.588190451,-19.6592582629,50],[20,-20,50],[0,-20,50],[-2.58819045103,-19.6592582629,50],[-5,-18.6602540378,50],[-7.07106781187,-17.0710678119,50],[-8.66025403784,-15,50],[-9.65925826289,-12.588190451,50],[-10,-10,50],[-10,20,50],[-9.65925826289,22.588190451,50],[-8.66025403784,25,50],[-7.07106781187,27.0710678119,50],[-5,28.6602540378,50],[-2.58819045103,29.6592582629,50],[0,30,50],[20,30,50],[22.588190451,29.6592582629,50],[25,28.6602540378,50],[27.0710678119,27.0710678119,50],[28.6602540378,25,50],[29.6592582629,22.588190451,50],[30,20,50]],[[27,28,55],[28,54,55],[54,27,55],[28,53,54],[26,27,54],[53,26,54],[28,52,53],[25,26,53],[52,25,53],[28,51,52],[51,24,52],[24,25,52],[22,51,50],[51,49,50],[49,22,50],[51,48,49],[48,21,49],[21,22,49],[20,21,48],[47,20,48],[51,47,48],[19,20,47],[46,19,47],[51,46,47],[18,19,46],[45,18,46],[51,45,46],[17,18,45],[44,17,45],[51,44,45],[16,17,44],[43,16,44],[51,43,44],[15,16,43],[42,15,43],[51,42,43],[14,15,42],[41,14,42],[51,41,42],[13,14,41],[40,13,41],[51,40,41],[12,13,40],[39,12,40],[51,39,40],[11,12,39],[38,11,39],[51,38,39],[10,11,38],[37,10,38],[51,37,38],[9,10,37],[36,9,37],[51,36,37],[8,9,36],[35,8,36],[51,35,36],[7,8,35],[34,7,35],[51,34,35],[6,7,34],[33,6,34],[51,33,34],[51,32,33],[5,6,33],[32,5,33],[51,31,32],[4,5,32],[31,4,32],[51,30,31],[3,4,31],[30,3,31],[51,29,30],[2,3,30],[29,2,30],[51,28,29],[1,2,29],[28,1,29],[27,0,28],[0,1,28],[26,0,27],[25,0,26],[24,0,25],[51,23,24],[23,0,24],[51,22,23],[22,0,23],[21,0,22],[20,0,21],[19,0,20],[18,0,19],[17,0,18],[16,0,17],[14,16,15],[13,16,14],[12,16,13],[11,16,12],[10,16,11],[9,16,10],[8,16,9],[7,16,8],[6,16,7],[5,16,6],[4,16,5],[3,16,4],[2,16,3],[1,16,2],[16,1,0]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],rounding1=5,chamfer2=10), [[[50,-35,0],[49.8296291314,-36.2940952255,0],[49.3301270189,-37.5,0],[48.5355339059,-38.5355339059,0],[47.5,-39.3301270189,0],[46.2940952255,-39.8296291314,0],[45,-40,0],[-45,-40,0],[-46.2940952255,-39.8296291314,0],[-47.5,-39.3301270189,0],[-48.5355339059,-38.5355339059,0],[-49.3301270189,-37.5,0],[-49.8296291314,-36.2940952255,0],[-50,-35,0],[-50,35,0],[-49.8296291314,36.2940952255,0],[-49.3301270189,37.5,0],[-48.5355339059,38.5355339059,0],[-47.5,39.3301270189,0],[-46.2940952255,39.8296291314,0],[-45,40,0],[45,40,0],[46.2940952255,39.8296291314,0],[47.5,39.3301270189,0],[48.5355339059,38.5355339059,0],[49.3301270189,37.5,0],[49.8296291314,36.2940952255,0],[50,35,0],[30,-10,50],[20,-20,50],[0,-20,50],[-10,-10,50],[-10,20,50],[0,30,50],[20,30,50],[30,20,50]],[[24,25,35],[34,24,35],[25,26,35],[28,34,35],[26,27,35],[27,28,35],[22,23,34],[21,22,34],[33,21,34],[28,33,34],[23,24,34],[18,19,33],[17,18,33],[32,17,33],[19,20,33],[28,32,33],[20,21,33],[16,17,32],[15,16,32],[14,15,32],[31,14,32],[28,31,32],[13,14,31],[12,13,31],[11,12,31],[10,11,31],[30,10,31],[28,30,31],[28,29,30],[9,10,30],[8,9,30],[7,8,30],[29,7,30],[6,7,29],[5,6,29],[4,5,29],[28,3,29],[3,4,29],[0,28,27],[26,0,27],[25,0,26],[24,0,25],[23,0,24],[22,0,23],[21,0,22],[20,0,21],[19,0,20],[18,0,19],[17,0,18],[16,0,17],[14,16,15],[13,16,14],[12,16,13],[11,16,12],[10,16,11],[9,16,10],[7,9,8],[6,9,7],[5,9,6],[4,9,5],[3,9,4],[28,2,3],[2,9,3],[28,1,2],[1,9,2],[28,0,1],[0,9,1],[0,16,9]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],chamfer=[0,5,10,15]), [[[50,-25,0],[35,-40,0],[-40,-40,0],[-50,-30,0],[-50,35,0],[-45,40,0],[50,40,0],[30,-5,50],[15,-20,50],[0,-20,50],[-10,-10,50],[-10,25,50],[-5,30,50],[30,30,50]],[[5,13,12],[13,11,12],[11,5,12],[13,10,11],[10,4,11],[4,5,11],[13,9,10],[3,4,10],[9,3,10],[2,3,9],[8,2,9],[13,8,9],[13,7,8],[1,2,8],[7,1,8],[13,0,7],[0,1,7],[0,13,6],[13,5,6],[5,0,6],[4,0,5],[2,4,3],[1,4,2],[4,1,0]]]);
assert_approx(prismoid([100,80],[40,50],h=50,shift=[10,5],chamfer1=[15,10,5,0], rounding2=[0,5,10,15]), [[[50,-40,0],[-45,-40,0],[-50,-35,0],[-50,30,0],[-40,40,0],[35,40,0],[50,25,0],[30,-5,50],[29.4888873943,-8.88228567654,50],[27.9903810568,-12.5,50],[25.6066017178,-15.6066017178,50],[22.5,-17.9903810568,50],[18.8822856765,-19.4888873943,50],[15,-20,50],[0,-20,50],[-2.58819045103,-19.6592582629,50],[-5,-18.6602540378,50],[-7.07106781187,-17.0710678119,50],[-8.66025403784,-15,50],[-9.65925826289,-12.588190451,50],[-10,-10,50],[-10,25,50],[-9.82962913145,26.2940952255,50],[-9.33012701892,27.5,50],[-8.53553390593,28.5355339059,50],[-7.5,29.3301270189,50],[-6.29409522551,29.8296291314,50],[-5,30,50],[30,30,50]],[[4,28,27],[28,26,27],[26,4,27],[28,25,26],[25,4,26],[28,24,25],[24,4,25],[3,4,24],[23,3,24],[28,23,24],[22,3,23],[28,22,23],[21,3,22],[28,21,22],[28,20,21],[20,3,21],[28,19,20],[2,3,20],[19,2,20],[28,18,19],[18,2,19],[28,17,18],[17,2,18],[1,2,17],[16,1,17],[28,16,17],[15,1,16],[28,15,16],[14,1,15],[28,14,15],[13,1,14],[28,13,14],[0,1,13],[12,0,13],[28,12,13],[11,0,12],[28,11,12],[10,0,11],[28,10,11],[9,0,10],[28,9,10],[8,0,9],[28,8,9],[28,7,8],[7,0,8],[28,0,7],[0,28,6],[28,5,6],[5,0,6],[28,4,5],[4,0,5],[3,0,4],[1,3,2],[3,1,0]]]);
}
test_prismoid();

View file

@ -111,7 +111,7 @@ module test_scale() {
for (val=vals) {
assert_equal(scale(point2d(val)), [[val.x,0,0],[0,val.y,0],[0,0,1]]);
assert_equal(scale(val), [[val.x,0,0,0],[0,val.y,0,0],[0,0,val.z,0],[0,0,0,1]]);
assert_equal(scale(val, p=[1,2,3]), vmul([1,2,3], val));
assert_equal(scale(val, p=[1,2,3]), v_mul([1,2,3], val));
scale(val) nil();
}
assert_equal(scale(3), [[3,0,0,0],[0,3,0,0],[0,0,3,0],[0,0,0,1]]);
@ -122,7 +122,7 @@ module test_scale() {
assert_equal(scale([2,3], p=square(1)), square([2,3]));
assert_equal(scale([2,2], cp=[0.5,0.5], p=square(1)), move([-0.5,-0.5], p=square([2,2])));
assert_equal(scale([2,3,4], p=cb), cube([2,3,4]));
assert_equal(scale([-2,-3,-4], p=cb), [[for (p=cb[0]) vmul(p,[-2,-3,-4])], [for (f=cb[1]) reverse(f)]]);
assert_equal(scale([-2,-3,-4], p=cb), [[for (p=cb[0]) v_mul(p,[-2,-3,-4])], [for (f=cb[1]) reverse(f)]]);
// Verify that module at least doesn't crash.
scale(-5) scale(5) nil();
}
@ -289,7 +289,7 @@ module test_rot() {
for (vec2 = vecs2d) {
assert_equal(
rot(from=vec1, to=vec2, p=pts2d, planar=true),
apply(affine2d_zrot(vang(vec2)-vang(vec1)), pts2d),
apply(affine2d_zrot(v_theta(vec2)-v_theta(vec1)), pts2d),
info=str(
"from = ", vec1, ", ",
"to = ", vec2, ", ",

View file

@ -32,66 +32,67 @@ module test_is_vector() {
test_is_vector();
module test_vfloor() {
assert_equal(vfloor([2.0, 3.14, 18.9, 7]), [2,3,18,7]);
assert_equal(vfloor([-2.0, -3.14, -18.9, -7]), [-2,-4,-19,-7]);
module test_v_floor() {
assert_equal(v_floor([2.0, 3.14, 18.9, 7]), [2,3,18,7]);
assert_equal(v_floor([-2.0, -3.14, -18.9, -7]), [-2,-4,-19,-7]);
}
test_vfloor();
test_v_floor();
module test_vceil() {
assert_equal(vceil([2.0, 3.14, 18.9, 7]), [2,4,19,7]);
assert_equal(vceil([-2.0, -3.14, -18.9, -7]), [-2,-3,-18,-7]);
module test_v_ceil() {
assert_equal(v_ceil([2.0, 3.14, 18.9, 7]), [2,4,19,7]);
assert_equal(v_ceil([-2.0, -3.14, -18.9, -7]), [-2,-3,-18,-7]);
}
test_vceil();
test_v_ceil();
module test_vmul() {
assert_equal(vmul([3,4,5], [8,7,6]), [24,28,30]);
assert_equal(vmul([1,2,3], [4,5,6]), [4,10,18]);
assert_equal(vmul([[1,2,3],[4,5,6],[7,8,9]], [[4,5,6],[3,2,1],[5,9,3]]), [32,28,134]);
module test_v_mul() {
assert_equal(v_mul([3,4,5], [8,7,6]), [24,28,30]);
assert_equal(v_mul([1,2,3], [4,5,6]), [4,10,18]);
assert_equal(v_mul([[1,2,3],[4,5,6],[7,8,9]], [[4,5,6],[3,2,1],[5,9,3]]), [32,28,134]);
}
test_vmul();
test_v_mul();
module test_vdiv() {
assert(vdiv([24,28,30], [8,7,6]) == [3, 4, 5]);
module test_v_div() {
assert(v_div([24,28,30], [8,7,6]) == [3, 4, 5]);
}
test_vdiv();
test_v_div();
module test_vabs() {
assert(vabs([2,4,8]) == [2,4,8]);
assert(vabs([-2,-4,-8]) == [2,4,8]);
assert(vabs([-2,4,8]) == [2,4,8]);
assert(vabs([2,-4,8]) == [2,4,8]);
assert(vabs([2,4,-8]) == [2,4,8]);
module test_v_abs() {
assert(v_abs([2,4,8]) == [2,4,8]);
assert(v_abs([-2,-4,-8]) == [2,4,8]);
assert(v_abs([-2,4,8]) == [2,4,8]);
assert(v_abs([2,-4,8]) == [2,4,8]);
assert(v_abs([2,4,-8]) == [2,4,8]);
}
test_vabs();
test_v_abs();
include <../strings.scad>
module test_vang() {
assert(vang([1,0])==0);
assert(vang([0,1])==90);
assert(vang([-1,0])==180);
assert(vang([0,-1])==-90);
assert(vang([1,1])==45);
assert(vang([-1,1])==135);
assert(vang([1,-1])==-45);
assert(vang([-1,-1])==-135);
assert(vang([0,0,1])==[0,90]);
assert(vang([0,1,1])==[90,45]);
assert(vang([0,1,-1])==[90,-45]);
assert(vang([1,0,0])==[0,0]);
assert(vang([0,1,0])==[90,0]);
assert(vang([0,-1,0])==[-90,0]);
assert(vang([-1,0,0])==[180,0]);
assert(vang([1,0,1])==[0,45]);
assert(vang([0,1,1])==[90,45]);
assert(vang([0,-1,1])==[-90,45]);
assert(approx(vang([1,1,1]),[45, 35.2643896828]));
module test_v_theta() {
assert_approx(v_theta([0,0]), 0);
assert_approx(v_theta([1,0]), 0);
assert_approx(v_theta([0,1]), 90);
assert_approx(v_theta([-1,0]), 180);
assert_approx(v_theta([0,-1]), -90);
assert_approx(v_theta([1,1]), 45);
assert_approx(v_theta([-1,1]), 135);
assert_approx(v_theta([1,-1]), -45);
assert_approx(v_theta([-1,-1]), -135);
assert_approx(v_theta([0,0,1]), 0);
assert_approx(v_theta([0,1,1]), 90);
assert_approx(v_theta([0,1,-1]), 90);
assert_approx(v_theta([1,0,0]), 0);
assert_approx(v_theta([0,1,0]), 90);
assert_approx(v_theta([0,-1,0]), -90);
assert_approx(v_theta([-1,0,0]), 180);
assert_approx(v_theta([1,0,1]), 0);
assert_approx(v_theta([0,1,1]), 90);
assert_approx(v_theta([0,-1,1]), -90);
assert_approx(v_theta([1,1,1]), 45);
}
test_vang();
test_v_theta();
module test_unit() {

View file

@ -183,7 +183,7 @@ module trapezoidal_threaded_rod(
higthr1 = ceil(higang1 / 360);
higthr2 = ceil(higang2 / 360);
pdepth = -min(subindex(profile,1));
dummy1 = assert(_r1>2*pdepth) assert(_r2>2*pdepth);
dummy1 = assert(_r1>pdepth) assert(_r2>pdepth);
skew_mat = affine3d_skew(sxz=(_r2-_r1)/l);
side_mat = affine3d_xrot(90) *
affine3d_mirror([-1,1,0]) *
@ -208,7 +208,7 @@ module trapezoidal_threaded_rod(
prof = apply(side_mat, [
for (thread = [-threads/2:1:threads/2-1]) let(
tang = (thread/starts) * 360 + ang,
hsc =
hsc = internal? 1 :
abs(tang) > twist/2? 0 :
(higang1==0 && higang2==0)? 1 :
lookup(tang, hig_table),
@ -225,7 +225,7 @@ module trapezoidal_threaded_rod(
];
thread_vnfs = vnf_merge([
for (i=[0:1:starts-1])
zrot(i*360/starts, p=vnf_vertex_array(thread_verts, reverse=left_handed)),
zrot(i*360/starts, p=vnf_vertex_array(thread_verts, reverse=left_handed, style="min_edge")),
for (i=[0:1:starts-1]) let(
rmat = zrot(i*360/starts),
pts = deduplicate(list_head(thread_verts[0], len(prof3d)+1)),
@ -447,11 +447,15 @@ module threaded_nut(
// Module: npt_threaded_rod()
// Description:
// Constructs a standard NPT pipe threading.
// Constructs a standard NPT pipe end threading. If `internal=true`, creates a mask for making
// internal pipe threads. Tapers smaller upwards if `internal=false`. Tapers smaller downwards
// if `internal=true`. If `hollow=true` and `internal=false`, then the pipe threads will be
// hollowed out into a pipe with the apropriate internal diameter.
// Arguments:
// d = Outer diameter of threaded rod.
// left_handed = if true, create left-handed threads. Default = false
// bevel = if true, bevel the thread ends. Default: false
// size = NPT standard pipe size in inches. 1/16", 1/8", 1/4", 3/8", 1/2", 3/4", 1", 1+1/4", 1+1/2", or 2". Default: 1/2"
// left_handed = If true, create left-handed threads. Default = false
// bevel = If true, bevel the thread ends. Default: false
// hollow = If true, create a pipe with the correct internal diameter.
// internal = If true, make this a mask for making internal threads.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
@ -467,9 +471,16 @@ module npt_threaded_rod(
size=1/2,
left_handed=false,
bevel=false,
hollow=false,
internal=false,
anchor, spin, orient
) {
assert(is_finite(size));
assert(is_bool(left_handed));
assert(is_bool(bevel));
assert(is_bool(hollow));
assert(is_bool(internal));
assert(!(internal&&hollow), "Cannot created a hollow internal threads mask.");
info_table = [
// Size OD len TPI
[ 1/16, [ 0.3896, 0.308, 27 ]],
@ -488,8 +499,10 @@ module npt_threaded_rod(
l = 25.4 * info[0];
d = 25.4 * info[1];
pitch = 25.4 / info[2];
r1 = get_radius(d=d, dflt=0.84 * 25.4 / 2);
r2 = r1 - l/32;
rr = get_radius(d=d, dflt=0.84 * 25.4 / 2);
rr2 = rr - l/32;
r1 = internal? rr2 : rr;
r2 = internal? rr : rr2;
depth = pitch * cos(30) * 5/8;
profile = internal? [
[-6/16, -depth/pitch],
@ -506,20 +519,28 @@ module npt_threaded_rod(
[ 6/16, -depth/pitch],
[ 7/16, -depth/pitch*1.07]
];
trapezoidal_threaded_rod(
d1=2*r1, d2=2*r2, l=l,
pitch=pitch,
thread_depth=depth,
thread_angle=30,
profile=profile,
left_handed=left_handed,
bevel=bevel,
internal=internal,
higbee=r1*PI/2,
anchor=anchor,
spin=spin,
orient=orient
) children();
attachable(anchor,spin,orient, l=l, r1=r1, r2=r2) {
difference() {
trapezoidal_threaded_rod(
d1=2*r1, d2=2*r2, l=l,
pitch=pitch,
thread_depth=depth,
thread_angle=30,
profile=profile,
left_handed=left_handed,
bevel=bevel,
internal=internal,
higbee=r1*PI/2,
anchor=anchor,
spin=spin,
orient=orient
);
if (hollow) {
cylinder(l=l+1, d=size*INCH, center=true);
} else nil();
}
children();
}
}

View file

@ -414,8 +414,8 @@ function rot(a=0, v, cp, from, to, reverse=false, planar=false, p, _m) =
assert(approx(point3d(from).z, 0), "'from' must be a 2D vector when 'planar' is true.")
assert(approx(point3d(to).z, 0), "'to' must be a 2D vector when 'planar' is true.")
affine2d_zrot(
vang(point2d(to)) -
vang(point2d(from))
v_theta(to) -
v_theta(from)
),
m2 = is_undef(cp)? m1 : (move(cp) * m1 * move(-cp)),
m3 = reverse? matrix_inverse(m2) : m2
@ -946,8 +946,8 @@ function yscale(y=1, p, cp=0, planar=false) =
assert(is_bool(planar))
let( cp = is_num(cp)? [0,cp,0] : cp )
(planar || (!is_undef(p) && len(p)==2))
? scale([1,y],p=p)
: scale([1,y,1],p=p);
? scale([1,y], cp=cp, p=p)
: scale([1,y,1], cp=cp, p=p);
// Function&Module: zscale()

View file

@ -90,7 +90,7 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
// "xjump" | | x | Move the turtle's x position to the specified value
// "yjump | | y | Move the turtle's y position to the specified value
// "zjump | | y | Move the turtle's y position to the specified value
// "left" | [angle] | Turn turtle left by specified angle or default angle
// "left" | | [angle] | Turn turtle left by specified angle or default angle
// "right" | | [angle] | Turn turtle to the right by specified angle or default angle
// "up" | | [angle] | Turn turtle up by specified angle or default angle
// "down" | | [angle] | Turn turtle down by specified angle or default angle
@ -693,7 +693,7 @@ function _turtle3d_list_command(command,arcsteps,movescale, lastT,lastPre,index)
assert(is_vector(grow,2), str("Parameter to \"grow\" must be a scalar or 2d vector at index ",index))
assert(is_vector(shrink,2), str("Parameter to \"shrink\" must be a scalar or 2d vector at index ",index))
let(
scaling = point3d(vdiv(grow,shrink),1),
scaling = point3d(v_div(grow,shrink),1),
usersteps = struct_val(keys,"steps"),
roll = struct_val(keys,"roll"),
////////////////////////////////////////////////////////////////////////////////////////

View file

@ -91,6 +91,23 @@ overrides the `anchor=` argument. A `center=true` argument is the same as `anch
A `center=false` argument can mean `anchor=[-1,-1,-1]` for a cube, or `anchor=BOTTOM` for a
cylinder.
Many 2D shapes provided by BOSL2 are also anchorable. Due to technical limitations of OpenSCAD,
however, `square()` and `circle()` are *not*. BOSL2 provides `rect()` and `oval()` as attachable
and anchorable equivalents. You can only anchor on the XY plane, of course, but you can use the
same `FRONT`, `BACK`, `LEFT`, `RIGHT`, and `CENTER` anchor constants.
```openscad-2D
rect([40,30], anchor=BACK+LEFT);
```
```openscad-2D
oval(d=50, anchor=FRONT);
```
```openscad-2D
hexagon(d=50, anchor=BACK);
```
## Spin
Attachable shapes also can be spun in place as you create them. You can do this by passing in
@ -107,6 +124,17 @@ vector, like [Xang,Yang,Zang]:
cube([20,20,40], center=true, spin=[10,20,30]);
```
You can also apply spin to 2D shapes from BOSL2. Again, you should use `rect()` and `oval()`
instead of `square()` and `circle()`:
```openscad-2D
rect([40,30], spin=30);
```
```openscad-2D
oval(d=[40,30], spin=30);
```
## Orientation
Another way to specify a rotation for an attachable shape, is to pass a 3D vector via the
@ -117,6 +145,9 @@ For example, you can make a cone that is tilted up and to the right like this:
cylinder(h=100, r1=50, r2=20, orient=UP+RIGHT);
```
You can *not* use `orient=` with 2D shapes.
## Mixing Anchoring, Spin, and Orientation
When giving `anchor=`, `spin=`, and `orient=`, they are applied anchoring first, spin second,
then orient last. For example, here's a cube:
@ -150,8 +181,14 @@ However, since spin is applied *after* anchoring, it can actually have a signifi
cylinder(d=50, l=40, anchor=FWD, spin=-30);
```
For 2D shapes, you can mix `anchor=` with `spin=`, but not with `orient=`.
## Attaching Children
```openscad-2D
rect([40,30], anchor=BACK+LEFT, spin=30);
```
## Attaching 3D Children
The reason attachables are called that, is because they can be attached to each other.
You can do that by making one attachable shape be a child of another attachable shape.
By default, the child of an attachable is attached to the center of the parent shape.
@ -178,13 +215,21 @@ cube(50,center=true)
attach(TOP,TOP) cylinder(d1=50,d2=20,l=20);
```
By default, `attach()` causes the child to overlap the parent by 0.01, to let CGAL correctly
join the parts. If you need the child to have no overlap, or a different overlap, you can use
the `overlap=` argument:
By default, `attach()` places the child exactly flush with the surface of the parent. Sometimes
it's useful to have the child overlap the parent by insetting a bit. You can do this with the
`overlap=` argument to `attach()`. A positive value will inset the child into the parent, and
a negative value will outset out from the parent:
```openscad
cube(50,center=true)
attach(TOP,TOP,overlap=0) cylinder(d1=50,d2=20,l=20);
attach(TOP,overlap=10)
cylinder(d=20,l=20);
```
```openscad
cube(50,center=true)
attach(TOP,overlap=-20)
cylinder(d=20,l=20);
```
If you want to position the child at the parent's anchorpoint, without re-orienting, you can
@ -217,6 +262,23 @@ cube(50, center=true)
position([TOP,RIGHT,FRONT]) cylinder(d1=50,d2=20,l=20);
```
## Attaching 2D Children
You can use attachments in 2D as well, but only in the XY plane. Also, the built-in `square()`
and `circle()` 2D modules do not support attachments. Instead, you should use the `rect()` and
`oval()` modules:
```openscad-2D
rect(50,center=true)
attach(RIGHT,FRONT)
trapezoid(w1=30,w2=0,h=30);
```
```openscad-2D
oval(d=50)
attach(BACK,FRONT,overlap=5)
trapezoid(w1=30,w2=0,h=30);
```
## Anchor Arrows
One way that is useful to show the position and orientation of an anchorpoint is by attaching
an anchor arrow to that anchor.
@ -259,6 +321,7 @@ cylinder(h=100, d=100, center=true)
show_anchors(s=30);
```
## Tagged Operations
BOSL2 introduces the concept of tags. Tags are names that can be given to attachables, so that
you can refer to them when performing `diff()`, `intersect()`, and `hulling()` operations.
@ -372,12 +435,171 @@ cube(50, center=true, $tags="hull") {
```
## Masking Children
TBW
## 3D Masking Attachments
To make it easier to mask away shapes from various edges of an attachable parent shape, there
are a few specialized alternatives to the `attach()` and `position()` modules.
### `edge_mask()`
If you have a 3D mask shape that you want to difference away from various edges, you can use
the `edge_mask()` module. This module will take a vertically oriented shape, and will rotate
and move it such that the BACK, RIGHT (X+,Y+) side of the shape will be aligned with the given
edges. The shape will be tagged as a "mask" so that you can use `diff("mask")`. For example,
here's a shape for rounding an edge:
```openscad
module round_edge(l,r) difference() {
translate([-1,-1,-l/2])
cube([r+1,r+1,l]);
translate([r,r])
cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
}
round_edge(l=30, r=19);
```
You can use that mask to round various edges of a cube:
```openscad
module round_edge(l,r) difference() {
translate([-1,-1,-l/2])
cube([r+1,r+1,l]);
translate([r,r])
cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
}
diff("mask")
cube([50,60,70],center=true)
edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT])
round_edge(l=71,r=10);
```
### `corner_mask()`
If you have a 3D mask shape that you want to difference away from various corners, you can use
the `corner_mask()` module. This module will take a shape and rotate and move it such that the
BACK RIGHT TOP (X+,Y+,Z+) side of the shape will be aligned with the given corner. The shape
will be tagged as a "mask" so that you can use `diff("mask")`. For example, here's a shape for
rounding a corner:
```openscad
module round_corner(r) difference() {
translate(-[1,1,1])
cube(r+1);
translate([r,r,r])
sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
}
round_corner(r=10);
```
You can use that mask to round various corners of a cube:
```openscad
module round_corner(r) difference() {
translate(-[1,1,1])
cube(r+1);
translate([r,r,r])
sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
}
diff("mask")
cube([50,60,70],center=true)
corner_mask([TOP,FRONT],LEFT+FRONT+TOP)
round_corner(r=10);
```
### Mix and Match Masks
You can use `edge_mask()` and `corner_mask()` together as well:
```openscad
module round_corner(r) difference() {
translate(-[1,1,1])
cube(r+1);
translate([r,r,r])
sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
}
module round_edge(l,r) difference() {
translate([-1,-1,-l/2])
cube([r+1,r+1,l]);
translate([r,r])
cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
}
diff("mask")
cube([50,60,70],center=true) {
edge_mask("ALL") round_edge(l=71,r=10);
corner_mask("ALL") round_corner(r=10);
}
```
## 2D Profile Mask Attachments
While 3D mask shapes give you a great deal of control, you need to make sure they are correctly
sized, and you need to provide separate mask shapes for corners and edges. Often, a single 2D
profile could be used to describe the edge mask shape (via `linear_extrude()`), and the corner
mask shape (via `rotate_extrude()`). This is where `edge_profile()`, `corner_profile()`, and
`face_profile()` come in.
### `edge_profile()`
Using the `edge_profile()` module, you can provide a 2D profile shape and it will be linearly
extruded to a mask of the apropriate length for each given edge. The resultant mask will be
tagged with "mask" so that you can difference it away with `diff("mask")`. The 2D profile is
assumed to be oriented with the BACK, RIGHT (X+,Y+) quadrant as the "cutter edge" that gets
re-oriented towards the edges of the parent shape. A typical mask profile for chamfering an
edge may look like:
```openscad-2D
mask2d_roundover(10);
```
Using that mask profile, you can mask the edges of a cube like:
```openscad
diff("mask")
cube([50,60,70],center=true)
edge_profile("ALL")
mask2d_roundover(10);
```
### `corner_profile()`
You can use the same profile to make a rounded corner mask as well:
```openscad
diff("mask")
cube([50,60,70],center=true)
corner_profile("ALL", r=10)
mask2d_roundover(10);
```
### `face_profile()`
As a simple shortcut to apply a profile mask to all edges and corners of a face, you can use the
`face_profile()` module:
```openscad
diff("mask")
cube([50,60,70],center=true)
face_profile(TOP, r=10)
mask2d_roundover(10);
```
## Coloring Attachables
TBW
Usually, when coloring a shape with the `color()` module, the parent color overrides the colors of
all children. This is often not what you want:
```openscad
$fn = 24;
color("red") spheroid(d=3) {
attach(CENTER,BOT) color("white") cyl(h=10, d=1) {
attach(TOP,BOT) color("green") cyl(h=5, d1=3, d2=0);
}
}
```
If you use the `recolor()` module, however, the child's color overrides the color of the parent.
This is probably easier to understand by example:
```openscad
$fn = 24;
recolor("red") spheroid(d=3) {
attach(CENTER,BOT) recolor("white") cyl(h=10, d=1) {
attach(TOP,BOT) recolor("green") cyl(h=5, d1=3, d2=0);
}
}
```
## Making Attachables
@ -683,6 +905,74 @@ stellate_cube() show_anchors(50);
## Making Named Anchors
TBW
While vector anchors are often useful, sometimes there are logically extra attachment points that
aren't on the perimeter of the shape. This is what named string anchors are for. For example,
the `teardrop()` shape uses a cylindrical geometry for it's vector anchors, but it also provides
a named anchor "cap" that is at the tip of the hat of the teardrop shape.
Named anchors are passed as an array of `anchorpt()`s to the `anchors=` argument of `attachable()`.
The `anchorpt()` call takes a name string, a positional point, an orientation vector, and a spin.
The name is the name of the anchor. The positional point is where the anchorpoint is at. The
orientation vector is the direction that a child attached at that anchorpoint should be oriented.
The spin is the number of degrees that an attached child should be rotated counter-clockwise around
the orientation vector. Spin is optional, and defaults to 0.
To make a simple attachable shape similar to a `teardrop()` that provides a "cap" anchor, you may
define it like this:
```openscad
module raindrop(r, thick, anchor=CENTER, spin=0, orient=UP) {
anchors = [
anchorpt("cap", [0,r/sin(45),0], BACK, 0)
];
attachable(anchor,spin,orient, r=r, l=thick, anchors=anchors) {
linear_extrude(height=thick, center=true) {
circle(r=r);
back(r*sin(45)) zrot(45) square(r, center=true);
}
children();
}
}
raindrop(r=25, thick=20, anchor="cap");
```
If you want multiple named anchors, just add them to the list of anchors:
```openscad-FlatSpin,VPD=150
module raindrop(r, thick, anchor=CENTER, spin=0, orient=UP) {
anchors = [
anchorpt("captop", [0,r/sin(45), thick/2], BACK+UP, 0),
anchorpt("cap", [0,r/sin(45), 0 ], BACK, 0),
anchorpt("capbot", [0,r/sin(45),-thick/2], BACK+DOWN, 0)
];
attachable(anchor,spin,orient, r=r, l=thick, anchors=anchors) {
linear_extrude(height=thick, center=true) {
circle(r=r);
back(r*sin(45)) zrot(45) square(r, center=true);
}
children();
}
}
raindrop(r=15, thick=10) show_anchors();
```
Sometimes the named anchor you want to add may be at a point that is reached through a complicated
set of translations and rotations. One quick way to calculate that point is to reproduce those
transformations in a transformation matrix chain. This is simplified by how you can use the
function forms of almost all the transformation modules to get the transformation matrices, and
chain them together with matrix multiplication. For example, if you have:
```
scale([1.1, 1.2, 1.3]) xrot(15) zrot(25) right(20) sphere(d=1);
```
and you want to calculate the centerpoint of the sphere, you can do it like:
```
sphere_pt = apply(
scale([1.1, 1.2, 1.3]) * xrot(15) * zrot(25) * right(20),
[0,0,0]
);
```

View file

@ -11,7 +11,7 @@
// Function: is_vector()
// Usage:
// is_vector(v, [length]);
// is_vector(v, <length>, ...);
// Description:
// Returns true if v is a list of finite numbers.
// Arguments:
@ -42,20 +42,17 @@ function is_vector(v, length, zero, all_nonzero=false, eps=EPSILON) =
&& (!all_nonzero || all_nonzero(v)) ;
// Function: vang()
// Function: v_theta()
// Usage:
// theta = vang([X,Y]);
// theta_phi = vang([X,Y,Z]);
// theta = v_theta([X,Y]);
// Description:
// Given a 2D vector, returns the angle in degrees counter-clockwise from X+ on the XY plane.
// Given a 3D vector, returns [THETA,PHI] where THETA is the number of degrees counter-clockwise from X+ on the XY plane, and PHI is the number of degrees up from the X+ axis along the XZ plane.
function vang(v) =
// Given a vector, returns the angle in degrees counter-clockwise from X+ on the XY plane.
function v_theta(v) =
assert( is_vector(v,2) || is_vector(v,3) , "Invalid vector")
len(v)==2? atan2(v.y,v.x) :
let(res=xyz_to_spherical(v)) [res[1], 90-res[2]];
atan2(v.y,v.x);
// Function: vmul()
// Function: v_mul()
// Description:
// Element-wise multiplication. Multiplies each element of `v1` by the corresponding element of `v2`.
// Both `v1` and `v2` must be the same length. Returns a vector of the products.
@ -63,13 +60,13 @@ function vang(v) =
// v1 = The first vector.
// v2 = The second vector.
// Example:
// vmul([3,4,5], [8,7,6]); // Returns [24, 28, 30]
function vmul(v1, v2) =
// v_mul([3,4,5], [8,7,6]); // Returns [24, 28, 30]
function v_mul(v1, v2) =
assert( is_list(v1) && is_list(v2) && len(v1)==len(v2), "Incompatible input")
[for (i = [0:1:len(v1)-1]) v1[i]*v2[i]];
// Function: vdiv()
// Function: v_div()
// Description:
// Element-wise vector division. Divides each element of vector `v1` by
// the corresponding element of vector `v2`. Returns a vector of the quotients.
@ -77,35 +74,35 @@ function vmul(v1, v2) =
// v1 = The first vector.
// v2 = The second vector.
// Example:
// vdiv([24,28,30], [8,7,6]); // Returns [3, 4, 5]
function vdiv(v1, v2) =
// v_div([24,28,30], [8,7,6]); // Returns [3, 4, 5]
function v_div(v1, v2) =
assert( is_vector(v1) && is_vector(v2,len(v1)), "Incompatible vectors")
[for (i = [0:1:len(v1)-1]) v1[i]/v2[i]];
// Function: vabs()
// Function: v_abs()
// Description: Returns a vector of the absolute value of each element of vector `v`.
// Arguments:
// v = The vector to get the absolute values of.
// Example:
// vabs([-1,3,-9]); // Returns: [1,3,9]
function vabs(v) =
// v_abs([-1,3,-9]); // Returns: [1,3,9]
function v_abs(v) =
assert( is_vector(v), "Invalid vector" )
[for (x=v) abs(x)];
// Function: vfloor()
// Function: v_floor()
// Description:
// Returns the given vector after performing a `floor()` on all items.
function vfloor(v) =
function v_floor(v) =
assert( is_vector(v), "Invalid vector" )
[for (x=v) floor(x)];
// Function: vceil()
// Function: v_ceil()
// Description:
// Returns the given vector after performing a `ceil()` on all items.
function vceil(v) =
function v_ceil(v) =
assert( is_vector(v), "Invalid vector" )
[for (x=v) ceil(x)];
@ -213,7 +210,7 @@ function vector_axis(v1,v2=undef,v3=undef) =
w1 = point3d(v1/norm(v1)),
w2 = point3d(v2/norm(v2)),
w3 = (norm(w1-w2) > eps && norm(w1+w2) > eps) ? w2
: (norm(vabs(w2)-UP) > eps)? UP
: (norm(v_abs(w2)-UP) > eps)? UP
: RIGHT
) unit(cross(w1,w3));
@ -291,6 +288,18 @@ function _vp_tree(ptlist, ind, leafsize) =
// tree = vantage point tree from vp_tree
// p = point to search for
// r = search radius
// Example: A set of four queries to find points within 1 unit of the query. The circles show the search region and all have radius 1.
// $fn=32;
// k = 2000;
// points = array_group(rands(0,10,k*2,seed=13333),2);
// vp = vp_tree(points);
// queries = [for(i=[3,7],j=[3,7]) [i,j]];
// search_ind = [for(q=queries) vp_search(points, vp, q, 1)];
// move_copies(points) circle(r=.08);
// for(i=idx(queries)){
// color("blue")stroke(move(queries[i],circle(r=1)), closed=true, width=.08);
// color("red")move_copies(select(points, search_ind[i])) circle(r=.08);
// }
function _vp_search(points, tree, p, r) =
is_list(tree[0]) ? [for(i=tree[0]) if (norm(points[i]-p)<=r) i]
:
@ -324,6 +333,20 @@ function vp_search(points, tree, p, r) =
// tree = vantage point tree from vp_tree
// p = point to search for
// k = number of neighbors to return
// Example: Four queries to find the 15 nearest points. The circles show the radius defined by the most distant query result. Note they are different for each query.
// $fn=32;
// k = 2000;
// points = array_group(rands(0,10,k*2,seed=13333),2);
// vp = vp_tree(points);
// queries = [for(i=[3,7],j=[3,7]) [i,j]];
// search_ind = [for(q=queries) vp_nearest(points, vp, q, 15)];
// move_copies(points) circle(r=.08);
// for(i=idx(queries)){
// color("red")move_copies(select(points, search_ind[i])) circle(r=.08);
// color("blue")stroke(move(queries[i],
// circle(r=norm(points[last(search_ind[i])]-queries[i]))),
// closed=true, width=.08);
// }
function _insert_sorted(list, k, new) =
len(list)==k && new[1]>= last(list)[1] ? list
: [

View file

@ -6,7 +6,7 @@
//////////////////////////////////////////////////////////////////////
BOSL_VERSION = [2,0,628];
BOSL_VERSION = [2,0,652];
// Section: BOSL Library Version Functions