mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
a9e72f06da
19 changed files with 520 additions and 352 deletions
122
affine.scad
122
affine.scad
|
@ -202,17 +202,19 @@ function affine3d_zrot(ang) = [
|
|||
// Arguments:
|
||||
// u = 3D axis vector to rotate around.
|
||||
// ang = number of degrees to rotate.
|
||||
function affine3d_rot_by_axis(u, ang) = let(
|
||||
u = unit(u),
|
||||
c = cos(ang),
|
||||
c2 = 1-c,
|
||||
s = sin(ang)
|
||||
) [
|
||||
[u[0]*u[0]*c2+c , u[0]*u[1]*c2-u[2]*s, u[0]*u[2]*c2+u[1]*s, 0],
|
||||
[u[1]*u[0]*c2+u[2]*s, u[1]*u[1]*c2+c , u[1]*u[2]*c2-u[0]*s, 0],
|
||||
[u[2]*u[0]*c2-u[1]*s, u[2]*u[1]*c2+u[0]*s, u[2]*u[2]*c2+c , 0],
|
||||
[ 0, 0, 0, 1]
|
||||
];
|
||||
function affine3d_rot_by_axis(u, ang) =
|
||||
approx(ang,0)? affine3d_identity() :
|
||||
let(
|
||||
u = unit(u),
|
||||
c = cos(ang),
|
||||
c2 = 1-c,
|
||||
s = sin(ang)
|
||||
) [
|
||||
[u.x*u.x*c2+c , u.x*u.y*c2-u.z*s, u.x*u.z*c2+u.y*s, 0],
|
||||
[u.y*u.x*c2+u.z*s, u.y*u.y*c2+c , u.y*u.z*c2-u.x*s, 0],
|
||||
[u.z*u.x*c2-u.y*s, u.z*u.y*c2+u.x*s, u.z*u.z*c2+c , 0],
|
||||
[ 0, 0, 0, 1]
|
||||
];
|
||||
|
||||
|
||||
// Function: affine3d_rot_from_to()
|
||||
|
@ -223,18 +225,23 @@ function affine3d_rot_by_axis(u, ang) = let(
|
|||
// Arguments:
|
||||
// from = 3D axis vector to rotate from.
|
||||
// to = 3D axis vector to rotate to.
|
||||
function affine3d_rot_from_to(from, to) = let(
|
||||
u = vector_axis(from,to),
|
||||
ang = vector_angle(from,to),
|
||||
c = cos(ang),
|
||||
c2 = 1-c,
|
||||
s = sin(ang)
|
||||
) [
|
||||
[u[0]*u[0]*c2+c , u[0]*u[1]*c2-u[2]*s, u[0]*u[2]*c2+u[1]*s, 0],
|
||||
[u[1]*u[0]*c2+u[2]*s, u[1]*u[1]*c2+c , u[1]*u[2]*c2-u[0]*s, 0],
|
||||
[u[2]*u[0]*c2-u[1]*s, u[2]*u[1]*c2+u[0]*s, u[2]*u[2]*c2+c , 0],
|
||||
[ 0, 0, 0, 1]
|
||||
];
|
||||
function affine3d_rot_from_to(from, to) =
|
||||
let(
|
||||
from = unit(point3d(from)),
|
||||
to = unit(point3d(to))
|
||||
) approx(from,to)? affine3d_identity() :
|
||||
let(
|
||||
u = vector_axis(from,to),
|
||||
ang = vector_angle(from,to),
|
||||
c = cos(ang),
|
||||
c2 = 1-c,
|
||||
s = sin(ang)
|
||||
) [
|
||||
[u.x*u.x*c2+c , u.x*u.y*c2-u.z*s, u.x*u.z*c2+u.y*s, 0],
|
||||
[u.y*u.x*c2+u.z*s, u.y*u.y*c2+c , u.y*u.z*c2-u.x*s, 0],
|
||||
[u.z*u.x*c2-u.y*s, u.z*u.y*c2+u.x*s, u.z*u.z*c2+c , 0],
|
||||
[ 0, 0, 0, 1]
|
||||
];
|
||||
|
||||
|
||||
// Function: affine_frame_map()
|
||||
|
@ -246,7 +253,7 @@ function affine3d_rot_from_to(from, to) = let(
|
|||
// Returns a transformation that maps one coordinate frame to another. You must specify two or three of `x`, `y`, and `z`. The specified
|
||||
// axes are mapped to the vectors you supplied. If you give two inputs, the third vector is mapped to the appropriate normal to maintain a right hand coordinate system.
|
||||
// If the vectors you give are orthogonal the result will be a rotation and the `reverse` parameter will supply the inverse map, which enables you
|
||||
// to map two arbitrary coordinate systems two each other by using the canonical coordinate system as an intermediary. You cannot use the `reverse` option
|
||||
// to map two arbitrary coordinate systems to each other by using the canonical coordinate system as an intermediary. You cannot use the `reverse` option
|
||||
// with non-orthogonal inputs.
|
||||
// Arguments:
|
||||
// x = Destination vector for x axis
|
||||
|
@ -259,30 +266,35 @@ function affine3d_rot_from_to(from, to) = let(
|
|||
// // The next map sends [1,1,0] to [0,1,1] and [-1,1,0] to [0,-1,1]
|
||||
// T = affine_frame_map(x=[0,1,1], y=[0,-1,1]) * affine_frame_map(x=[1,1,0], y=[-1,1,0],reverse=true);
|
||||
function affine_frame_map(x,y,z, reverse=false) =
|
||||
assert(num_defined([x,y,z])>=2, "Must define at least two inputs")
|
||||
let(
|
||||
xvalid = is_undef(x) || (is_vector(x) && len(x)==3),
|
||||
yvalid = is_undef(y) || (is_vector(y) && len(y)==3),
|
||||
zvalid = is_undef(z) || (is_vector(z) && len(z)==3)
|
||||
)
|
||||
assert(xvalid,"Input x must be a length 3 vector")
|
||||
assert(yvalid,"Input y must be a length 3 vector")
|
||||
assert(zvalid,"Input z must be a length 3 vector")
|
||||
let(
|
||||
x = is_def(x) ? unit(x) : undef,
|
||||
y = is_def(y) ? unit(y) : undef,
|
||||
z = is_def(z) ? unit(z) : undef,
|
||||
map = is_undef(x) ? [cross(y,z), y, z] :
|
||||
is_undef(y) ? [x, cross(z,x), z] :
|
||||
is_undef(z) ? [x, y, cross(x,y)] :
|
||||
[x, y, z]
|
||||
)
|
||||
reverse ?
|
||||
let( ocheck = approx(map[0]*map[1],0) && approx(map[0]*map[2],0) && approx(map[1]*map[2],0))
|
||||
assert(ocheck, "Inputs must be orthogonal when reverse==true")
|
||||
affine2d_to_3d(map)
|
||||
:
|
||||
affine2d_to_3d(transpose(map));
|
||||
assert(num_defined([x,y,z])>=2, "Must define at least two inputs")
|
||||
let(
|
||||
xvalid = is_undef(x) || (is_vector(x) && len(x)==3),
|
||||
yvalid = is_undef(y) || (is_vector(y) && len(y)==3),
|
||||
zvalid = is_undef(z) || (is_vector(z) && len(z)==3)
|
||||
)
|
||||
assert(xvalid,"Input x must be a length 3 vector")
|
||||
assert(yvalid,"Input y must be a length 3 vector")
|
||||
assert(zvalid,"Input z must be a length 3 vector")
|
||||
let(
|
||||
x = is_undef(x)? undef : unit(x),
|
||||
y = is_undef(y)? undef : unit(y),
|
||||
z = is_undef(z)? undef : unit(z),
|
||||
map = is_undef(x)? [cross(y,z), y, z] :
|
||||
is_undef(y)? [x, cross(z,x), z] :
|
||||
is_undef(z)? [x, y, cross(x,y)] :
|
||||
[x, y, z]
|
||||
)
|
||||
reverse? (
|
||||
let(
|
||||
ocheck = (
|
||||
approx(map[0]*map[1],0) &&
|
||||
approx(map[0]*map[2],0) &&
|
||||
approx(map[1]*map[2],0)
|
||||
)
|
||||
)
|
||||
assert(ocheck, "Inputs must be orthogonal when reverse==true")
|
||||
affine2d_to_3d(map)
|
||||
) : affine2d_to_3d(transpose(map));
|
||||
|
||||
|
||||
|
||||
|
@ -294,8 +306,10 @@ function affine_frame_map(x,y,z, reverse=false) =
|
|||
// Arguments:
|
||||
// v = The normal vector of the plane to reflect across.
|
||||
function affine3d_mirror(v) =
|
||||
let(v=unit(point3d(v)), a=v.x, b=v.y, c=v.z)
|
||||
[
|
||||
let(
|
||||
v=unit(point3d(v)),
|
||||
a=v.x, b=v.y, c=v.z
|
||||
) [
|
||||
[1-2*a*a, -2*a*b, -2*a*c, 0],
|
||||
[ -2*b*a, 1-2*b*b, -2*b*c, 0],
|
||||
[ -2*c*a, -2*c*b, 1-2*c*c, 0],
|
||||
|
@ -437,11 +451,13 @@ function apply_list(points,transform_list) =
|
|||
|
||||
|
||||
// Function: is_2d_transform()
|
||||
// Usage: is_2d_transform(t)
|
||||
// Description: Checks if the input is a 3d transform that does not act on the z coordinate, except
|
||||
// Usage:
|
||||
// is_2d_transform(t)
|
||||
// Description:
|
||||
// Checks if the input is a 3d transform that does not act on the z coordinate, except
|
||||
// possibly for a simple scaling of z. Note that an input which is only a zscale returns false.
|
||||
function is_2d_transform(t) = // z-parameters are zero, except we allow t[2][2]!=1 so scale() works
|
||||
t[2][0]==0 && t[2][1]==0 && t[2][3]==0 && t[0][2] == 0 && t[1][2]==0 &&
|
||||
t[2][0]==0 && t[2][1]==0 && t[2][3]==0 && t[0][2] == 0 && t[1][2]==0 &&
|
||||
(t[2][2]==1 || !(t[0][0]==1 && t[0][1]==0 && t[1][0]==0 && t[1][1]==1)); // But rule out zscale()
|
||||
|
||||
|
||||
|
|
|
@ -326,7 +326,7 @@ function attach_transform(anchor=CENTER, spin=0, orient=UP, geom, p) =
|
|||
ang = vector_angle(anch[2], DOWN),
|
||||
axis = vector_axis(anch[2], DOWN),
|
||||
ang2 = (anch[2]==UP || anch[2]==DOWN)? 0 : 180-anch[3],
|
||||
axis2 = rotate_points3d([axis],[0,0,ang2])[0]
|
||||
axis2 = rot(p=axis,[0,0,ang2])
|
||||
)
|
||||
affine3d_rot_by_axis(axis2,ang) *
|
||||
affine3d_zrot(ang2+spin) *
|
||||
|
|
14
beziers.scad
14
beziers.scad
|
@ -884,12 +884,14 @@ function _bezier_triangle(tri, splinesteps=16, vnf=EMPTY_VNF) =
|
|||
// trace_bezier_patches([patch], size=1, showcps=true);
|
||||
function bezier_patch_flat(size=[100,100], N=4, spin=0, orient=UP, trans=[0,0,0]) =
|
||||
let(
|
||||
patch = [for (x=[0:1:N]) [for (y=[0:1:N]) vmul(point3d(size),[x/N-0.5, 0.5-y/N, 0])]]
|
||||
) [for (row=patch)
|
||||
translate_points(v=trans,
|
||||
rotate_points3d(a=spin, from=UP, to=orient, row)
|
||||
)
|
||||
];
|
||||
patch = [
|
||||
for (x=[0:1:N]) [
|
||||
for (y=[0:1:N])
|
||||
vmul(point3d(size), [x/N-0.5, 0.5-y/N, 0])
|
||||
]
|
||||
],
|
||||
m = move(trans) * rot(a=spin, from=UP, to=orient)
|
||||
) [for (row=patch) apply(m, row)];
|
||||
|
||||
|
||||
|
||||
|
|
84
coords.scad
84
coords.scad
|
@ -100,90 +100,6 @@ function path4d(points, fill=0) =
|
|||
result + repeat(addition, len(result));
|
||||
|
||||
|
||||
// Function: rotate_points2d()
|
||||
// Usage:
|
||||
// rotate_points2d(pts, a, [cp]);
|
||||
// Description:
|
||||
// Rotates each 2D point in an array by a given amount, around an optional centerpoint.
|
||||
// Arguments:
|
||||
// pts = List of 3D points to rotate.
|
||||
// a = Angle to rotate by.
|
||||
// cp = 2D Centerpoint to rotate around. Default: `[0,0]`
|
||||
function rotate_points2d(pts, a, cp=[0,0]) =
|
||||
approx(a,0)? pts :
|
||||
let(
|
||||
cp = point2d(cp),
|
||||
pts = path2d(pts),
|
||||
m = affine2d_zrot(a)
|
||||
) [for (pt = pts) point2d(m*concat(pt-cp, [1])+cp)];
|
||||
|
||||
|
||||
// Function: rotate_points3d()
|
||||
// Usage:
|
||||
// rotate_points3d(pts, a, [cp], [reverse]);
|
||||
// rotate_points3d(pts, a, v, [cp], [reverse]);
|
||||
// rotate_points3d(pts, from, to, [a], [cp], [reverse]);
|
||||
// Description:
|
||||
// Rotates each 3D point in an array by a given amount, around a given centerpoint.
|
||||
// Arguments:
|
||||
// pts = List of points to rotate.
|
||||
// a = Rotation angle(s) in degrees.
|
||||
// v = If given, axis vector to rotate around.
|
||||
// cp = Centerpoint to rotate around.
|
||||
// from = If given, the vector to rotate something from. Used with `to`.
|
||||
// to = If given, the vector to rotate something to. Used with `from`.
|
||||
// reverse = If true, performs an exactly reversed rotation.
|
||||
function rotate_points3d(pts, a=0, v=undef, cp=[0,0,0], from=undef, to=undef, reverse=false) =
|
||||
assert(is_undef(from)==is_undef(to), "`from` and `to` must be given together.")
|
||||
(is_undef(from) && (a==0 || a==[0,0,0]))? pts :
|
||||
let (
|
||||
from = is_undef(from)? undef : (from / norm(from)),
|
||||
to = is_undef(to)? undef : (to / norm(to)),
|
||||
cp = point3d(cp),
|
||||
pts2 = path3d(pts)
|
||||
)
|
||||
(!is_undef(from) && approx(from,to) && (a==0 || a == [0,0,0]))? pts2 :
|
||||
let (
|
||||
mrot = reverse? (
|
||||
!is_undef(from)? (
|
||||
assert(norm(from)>0, "The from argument cannot equal [0,0] or [0,0,0]")
|
||||
assert(norm(to)>0, "The to argument cannot equal [0,0] or [0,0,0]")
|
||||
let (
|
||||
ang = vector_angle(from, to),
|
||||
v = vector_axis(from, to)
|
||||
)
|
||||
affine3d_rot_by_axis(from, -a) * affine3d_rot_by_axis(v, -ang)
|
||||
) : !is_undef(v)? (
|
||||
affine3d_rot_by_axis(v, -a)
|
||||
) : is_num(a)? (
|
||||
affine3d_zrot(-a)
|
||||
) : (
|
||||
affine3d_xrot(-a.x) * affine3d_yrot(-a.y) * affine3d_zrot(-a.z)
|
||||
)
|
||||
) : (
|
||||
!is_undef(from)? (
|
||||
assert(norm(from)>0, "The from argument cannot equal [0,0] or [0,0,0]")
|
||||
assert(norm(to)>0, "The to argument cannot equal [0,0] or [0,0,0]")
|
||||
let (
|
||||
from = from / norm(from),
|
||||
to = to / norm(from),
|
||||
ang = vector_angle(from, to),
|
||||
v = vector_axis(from, to)
|
||||
)
|
||||
affine3d_rot_by_axis(v, ang) * affine3d_rot_by_axis(from, a)
|
||||
) : !is_undef(v)? (
|
||||
affine3d_rot_by_axis(v, a)
|
||||
) : is_num(a)? (
|
||||
affine3d_zrot(a)
|
||||
) : (
|
||||
affine3d_zrot(a.z) * affine3d_yrot(a.y) * affine3d_xrot(a.x)
|
||||
)
|
||||
),
|
||||
m = affine3d_translate(cp) * mrot * affine3d_translate(-cp)
|
||||
)
|
||||
[for (pt = pts2) point3d(m*concat(pt, fill=1))];
|
||||
|
||||
|
||||
|
||||
// Section: Coordinate Systems
|
||||
|
||||
|
|
|
@ -1,48 +1,17 @@
|
|||
include <BOSL2/std.scad>
|
||||
include <BOSL2/paths.scad>
|
||||
include <BOSL2/beziers.scad>
|
||||
|
||||
module leaf(s) {
|
||||
path = [
|
||||
[0,0], [1.5,-1],
|
||||
[2,1], [0,3], [-2,1],
|
||||
[-1.5,-1], [0,0]
|
||||
];
|
||||
xrot(90)
|
||||
linear_sweep_bezier(
|
||||
scale_points(path, [s,s]/2),
|
||||
height=0.02
|
||||
);
|
||||
}
|
||||
|
||||
module branches(minsize, s1, s2){
|
||||
if(s2>minsize) {
|
||||
module tree(l=1500, sc=0.7, depth=10)
|
||||
recolor("lightgray")
|
||||
cylinder(l=l, d1=l/5, d2=l/5*sc)
|
||||
attach(TOP)
|
||||
zrot(gaussian_rands(90,20)[0])
|
||||
zrot_copies(n=floor(log_rands(2,5,4)[0]))
|
||||
zrot(gaussian_rands(0,5)[0])
|
||||
yrot(gaussian_rands(30,10)[0]) {
|
||||
sc = gaussian_rands(0.7,0.05)[0];
|
||||
cylinder(d1=s2, d2=s2*sc, l=s1)
|
||||
branches(minsize, s1*sc, s2*sc);
|
||||
}
|
||||
} else {
|
||||
recolor("springgreen")
|
||||
attach(TOP) zrot(90)
|
||||
leaf(gaussian_rands(100,5)[0]);
|
||||
}
|
||||
}
|
||||
|
||||
module tree(h, d, minsize) {
|
||||
sc = gaussian_rands(0.7,0.05)[0];
|
||||
recolor("lightgray") {
|
||||
cylinder(d1=d, d2=d*sc, l=h) {
|
||||
branches(minsize, h, d*sc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tree(d=300, h=1500, minsize=10);
|
||||
if (depth>0)
|
||||
zrot(90)
|
||||
zrot_copies(n=2)
|
||||
yrot(30) tree(depth=depth-1, l=l*sc, sc=sc);
|
||||
else
|
||||
recolor("springgreen")
|
||||
yscale(0.67)
|
||||
teardrop(d=l*3, l=1, anchor=BOT, spin=90);
|
||||
tree();
|
||||
|
||||
|
||||
// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
||||
|
|
|
@ -65,7 +65,7 @@ module orient_cubes() {
|
|||
}
|
||||
|
||||
for (ang = [0:90:270]) {
|
||||
off = rotate_points3d([40*BACK],ang)[0];
|
||||
off = rot(p=40*BACK,ang);
|
||||
translate(off) {
|
||||
orient_cube(ang);
|
||||
}
|
||||
|
|
48
examples/randomized_fractal_tree.scad
Normal file
48
examples/randomized_fractal_tree.scad
Normal file
|
@ -0,0 +1,48 @@
|
|||
include <BOSL2/std.scad>
|
||||
include <BOSL2/paths.scad>
|
||||
include <BOSL2/beziers.scad>
|
||||
|
||||
module leaf(s) {
|
||||
path = [
|
||||
[0,0], [1.5,-1],
|
||||
[2,1], [0,3], [-2,1],
|
||||
[-1.5,-1], [0,0]
|
||||
];
|
||||
xrot(90)
|
||||
linear_sweep_bezier(
|
||||
path * s/2,
|
||||
height=0.02
|
||||
);
|
||||
}
|
||||
|
||||
module branches(minsize, s1, s2){
|
||||
if(s2>minsize) {
|
||||
attach(TOP)
|
||||
zrot(gaussian_rands(90,20)[0])
|
||||
zrot_copies(n=floor(log_rands(2,5,4)[0]))
|
||||
zrot(gaussian_rands(0,5)[0])
|
||||
yrot(gaussian_rands(30,10)[0]) {
|
||||
sc = gaussian_rands(0.7,0.05)[0];
|
||||
cylinder(d1=s2, d2=s2*sc, l=s1)
|
||||
branches(minsize, s1*sc, s2*sc);
|
||||
}
|
||||
} else {
|
||||
recolor("springgreen")
|
||||
attach(TOP) zrot(90)
|
||||
leaf(gaussian_rands(100,5)[0]);
|
||||
}
|
||||
}
|
||||
|
||||
module tree(h, d, minsize) {
|
||||
sc = gaussian_rands(0.7,0.05)[0];
|
||||
recolor("lightgray") {
|
||||
cylinder(d1=d, d2=d*sc, l=h) {
|
||||
branches(minsize, h, d*sc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tree(d=300, h=1500, minsize=10);
|
||||
|
||||
|
||||
// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
|
@ -88,7 +88,7 @@ module hull_points(points, fast=false) {
|
|||
function hull2d_path(points) =
|
||||
(len(points) < 3)? [] : let(
|
||||
a=0, b=1,
|
||||
c = first_noncollinear(a, b, points, 2)
|
||||
c = first_noncollinear(a, b, points)
|
||||
) (c == len(points))? _hull2d_collinear(points) : let(
|
||||
remaining = [ for (i = [2:1:len(points)-1]) if (i != c) i ],
|
||||
ccw = triangle_area(points[a], points[b], points[c]) > 0,
|
||||
|
|
|
@ -757,7 +757,11 @@ function is_matrix(A,m,n, square=false) =
|
|||
// approx(0.3333,1/3); // Returns: false
|
||||
// approx(0.3333,1/3,eps=1e-3); // Returns: true
|
||||
// approx(PI,3.1415926536); // Returns: true
|
||||
function approx(a,b,eps=EPSILON) = let(c=a-b) (is_num(c)? abs(c) : norm(c)) <= eps;
|
||||
function approx(a,b,eps=EPSILON) =
|
||||
a==b? true :
|
||||
a*0!=b*0? false :
|
||||
is_list(a)? ([for (i=idx(a)) if(!approx(a[i],b[i],eps=eps)) 1] == []) :
|
||||
(abs(a-b) <= eps);
|
||||
|
||||
|
||||
function _type_num(x) =
|
||||
|
|
|
@ -337,7 +337,7 @@ module regular_polyhedron(
|
|||
// Would like to orient so an edge (longest edge?) is parallel to x axis
|
||||
facepts = move(translation, p=select(scaled_points, faces[i]));
|
||||
center = mean(facepts);
|
||||
rotatedface = rotate_points3d(move(-center, p=facepts), from=face_normals[i], to=[0,0,1]);
|
||||
rotatedface = rot(from=face_normals[i], to=[0,0,1], p=move(-center, p=facepts));
|
||||
clockwise = sortidx([for(pt=rotatedface) -atan2(pt.y,pt.x)]);
|
||||
$face = rotate_children?
|
||||
path2d(select(rotatedface,clockwise)) :
|
||||
|
@ -682,21 +682,22 @@ function regular_polyhedron_info(
|
|||
facedown = facedown == true ? (stellate==false? entry[facevertices][0] : 3) : facedown,
|
||||
down_direction = facedown == false? [0,0,-1] :
|
||||
faces_normals_vertices[1][search(facedown, faces_vertex_count)[0]],
|
||||
scaled_points = scalefactor * rotate_points3d(faces_normals_vertices[2], from=down_direction, to=[0,0,-1]),
|
||||
scaled_points = scalefactor * rot(p=faces_normals_vertices[2], from=down_direction, to=[0,0,-1]),
|
||||
bounds = pointlist_bounds(scaled_points),
|
||||
boundtable = [bounds[0], [0,0,0], bounds[1]],
|
||||
translation = [for(i=[0:2]) -boundtable[1+anchor[i]][i]],
|
||||
face_normals = rotate_points3d(faces_normals_vertices[1], from=down_direction, to=[0,0,-1]),
|
||||
face_normals = rot(p=faces_normals_vertices[1], from=down_direction, to=[0,0,-1]),
|
||||
side_length = scalefactor * entry[edgelen]
|
||||
)
|
||||
info == "fullentry" ? [scaled_points,
|
||||
translation,
|
||||
stellate ? faces : face_triangles,
|
||||
faces,
|
||||
face_normals,
|
||||
side_length*entry[in_radius]] :
|
||||
info == "vnf" ? [move(translation,p=scaled_points),
|
||||
stellate ? faces : face_triangles] :
|
||||
info == "fullentry" ? [
|
||||
scaled_points,
|
||||
translation,
|
||||
stellate ? faces : face_triangles,
|
||||
faces,
|
||||
face_normals,
|
||||
side_length*entry[in_radius]
|
||||
] :
|
||||
info == "vnf" ? [move(translation,p=scaled_points), stellate ? faces : face_triangles] :
|
||||
info == "vertices" ? move(translation,p=scaled_points) :
|
||||
info == "faces" ? faces :
|
||||
info == "face normals" ? face_normals :
|
||||
|
|
|
@ -763,7 +763,7 @@ module offset_sweep(
|
|||
top_start_ind = len(vertices_faces_bot[0]);
|
||||
initial_vertices_top = zip(path, repeat(middle,len(path)));
|
||||
vertices_faces_top = make_polyhedron(
|
||||
path, translate_points(offsets_top,[0,middle]),
|
||||
path, move(p=offsets_top,[0,middle]),
|
||||
struct_val(top,"offset"), !clockwise,
|
||||
struct_val(top,"quality"),
|
||||
struct_val(top,"check_valid"),
|
||||
|
@ -1278,7 +1278,7 @@ function _stroke_end(width,left, right, spec) =
|
|||
angle = struct_val(spec,"absolute")?
|
||||
angle_between_lines(left[0]-right[0],[cos(user_angle),sin(user_angle)]) :
|
||||
user_angle,
|
||||
endseg = [center, rotate_points2d([left[0]],angle, cp=center)[0]],
|
||||
endseg = [center, rot(p=[left[0]], angle, cp=center)[0]],
|
||||
intright = angle>0,
|
||||
pathclip = _path_line_intersection(intright? right : left, endseg),
|
||||
pathextend = line_intersection(endseg, select(intright? left:right,0,1))
|
||||
|
|
|
@ -54,56 +54,6 @@ module test_path4d() {
|
|||
test_path4d();
|
||||
|
||||
|
||||
module test_translate_points() {
|
||||
pts = [[0,0,1], [0,1,0], [1,0,0], [0,0,-1], [0,-1,0], [-1,0,0]];
|
||||
assert(translate_points(pts, v=[1,2,3]) == [[1,2,4], [1,3,3], [2,2,3], [1,2,2], [1,1,3], [0,2,3]]);
|
||||
assert(translate_points(pts, v=[-1,-2,-3]) == [[-1,-2,-2], [-1,-1,-3], [0,-2,-3], [-1,-2,-4], [-1,-3,-3], [-2,-2,-3]]);
|
||||
assert(translate_points(pts, v=[1,2]) == [[1,2,1], [1,3,0], [2,2,0], [1,2,-1], [1,1,0], [0,2,0]]);
|
||||
pts2 = [[0,1], [1,0], [0,-1], [-1,0]];
|
||||
assert(translate_points(pts2, v=[1,2]) == [[1,3], [2,2], [1,1], [0,2]]);
|
||||
}
|
||||
test_translate_points();
|
||||
|
||||
|
||||
module test_scale_points() {
|
||||
pts = [[0,0,1], [0,1,0], [1,0,0], [0,0,-1], [0,-1,0], [-1,0,0]];
|
||||
assert(scale_points(pts, v=[2,3,4]) == [[0,0,4], [0,3,0], [2,0,0], [0,0,-4], [0,-3,0], [-2,0,0]]);
|
||||
assert(scale_points(pts, v=[-2,-3,-4]) == [[0,0,-4], [0,-3,0], [-2,0,0], [0,0,4], [0,3,0], [2,0,0]]);
|
||||
assert(scale_points(pts, v=[1,1,1]) == [[0,0,1], [0,1,0], [1,0,0], [0,0,-1], [0,-1,0], [-1,0,0]]);
|
||||
assert(scale_points(pts, v=[-1,-1,-1]) == [[0,0,-1], [0,-1,0], [-1,0,0], [0,0,1], [0,1,0], [1,0,0]]);
|
||||
pts2 = [[0,1], [1,0], [0,-1], [-1,0]];
|
||||
assert(scale_points(pts2, v=[2,3]) == [[0,3], [2,0], [0,-3], [-2,0]]);
|
||||
}
|
||||
test_scale_points();
|
||||
|
||||
|
||||
module test_rotate_points2d() {
|
||||
pts = [[0,1], [1,0], [0,-1], [-1,0]];
|
||||
s = sin(45);
|
||||
assert(rotate_points2d(pts,45) == [[-s,s],[s,s],[s,-s],[-s,-s]]);
|
||||
assert(rotate_points2d(pts,90) == [[-1,0],[0,1],[1,0],[0,-1]]);
|
||||
assert(rotate_points2d(pts,90,cp=[1,0]) == [[0,-1],[1,0],[2,-1],[1,-2]]);
|
||||
}
|
||||
test_rotate_points2d();
|
||||
|
||||
|
||||
module test_rotate_points3d() {
|
||||
pts = [[0,0,1], [0,1,0], [1,0,0], [0,0,-1], [0,-1,0], [-1,0,0]];
|
||||
assert(rotate_points3d(pts, [90,0,0]) == [[0,-1,0], [0,0,1], [1,0,0], [0,1,0], [0,0,-1], [-1,0,0]]);
|
||||
assert(rotate_points3d(pts, [0,90,0]) == [[1,0,0], [0,1,0], [0,0,-1], [-1,0,0], [0,-1,0], [0,0,1]]);
|
||||
assert(rotate_points3d(pts, [0,0,90]) == [[0,0,1], [-1,0,0], [0,1,0], [0,0,-1], [1,0,0], [0,-1,0]]);
|
||||
assert(rotate_points3d(pts, [0,0,90],cp=[2,0,0]) == [[2,-2,1], [1,-2,0], [2,-1,0], [2,-2,-1], [3,-2,0], [2,-3,0]]);
|
||||
assert(rotate_points3d(pts, 90, v=UP) == [[0,0,1], [-1,0,0], [0,1,0], [0,0,-1], [1,0,0], [0,-1,0]]);
|
||||
assert(rotate_points3d(pts, 90, v=DOWN) == [[0,0,1], [1,0,0], [0,-1,0], [0,0,-1], [-1,0,0], [0,1,0]]);
|
||||
assert(rotate_points3d(pts, 90, v=RIGHT) == [[0,-1,0], [0,0,1], [1,0,0], [0,1,0], [0,0,-1], [-1,0,0]]);
|
||||
assert(rotate_points3d(pts, from=UP, to=BACK) == [[0,1,0], [0,0,-1], [1,0,0], [0,-1,0], [0,0,1], [-1,0,0]]);
|
||||
assert(rotate_points3d(pts, 90, from=UP, to=BACK), [[0,1,0], [-1,0,0], [0,0,-1], [0,-1,0], [1,0,0], [0,0,1]]);
|
||||
assert(rotate_points3d(pts, from=UP, to=UP*2) == [[0,0,1], [0,1,0], [1,0,0], [0,0,-1], [0,-1,0], [-1,0,0]]);
|
||||
assert(rotate_points3d(pts, from=UP, to=DOWN*2) == [[0,0,-1], [0,1,0], [-1,0,0], [0,0,1], [0,-1,0], [1,0,0]]);
|
||||
}
|
||||
test_rotate_points3d();
|
||||
|
||||
|
||||
module test_polar_to_xy() {
|
||||
assert(approx(polar_to_xy(20,45), [20/sqrt(2), 20/sqrt(2)]));
|
||||
assert(approx(polar_to_xy(20,135), [-20/sqrt(2), 20/sqrt(2)]));
|
||||
|
|
|
@ -11,11 +11,11 @@ function rec_cmp(a,b,eps=1e-9) =
|
|||
|
||||
module verify_f(actual,expected) {
|
||||
if (!rec_cmp(actual,expected)) {
|
||||
echo(str("Expected: ",fmtf(expected,10)));
|
||||
echo(str("Expected: ",fmt_float(expected,10)));
|
||||
echo(str(" : ",expected));
|
||||
echo(str("Actual : ",fmtf(actual,10)));
|
||||
echo(str("Actual : ",fmt_float(actual,10)));
|
||||
echo(str(" : ",actual));
|
||||
echo(str("Delta : ",fmtf(expected-actual,10)));
|
||||
echo(str("Delta : ",fmt_float(expected-actual,10)));
|
||||
echo(str(" : ",expected-actual));
|
||||
assert(approx(expected,actual));
|
||||
}
|
||||
|
|
159
tests/test_transforms.scad
Normal file
159
tests/test_transforms.scad
Normal file
|
@ -0,0 +1,159 @@
|
|||
include <BOSL2/std.scad>
|
||||
|
||||
module test(got,expect,extra_info) {
|
||||
if (
|
||||
is_undef(expect) != is_undef(got) ||
|
||||
expect*0 != got*0 ||
|
||||
(is_vnf(expect) && !all([for (i=idx(expect[0])) approx(got[0][i],expect[0][i])]) && got[1]!=expect[1]) ||
|
||||
(is_matrix(expect) && !all([for (i=idx(expect)) approx(got[i],expect[i])])) ||
|
||||
(got!=expect && !approx(got, expect))
|
||||
) {
|
||||
fmt = is_int(expect)? "{:.14i}" :
|
||||
is_num(expect)? "{:.14g}" :
|
||||
is_vector(expect)? "{:.14g}" :
|
||||
"{}";
|
||||
echofmt(str("Expected: ",fmt),[expect]);
|
||||
echofmt(str("But Got : ",fmt),[got]);
|
||||
if (expect*0 == got*0) {
|
||||
echofmt(str("Delta is: ",fmt),[expect-got]);
|
||||
}
|
||||
if (!is_undef(extra_info)) {
|
||||
echo(str("Extra Info: ",extra_info));
|
||||
}
|
||||
assert(false, "TEST FAILED!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module test_rot() {
|
||||
pts2d = 50 * [for (x=[-1,0,1],y=[-1,0,1]) [x,y]];
|
||||
pts3d = 50 * [for (x=[-1,0,1],y=[-1,0,1],z=[-1,0,1]) [x,y,z]];
|
||||
vecs2d = [
|
||||
for (x=[-1,0,1], y=[-1,0,1]) if(x!=0||y!=0) [x,y],
|
||||
polar_to_xy(1, -75),
|
||||
polar_to_xy(1, 75)
|
||||
];
|
||||
vecs3d = [
|
||||
LEFT, RIGHT, FRONT, BACK, DOWN, UP,
|
||||
spherical_to_xyz(1, -30, 45),
|
||||
spherical_to_xyz(1, 0, 45),
|
||||
spherical_to_xyz(1, 30, 45),
|
||||
spherical_to_xyz(2, 30, 45),
|
||||
spherical_to_xyz(1, -30, 135),
|
||||
spherical_to_xyz(2, -30, 135),
|
||||
spherical_to_xyz(1, 0, 135),
|
||||
spherical_to_xyz(1, 30, 135),
|
||||
spherical_to_xyz(1, -30, 75),
|
||||
spherical_to_xyz(1, 45, 45),
|
||||
];
|
||||
angs = [-180, -90, -45, 0, 30, 45, 90];
|
||||
for (a = [-360*3:360:360*3]) {
|
||||
test(rot(a), affine3d_identity(), extra_info=str("rot(",a,") != identity"));
|
||||
test(rot(a,p=pts2d), pts2d, extra_info=str("rot(",a,",p=...), 2D"));
|
||||
test(rot(a,p=pts3d), pts3d, extra_info=str("rot(",a,",p=...), 3D"));
|
||||
}
|
||||
test(rot(90), [[0,-1,0,0],[1,0,0,0],[0,0,1,0],[0,0,0,1]])
|
||||
for (a=angs) {
|
||||
test(rot(a), affine3d_zrot(a), extra_info=str("Z angle (only) = ",a));
|
||||
test(rot([a,0,0]), affine3d_xrot(a), extra_info=str("X angle = ",a));
|
||||
test(rot([0,a,0]), affine3d_yrot(a), extra_info=str("Y angle = ",a));
|
||||
test(rot([0,0,a]), affine3d_zrot(a), extra_info=str("Z angle = ",a));
|
||||
|
||||
test(rot(a,p=pts2d), apply(affine3d_zrot(a),pts2d), extra_info=str("Z angle (only) = ",a, ", p=..., 2D"));
|
||||
test(rot([0,0,a],p=pts2d), apply(affine3d_zrot(a),pts2d), extra_info=str("Z angle = ",a, ", p=..., 2D"));
|
||||
|
||||
test(rot(a,p=pts3d), apply(affine3d_zrot(a),pts3d), extra_info=str("Z angle (only) = ",a, ", p=..., 3D"));
|
||||
test(rot([a,0,0],p=pts3d), apply(affine3d_xrot(a),pts3d), extra_info=str("X angle = ",a, ", p=..., 3D"));
|
||||
test(rot([0,a,0],p=pts3d), apply(affine3d_yrot(a),pts3d), extra_info=str("Y angle = ",a, ", p=..., 3D"));
|
||||
test(rot([0,0,a],p=pts3d), apply(affine3d_zrot(a),pts3d), extra_info=str("Z angle = ",a, ", p=..., 3D"));
|
||||
}
|
||||
for (xa=angs, ya=angs, za=angs) {
|
||||
test(
|
||||
rot([xa,ya,za]),
|
||||
affine3d_chain([
|
||||
affine3d_xrot(xa),
|
||||
affine3d_yrot(ya),
|
||||
affine3d_zrot(za)
|
||||
]),
|
||||
extra_info=str("[X,Y,Z] = ",[xa,ya,za])
|
||||
);
|
||||
test(
|
||||
rot([xa,ya,za],p=pts3d),
|
||||
apply(
|
||||
affine3d_chain([
|
||||
affine3d_xrot(xa),
|
||||
affine3d_yrot(ya),
|
||||
affine3d_zrot(za)
|
||||
]),
|
||||
pts3d
|
||||
),
|
||||
extra_info=str("[X,Y,Z] = ",[xa,ya,za], ", p=...")
|
||||
);
|
||||
}
|
||||
for (vec1 = vecs3d) {
|
||||
for (ang = angs) {
|
||||
test(
|
||||
rot(a=ang, v=vec1),
|
||||
affine3d_rot_by_axis(vec1,ang),
|
||||
extra_info=str("a = ",ang,", v = ", vec1)
|
||||
);
|
||||
test(
|
||||
rot(a=ang, v=vec1, p=pts3d),
|
||||
apply(affine3d_rot_by_axis(vec1,ang), pts3d),
|
||||
extra_info=str("a = ",ang,", v = ", vec1, ", p=...")
|
||||
);
|
||||
}
|
||||
}
|
||||
for (vec1 = vecs2d) {
|
||||
for (vec2 = vecs2d) {
|
||||
test(
|
||||
rot(from=vec1, to=vec2, p=pts2d, planar=true),
|
||||
apply(affine2d_zrot(vang(vec2)-vang(vec1)), pts2d),
|
||||
extra_info=str(
|
||||
"from = ", vec1, ", ",
|
||||
"to = ", vec2, ", ",
|
||||
"planar = ", true, ", ",
|
||||
"p=..., 2D"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
for (vec1 = vecs3d) {
|
||||
for (vec2 = vecs3d) {
|
||||
for (a = angs) {
|
||||
test(
|
||||
rot(from=vec1, to=vec2, a=a),
|
||||
affine3d_chain([
|
||||
affine3d_zrot(a),
|
||||
affine3d_rot_from_to(vec1,vec2)
|
||||
]),
|
||||
extra_info=str(
|
||||
"from = ", vec1, ", ",
|
||||
"to = ", vec2, ", ",
|
||||
"a = ", a
|
||||
)
|
||||
);
|
||||
test(
|
||||
rot(from=vec1, to=vec2, a=a, p=pts3d),
|
||||
apply(
|
||||
affine3d_chain([
|
||||
affine3d_zrot(a),
|
||||
affine3d_rot_from_to(vec1,vec2)
|
||||
]),
|
||||
pts3d
|
||||
),
|
||||
extra_info=str(
|
||||
"from = ", vec1, ", ",
|
||||
"to = ", vec2, ", ",
|
||||
"a = ", a, ", ",
|
||||
"p=..., 3D"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
test_rot();
|
||||
|
||||
|
||||
// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
|
@ -60,7 +60,7 @@ module thread_helix(base_d, pitch, thread_depth=undef, thread_angle=15, twist=72
|
|||
[0, -cap/2-dz],
|
||||
]
|
||||
);
|
||||
pline = scale(pitch, p=profile);
|
||||
pline = profile * pitch;
|
||||
dir = left_handed? -1 : 1;
|
||||
idir = internal? -1 : 1;
|
||||
attachable(anchor,spin,orient, r=r, l=h) {
|
||||
|
|
137
transforms.scad
137
transforms.scad
|
@ -337,102 +337,56 @@ function up(z=0,p=undef) = move([0,0,z],p=p);
|
|||
// stroke(rot(30,p=path), closed=true);
|
||||
module rot(a=0, v=undef, cp=undef, from=undef, to=undef, reverse=false)
|
||||
{
|
||||
if (!is_undef(cp)) {
|
||||
translate(cp) rot(a=a, v=v, from=from, to=to, reverse=reverse) translate(-cp) children();
|
||||
} else if (!is_undef(from)) {
|
||||
assert(!is_undef(to), "`from` and `to` should be used together.");
|
||||
from = point3d(from);
|
||||
to = point3d(to);
|
||||
axis = vector_axis(from, to);
|
||||
ang = vector_angle(from, to);
|
||||
if (ang < 0.0001 && a == 0) {
|
||||
children(); // May be slightly faster?
|
||||
} else if (reverse) {
|
||||
rotate(a=-ang, v=axis) rotate(a=-a, v=from) children();
|
||||
} else {
|
||||
rotate(a=ang, v=axis) rotate(a=a, v=from) children();
|
||||
}
|
||||
} else if (a == 0) {
|
||||
children(); // May be slightly faster?
|
||||
} else if (reverse) {
|
||||
if (!is_undef(v)) {
|
||||
rotate(a=-a, v=v) children();
|
||||
} else if (is_num(a)) {
|
||||
rotate(-a) children();
|
||||
} else {
|
||||
rotate([-a[0],0,0]) rotate([0,-a[1],0]) rotate([0,0,-a[2]]) children();
|
||||
}
|
||||
} else {
|
||||
rotate(a=a, v=v) children();
|
||||
}
|
||||
m = rot(a=a, v=v, cp=cp, from=from, to=to, reverse=reverse, planar=false);
|
||||
multmatrix(m) children();
|
||||
}
|
||||
|
||||
function rot(a=0, v=undef, cp=undef, from=undef, to=undef, reverse=false, p=undef, planar=false) =
|
||||
function rot(a=0, v, cp, from, to, reverse=false, planar=false, p, _m) =
|
||||
assert(is_undef(from)==is_undef(to), "from and to must be specified together.")
|
||||
let(
|
||||
rev = reverse? -1 : 1,
|
||||
from = is_undef(from)? undef : point3d(from),
|
||||
to = is_undef(to)? undef : point3d(to)
|
||||
)
|
||||
is_undef(p)? (
|
||||
is_undef(cp)? (
|
||||
planar? (
|
||||
is_undef(from)? affine2d_zrot(a*rev) :
|
||||
affine2d_zrot(vector_angle(from,to)*sign(vector_axis(from,to)[2])*rev)
|
||||
) : (
|
||||
!is_undef(from)? affine3d_chain([
|
||||
affine3d_zrot(a*rev),
|
||||
affine3d_rot_by_axis(
|
||||
vector_axis(from,to),
|
||||
vector_angle(from,to)*rev
|
||||
)
|
||||
]) :
|
||||
!is_undef(v)? affine3d_rot_by_axis(v,a*rev) :
|
||||
is_num(a)? affine3d_zrot(a*rev) :
|
||||
reverse? affine3d_chain([affine3d_zrot(-a.z),affine3d_yrot(-a.y),affine3d_xrot(-a.x)]) :
|
||||
affine3d_chain([affine3d_xrot(a.x),affine3d_yrot(a.y),affine3d_zrot(a.z)])
|
||||
)
|
||||
) : (
|
||||
planar? (
|
||||
affine2d_chain([
|
||||
move(-cp),
|
||||
rot(a=a, v=v, from=from, to=to, reverse=reverse, planar=true),
|
||||
move(cp)
|
||||
])
|
||||
) : (
|
||||
affine3d_chain([
|
||||
move(-cp),
|
||||
rot(a=a, v=v, from=from, to=to, reverse=reverse),
|
||||
move(cp)
|
||||
])
|
||||
)
|
||||
)
|
||||
planar? let(
|
||||
cp = is_undef(cp)? cp : point2d(cp),
|
||||
m1 = is_undef(from)? affine2d_zrot(a) :
|
||||
assert(is_vector(from))
|
||||
assert(!approx(norm(from),0))
|
||||
assert(approx(point3d(from).z, 0))
|
||||
assert(is_vector(to))
|
||||
assert(!approx(norm(to),0))
|
||||
assert(approx(point3d(to).z, 0))
|
||||
affine2d_zrot(
|
||||
vang(point2d(to)) -
|
||||
vang(point2d(from))
|
||||
),
|
||||
m2 = is_undef(cp)? m1 : (move(cp) * m1 * move(-cp)),
|
||||
m3 = reverse? matrix_inverse(m2) : m2
|
||||
) m3 : let(
|
||||
from = is_undef(from)? undef : point3d(from),
|
||||
to = is_undef(to)? undef : point3d(to),
|
||||
cp = is_undef(cp)? undef : point3d(cp),
|
||||
m1 = !is_undef(from)? (
|
||||
assert(is_vector(from))
|
||||
assert(!approx(norm(from),0))
|
||||
assert(is_vector(to))
|
||||
assert(!approx(norm(to),0))
|
||||
affine3d_rot_from_to(from,to) * affine3d_zrot(a)
|
||||
) :
|
||||
!is_undef(v)? affine3d_rot_by_axis(v,a) :
|
||||
is_num(a)? affine3d_zrot(a) :
|
||||
affine3d_zrot(a.z) * affine3d_yrot(a.y) * affine3d_xrot(a.x),
|
||||
m2 = is_undef(cp)? m1 : (move(cp) * m1 * move(-cp)),
|
||||
m3 = reverse? matrix_inverse(m2) : m2
|
||||
) m3
|
||||
) : (
|
||||
assert(is_list(p))
|
||||
is_num(p.x)? (
|
||||
rot(a=a, v=v, cp=cp, from=from, to=to, reverse=reverse, p=[p], planar=planar)[0]
|
||||
) : is_vnf(p)? (
|
||||
[rot(a=a, v=v, cp=cp, from=from, to=to, reverse=reverse, p=p.x, planar=planar), p.y]
|
||||
) : is_list(p.x) && is_list(p.x.x)? (
|
||||
[for (l=p) rot(a=a, v=v, cp=cp, from=from, to=to, reverse=reverse, p=l, planar=planar)]
|
||||
) : (
|
||||
(
|
||||
(planar || (p!=[] && len(p[0])==2)) && !(
|
||||
(is_vector(a) && norm(point2d(a))>0) ||
|
||||
(!is_undef(v) && norm(point2d(v))>0 && !approx(a,0)) ||
|
||||
(!is_undef(from) && !approx(from,to) && !(abs(point3d(from).z)>0 || abs(point3d(to).z))) ||
|
||||
(!is_undef(from) && approx(from,to) && norm(point2d(from))>0 && a!=0)
|
||||
)
|
||||
)? (
|
||||
is_undef(from)? rotate_points2d(p, a=a*rev, cp=cp) : (
|
||||
approx(from,to)&&approx(a,0)? p :
|
||||
rotate_points2d(p, a=vector_angle(from,to)*sign(vector_axis(from,to)[2])*rev, cp=cp)
|
||||
)
|
||||
) : (
|
||||
let( cp = is_undef(cp)? [0,0,0] : cp )
|
||||
rotate_points3d(p, a=a, v=v, cp=cp, from=from, to=to, reverse=reverse)
|
||||
)
|
||||
)
|
||||
let(
|
||||
m = !is_undef(_m)? _m :
|
||||
rot(a=a, v=v, cp=cp, from=from, to=to, reverse=reverse, planar=planar),
|
||||
res = p==[]? [] :
|
||||
is_vector(p)? apply(m, p) :
|
||||
is_vnf(p)? [apply(m, p[0]), p[1]] :
|
||||
is_list(p[0])? [for (pp=p) rot(p=pp, _m=m)] :
|
||||
assert(false, "The p argument for rot() is not a point, path, patch, matrix, or VNF.")
|
||||
) res
|
||||
);
|
||||
|
||||
|
||||
|
@ -784,8 +738,9 @@ function mirror(v, p) =
|
|||
assert(is_list(p))
|
||||
let(m = len(v)==2? affine2d_mirror(v) : affine3d_mirror(v))
|
||||
is_undef(p)? m :
|
||||
is_num(p.x)? apply(m,p) :
|
||||
is_vnf(p)? [mirror(v=v,p=p[0]), [for (face=p[1]) reverse(face)]] :
|
||||
apply(m, p);
|
||||
[for (l=p) is_vector(l)? apply(m,l) : mirror(v=v, p=l)];
|
||||
|
||||
|
||||
// Function&Module: xflip()
|
||||
|
|
139
tutorials/Fractal_Tree.md
Normal file
139
tutorials/Fractal_Tree.md
Normal file
|
@ -0,0 +1,139 @@
|
|||
# Fractal Tree Tutorial
|
||||
|
||||
### Start with a Tree Trunk
|
||||
|
||||
Firstoff, include the BOSL2 library, then add a tapered cylinder for the tree trunk.
|
||||
|
||||
```openscad-example
|
||||
include <BOSL2/std.scad>
|
||||
cylinder(l=1500, d1=300, d2=210);
|
||||
```
|
||||
|
||||
### Parameterize It
|
||||
|
||||
It's easier to adjust a model if you split out the defining parameters.
|
||||
|
||||
```openscad-example
|
||||
include <BOSL2/std.scad>
|
||||
l = 1500;
|
||||
sc = 0.7;
|
||||
cylinder(l=l, d1=l/5, d2=l/5*sc);
|
||||
```
|
||||
|
||||
### Attaching Branches
|
||||
|
||||
You can attach branches to the top of the trunk by using `attach()` as a child of the trunk cylinder.
|
||||
|
||||
```openscad-example
|
||||
include <BOSL2/std.scad>
|
||||
l = 1500;
|
||||
sc = 0.7;
|
||||
cylinder(l=l, d1=l/5, d2=l/5*sc) {
|
||||
attach(TOP) yrot( 30) cylinder(l=l*sc, d1=l/5*sc, d2=l/5*sc*sc);
|
||||
attach(TOP) yrot(-30) cylinder(l=l*sc, d1=l/5*sc, d2=l/5*sc*sc);
|
||||
}
|
||||
```
|
||||
|
||||
### Replicate Branches
|
||||
|
||||
Instead of attaching each branch individually, you can attach multiple branch copies at once.
|
||||
|
||||
```openscad-example
|
||||
include <BOSL2/std.scad>
|
||||
l = 1500;
|
||||
sc = 0.7;
|
||||
cylinder(l=l, d1=l/5, d2=l/5*sc)
|
||||
attach(TOP)
|
||||
zrot_copies(n=2) // Make multiple rotated copies
|
||||
yrot(30) cylinder(l=l*sc, d1=l/5*sc, d2=l/5*sc*sc);
|
||||
```
|
||||
|
||||
### Make it a Module
|
||||
|
||||
Lets make this into a module, for convenience.
|
||||
|
||||
```openscad-example
|
||||
include <BOSL2/std.scad>
|
||||
module tree(l=1500, sc=0.7)
|
||||
cylinder(l=l, d1=l/5, d2=l/5*sc)
|
||||
attach(TOP)
|
||||
zrot_copies(n=2)
|
||||
yrot(30) cylinder(l=l*sc, d1=l/5*sc, d2=l/5*sc*sc);
|
||||
tree();
|
||||
```
|
||||
|
||||
### Use Recursion
|
||||
|
||||
Since branches look much like the main trunk, we can make it recursive. Don't forget the termination clause, or else it'll try to recurse forever!
|
||||
|
||||
```openscad-example
|
||||
include <BOSL2/std.scad>
|
||||
module tree(l=1500, sc=0.7, depth=10)
|
||||
cylinder(l=l, d1=l/5, d2=l/5*sc)
|
||||
attach(TOP)
|
||||
if (depth>0) // Important!
|
||||
zrot_copies(n=2)
|
||||
yrot(30) tree(depth=depth-1, l=l*sc, sc=sc);
|
||||
tree();
|
||||
```
|
||||
|
||||
### Make it Not Flat
|
||||
|
||||
A flat planar tree isn't what we want, so lets bush it out a bit by rotating each level 90 degrees.
|
||||
|
||||
```openscad-example
|
||||
include <BOSL2/std.scad>
|
||||
module tree(l=1500, sc=0.7, depth=10)
|
||||
cylinder(l=l, d1=l/5, d2=l/5*sc)
|
||||
attach(TOP)
|
||||
if (depth>0)
|
||||
zrot(90) // Bush it out
|
||||
zrot_copies(n=2)
|
||||
yrot(30) tree(depth=depth-1, l=l*sc, sc=sc);
|
||||
tree();
|
||||
```
|
||||
|
||||
### Adding Leaves
|
||||
|
||||
Let's add leaves. They look much like squashed versions of the standard teardrop() module, so lets use that.
|
||||
|
||||
```openscad-example
|
||||
include <BOSL2/std.scad>
|
||||
module tree(l=1500, sc=0.7, depth=10)
|
||||
cylinder(l=l, d1=l/5, d2=l/5*sc)
|
||||
attach(TOP)
|
||||
if (depth>0)
|
||||
zrot(90)
|
||||
zrot_copies(n=2)
|
||||
yrot(30) tree(depth=depth-1, l=l*sc, sc=sc);
|
||||
else
|
||||
yscale(0.67)
|
||||
teardrop(d=l*3, l=1, anchor=BOT, spin=90);
|
||||
tree();
|
||||
```
|
||||
|
||||
### Adding Color
|
||||
|
||||
We can finish this off with some color. The `color()` module will force all it's children and
|
||||
their descendants to the new color, even if they were colored before. The `recolor()` module,
|
||||
however, will only color children and decendants that don't already have a color set by a more
|
||||
nested `recolor()`.
|
||||
|
||||
```openscad-example
|
||||
include <BOSL2/std.scad>
|
||||
module tree(l=1500, sc=0.7, depth=10)
|
||||
recolor("lightgray")
|
||||
cylinder(l=l, d1=l/5, d2=l/5*sc)
|
||||
attach(TOP)
|
||||
if (depth>0)
|
||||
zrot(90)
|
||||
zrot_copies(n=2)
|
||||
yrot(30)
|
||||
tree(depth=depth-1, l=l*sc, sc=sc);
|
||||
else
|
||||
recolor("springgreen")
|
||||
yscale(0.67)
|
||||
teardrop(d=l*3, l=1, anchor=BOT, spin=90);
|
||||
tree();
|
||||
```
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
BOSL_VERSION = [2,0,209];
|
||||
BOSL_VERSION = [2,0,217];
|
||||
|
||||
|
||||
// Section: BOSL Library Version Functions
|
||||
|
|
15
vnf.scad
15
vnf.scad
|
@ -24,8 +24,17 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
|
|||
|
||||
|
||||
// Function: is_vnf()
|
||||
// Description: Returns true if the given value looks passingly like a VNF structure.
|
||||
function is_vnf(x) = is_list(x) && len(x)==2 && is_list(x[0]) && is_list(x[1]) && (x[0]==[] || is_vector(x[0][0])) && (x[1]==[] || is_vector(x[1][0]));
|
||||
// Usage:
|
||||
// bool = is_vnf(x);
|
||||
// Description:
|
||||
// Returns true if the given value looks like a VNF structure.
|
||||
function is_vnf(x) =
|
||||
is_list(x) &&
|
||||
len(x)==2 &&
|
||||
is_list(x[0]) &&
|
||||
is_list(x[1]) &&
|
||||
(x[0]==[] || (len(x[0])>=3 && is_vector(x[0][0]))) &&
|
||||
(x[1]==[] || is_vector(x[1][0]));
|
||||
|
||||
|
||||
// Function: is_vnf_list()
|
||||
|
@ -73,7 +82,7 @@ function vnf_quantize(vnf,q=pow(2,-12)) =
|
|||
// vnf4 = vnf_get_vertex(vnf3, p=[[1,3,2],[3,2,1]]); // Returns: [[1,2], [[[3,5,8],[3,2,1],[1,3,2]],[]]]
|
||||
function vnf_get_vertex(vnf=EMPTY_VNF, p) =
|
||||
is_path(p)? _vnf_get_vertices(vnf, p) :
|
||||
assert(is_vnf(vnf))
|
||||
assert(is_list(vnf) && len(vnf)==2 && is_list(vnf[0]) && is_list(vnf[1]), "Argument vnf should contain a VNF structure.")
|
||||
assert(is_vector(p))
|
||||
let(
|
||||
v = search([p], vnf[0])[0]
|
||||
|
|
Loading…
Reference in a new issue