big function re-org to eliminate debug.scad and hide affine.scad from docs

This commit is contained in:
Adrian Mariano 2021-10-01 00:30:28 -04:00
parent 261099e102
commit 6e3efd68a4
14 changed files with 707 additions and 881 deletions

View file

@ -1,5 +1,6 @@
DocsDirectory: BOSL2.wiki/
IgnoreFiles:
affine.scad
foo.scad
std.scad
bosl1compat.scad

View file

@ -6,474 +6,6 @@
//////////////////////////////////////////////////////////////////////
// Section: Matrix Manipulation
// Function: ident()
// Usage:
// mat = ident(n);
// Topics: Affine, Matrices
// Description:
// Create an `n` by `n` square identity matrix.
// Arguments:
// n = The size of the identity matrix square, `n` by `n`.
// Example:
// mat = ident(3);
// // Returns:
// // [
// // [1, 0, 0],
// // [0, 1, 0],
// // [0, 0, 1]
// // ]
// Example:
// mat = ident(4);
// // Returns:
// // [
// // [1, 0, 0, 0],
// // [0, 1, 0, 0],
// // [0, 0, 1, 0],
// // [0, 0, 0, 1]
// // ]
function ident(n) = [
for (i = [0:1:n-1]) [
for (j = [0:1:n-1]) (i==j)? 1 : 0
]
];
// Function: is_affine()
// Usage:
// bool = is_affine(x, [dim]);
// Topics: Affine, Matrices, Transforms, Type Checking
// See Also: is_matrix()
// Description:
// Tests if the given value is an affine matrix, possibly also checking it's dimenstion.
// Arguments:
// x = The value to test for being an affine matrix.
// dim = The number of dimensions the given affine is required to be for. Generally 2 for 2D or 3 for 3D. If given as a list of integers, allows any of the given dimensions. Default: `[2,3]`
// Example:
// bool = is_affine(affine2d_scale([2,3])); // Returns true
// bool = is_affine(affine3d_scale([2,3,4])); // Returns true
// bool = is_affine(affine3d_scale([2,3,4]),2); // Returns false
// bool = is_affine(affine3d_scale([2,3]),2); // Returns true
// bool = is_affine(affine3d_scale([2,3,4]),3); // Returns true
// bool = is_affine(affine3d_scale([2,3]),3); // Returns false
function is_affine(x,dim=[2,3]) =
is_finite(dim)? is_affine(x,[dim]) :
let( ll = len(x) )
is_list(x) && in_list(ll-1,dim) &&
[for (r=x) if(!is_list(r) || len(r)!=ll) 1] == [];
// Function: is_2d_transform()
// Usage:
// x = is_2d_transform(t);
// Topics: Affine, Matrices, Transforms, Type Checking
// See Also: is_affine(), is_matrix()
// 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.
// Arguments:
// t = The transformation matrix to check.
// Example:
// b = is_2d_transform(zrot(45)); // Returns: true
// b = is_2d_transform(yrot(45)); // Returns: false
// b = is_2d_transform(xrot(45)); // Returns: false
// b = is_2d_transform(move([10,20,0])); // Returns: true
// b = is_2d_transform(move([10,20,30])); // Returns: false
// b = is_2d_transform(scale([2,3,4])); // Returns: true
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][2]==1 || !(t[0][0]==1 && t[0][1]==0 && t[1][0]==0 && t[1][1]==1)); // But rule out zscale()
// Function: affine2d_to_3d()
// Usage:
// mat = affine2d_to_3d(m);
// Topics: Affine, Matrices, Transforms
// See Also: affine3d_to_2d()
// Description:
// Takes a 3x3 affine2d matrix and returns its 4x4 affine3d equivalent.
// Example:
// mat = affine2d_to_3d(affine2d_translate([10,20]));
// // Returns:
// // [
// // [1, 0, 0, 10],
// // [0, 1, 0, 20],
// // [0, 0, 1, 0],
// // [0, 0, 0, 1],
// // ]
function affine2d_to_3d(m) = [
[ m[0][0], m[0][1], 0, m[0][2] ],
[ m[1][0], m[1][1], 0, m[1][2] ],
[ 0, 0, 1, 0 ],
[ m[2][0], m[2][1], 0, m[2][2] ]
];
// Function: affine3d_to_2d()
// Usage:
// mat = affine3d_to_2d(m);
// Topics: Affine, Matrices
// See Also: affine2d_to_3d()
// Description:
// Takes a 4x4 affine3d matrix and returns its 3x3 affine2d equivalent. 3D transforms that would alter the Z coordinate are disallowed.
// Example:
// mat = affine2d_to_3d(affine3d_translate([10,20,0]));
// // Returns:
// // [
// // [1, 0, 10],
// // [0, 1, 20],
// // [0, 0, 1],
// // ]
function affine3d_to_2d(m) =
assert(is_2d_transform(m))
[
for (r=[0:3]) if (r!=2) [
for (c=[0:3]) if (c!=2) m[r][c]
]
];
// Function: apply()
// Usage:
// pts = apply(transform, points);
// Topics: Affine, Matrices, Transforms
// Description:
// Applies the specified transformation matrix to a point, pointlist, bezier patch or VNF.
// Both inputs can be 2D or 3D, and it is also allowed to supply 3D transformations with 2D
// data as long as the the only action on the z coordinate is a simple scaling.
// .
// If you construct your own matrices you can also use a transform that acts like a projection
// with fewer rows to produce lower dimensional output.
// Arguments:
// transform = The 2D or 3D transformation matrix to apply to the point/points.
// points = The point, pointlist, bezier patch, or VNF to apply the transformation to.
// Example(3D):
// path1 = path3d(circle(r=40));
// tmat = xrot(45);
// path2 = apply(tmat, path1);
// #stroke(path1,closed=true);
// stroke(path2,closed=true);
// Example(2D):
// path1 = circle(r=40);
// tmat = translate([10,5]);
// path2 = apply(tmat, path1);
// #stroke(path1,closed=true);
// stroke(path2,closed=true);
// Example(2D):
// path1 = circle(r=40);
// tmat = rot(30) * back(15) * scale([1.5,0.5,1]);
// path2 = apply(tmat, path1);
// #stroke(path1,closed=true);
// stroke(path2,closed=true);
function apply(transform,points) =
points==[] ? [] :
is_vector(points)
? /* Point */ apply(transform, [points])[0] :
is_list(points) && len(points)==2 && is_path(points[0],3) && is_list(points[1]) && is_vector(points[1][0])
? /* VNF */ [apply(transform, points[0]), points[1]] :
is_list(points) && is_list(points[0]) && is_vector(points[0][0])
? /* BezPatch */ [for (x=points) apply(transform,x)] :
let(
tdim = len(transform[0])-1,
datadim = len(points[0]),
outdim = min(datadim,len(transform)),
matrix = [for(i=[0:1:tdim]) [for(j=[0:1:outdim-1]) transform[j][i]]]
)
tdim==datadim && (datadim==3 || datadim==2) ? [for(p=points) concat(p,1)] * matrix
: tdim == 3 && datadim == 2 ?
assert(is_2d_transform(transform), str("Transforms is 3d but points are 2d"))
[for(p=points) concat(p,[0,1])]*matrix
: assert(false, str("Unsupported combination: transform with dimension ",tdim,", data of dimension ",datadim));
// Function: rot_decode()
// Usage:
// info = rot_decode(rotation,[long]); // Returns: [angle,axis,cp,translation]
// Topics: Affine, Matrices, Transforms
// Description:
// Given an input 3D rigid transformation operator (one composed of just rotations and translations) represented
// as a 4x4 matrix, compute the rotation and translation parameters of the operator. Returns a list of the
// four parameters, the angle, in the interval [0,180], the rotation axis as a unit vector, a centerpoint for
// the rotation, and a translation. If you set `parms = rot_decode(rotation)` then the transformation can be
// reconstructed from parms as `move(parms[3]) * rot(a=parms[0],v=parms[1],cp=parms[2])`. This decomposition
// makes it possible to perform interpolation. If you construct a transformation using `rot` the decoding
// may flip the axis (if you gave an angle outside of [0,180]). The returned axis will be a unit vector, and
// the centerpoint lies on the plane through the origin that is perpendicular to the axis. It may be different
// than the centerpoint you used to construct the transformation.
// .
// If you set `long` to true then return the reversed rotation, with the angle in [180,360].
// Arguments:
// rotation = rigid transformation to decode
// long = if true return the "long way" around, with the angle in [180,360]. Default: false
// Example:
// info = rot_decode(rot(45));
// // Returns: [45, [0,0,1], [0,0,0], [0,0,0]]
// Example:
// info = rot_decode(rot(a=37, v=[1,2,3], cp=[4,3,-7])));
// // Returns: [37, [0.26, 0.53, 0.80], [4.8, 4.6, -4.6], [0,0,0]]
// Example:
// info = rot_decode(left(12)*xrot(-33));
// // Returns: [33, [-1,0,0], [0,0,0], [-12,0,0]]
// Example:
// info = rot_decode(translate([3,4,5]));
// // Returns: [0, [0,0,1], [0,0,0], [3,4,5]]
function rot_decode(M,long=false) =
assert(is_matrix(M,4,4) && approx(M[3],[0,0,0,1]), "Input matrix must be a 4x4 matrix representing a 3d transformation")
let(R = submatrix(M,[0:2],[0:2]))
assert(approx(det3(R),1) && approx(norm_fro(R * transpose(R)-ident(3)),0),"Input matrix is not a rotation")
let(
translation = [for(row=[0:2]) M[row][3]], // translation vector
largest = max_index([R[0][0], R[1][1], R[2][2]]),
axis_matrix = R + transpose(R) - (matrix_trace(R)-1)*ident(3), // Each row is on the rotational axis
// Construct quaternion q = c * [x sin(theta/2), y sin(theta/2), z sin(theta/2), cos(theta/2)]
q_im = axis_matrix[largest],
q_re = R[(largest+2)%3][(largest+1)%3] - R[(largest+1)%3][(largest+2)%3],
c_sin = norm(q_im), // c * sin(theta/2) for some c
c_cos = abs(q_re) // c * cos(theta/2)
)
approx(c_sin,0) ? [0,[0,0,1],[0,0,0],translation] :
let(
angle = 2*atan2(c_sin, c_cos), // This is supposed to be more accurate than acos or asin
axis = (q_re>=0 ? 1:-1)*q_im/c_sin,
tproj = translation - (translation*axis)*axis, // Translation perpendicular to axis determines centerpoint
cp = (tproj + cross(axis,tproj)*c_cos/c_sin)/2
)
[long ? 360-angle:angle,
long? -axis : axis,
cp,
(translation*axis)*axis];
// Function: rot_inverse()
// Usage:
// B = rot_inverse(A)
// Description:
// Inverts a 2d (3x3) or 3d (4x4) rotation matrix. The matrix can be a rotation around any center,
// so it may include a translation.
function rot_inverse(T) =
assert(is_matrix(T,square=true),"Matrix must be square")
let( n = len(T))
assert(n==3 || n==4, "Matrix must be 3x3 or 4x4")
let(
rotpart = [for(i=[0:n-2]) [for(j=[0:n-2]) T[j][i]]],
transpart = [for(row=[0:n-2]) T[row][n-1]]
)
assert(approx(determinant(T),1),"Matrix is not a rotation")
concat(hstack(rotpart, -rotpart*transpart),[[for(i=[2:n]) 0, 1]]);
function _closest_angle(alpha,beta) =
is_vector(beta) ? [for(entry=beta) _closest_angle(alpha,entry)]
: beta-alpha > 180 ? beta - ceil((beta-alpha-180)/360) * 360
: beta-alpha < -180 ? beta + ceil((alpha-beta-180)/360) * 360
: beta;
// Smooth data with N point moving average. If angle=true handles data as angles.
// If closed=true assumes last point is adjacent to the first one.
// If closed=false pads data with left/right value (probably wrong behavior...should do linear interp)
function _smooth(data,len,closed=false,angle=false) =
let( halfwidth = floor(len/2),
result = closed ? [for(i=idx(data))
let(
window = angle ? _closest_angle(data[i],select(data,i-halfwidth,i+halfwidth))
: select(data,i-halfwidth,i+halfwidth)
)
mean(window)]
: [for(i=idx(data))
let(
window = select(data,max(i-halfwidth,0),min(i+halfwidth,len(data)-1)),
left = i-halfwidth<0,
pad = left ? data[0] : last(data)
)
sum(window)+pad*(len-len(window))] / len
)
result;
// Function: rot_resample()
// Usage:
// rlist = rot_resample(rotlist, N, [method], [twist], [scale], [smoothlen], [long], [turns], [closed])
// Description:
// Takes as input a list of rotation matrices in 3d. Produces as output a resampled
// list of rotation operators (4x4 matrixes) suitable for use with sweep(). You can optionally apply twist to
// the output with the twist parameter, which is either a scalar to apply a uniform
// overall twist, or a vector to apply twist non-uniformly. Similarly you can apply
// scaling either overall or with a vector. The smoothlen parameter applies smoothing
// to the twist and scaling to prevent abrupt changes. This is done by a moving average
// of the smoothing or scaling values. The default of 1 means no smoothing. The long parameter causes
// the interpolation to be done the "long" way around the rotation instead of the short way.
// Note that the rotation matrix cannot distinguish which way you rotate, only the place you
// end after rotation. Another ambiguity arises if your rotation is more than 360 degrees.
// You can add turns with the turns parameter, so giving turns=1 will add 360 degrees to the
// rotation so it completes one full turn plus the additional rotation given my the transform.
// You can give long as a scalar or as a vector. Finally if closed is true then the
// resampling will connect back to the beginning.
// .
// The default is to resample based on the length of the arc defined by each rotation operator. This produces
// uniform sampling over all of the transformations. It requires that each rotation has nonzero length.
// In this case N specifies the total number of samples. If you set method to "count" then N you get
// N samples for each transform. You can set N to a vector to vary the samples at each step.
// Arguments:
// rotlist = list of rotation operators in 3d to resample
// N = Number of rotations to produce as output when method is "length" or number for each transformation if method is "count". Can be a vector when method is "count"
// --
// method = sampling method, either "length" or "count"
// twist = scalar or vector giving twist to add overall or at each rotation. Default: none
// scale = scalar or vector giving scale factor to add overall or at each rotation. Default: none
// smoothlen = amount of smoothing to apply to scaling and twist. Should be an odd integer. Default: 1
// long = resample the "long way" around the rotation, a boolean or list of booleans. Default: false
// turns = add extra turns. If a scalar adds the turns to every rotation, or give a vector. Default: 0
// closed = if true then the rotation list is treated as closed. Default: false
// Example: Resampling the arc from a compound rotation with translations thrown in.
// tran = rot_resample([ident(4), back(5)*up(4)*xrot(-10)*zrot(-20)*yrot(117,cp=[10,0,0])], N=25);
// sweep(circle(r=1,$fn=3), tran);
// Example: Applying a scale factor
// tran = rot_resample([ident(4), back(5)*up(4)*xrot(-10)*zrot(-20)*yrot(117,cp=[10,0,0])], N=25, scale=2);
// sweep(circle(r=1,$fn=3), tran);
// Example: Applying twist
// tran = rot_resample([ident(4), back(5)*up(4)*xrot(-10)*zrot(-20)*yrot(117,cp=[10,0,0])], N=25, twist=60);
// sweep(circle(r=1,$fn=3), tran);
// Example: Going the long way
// tran = rot_resample([ident(4), back(5)*up(4)*xrot(-10)*zrot(-20)*yrot(117,cp=[10,0,0])], N=25, long=true);
// sweep(circle(r=1,$fn=3), tran);
// Example: Getting transformations from turtle3d
// include<BOSL2/turtle3d.scad>
// tran=turtle3d(["arcsteps",1,"up", 10, "arczrot", 10,170],transforms=true);
// sweep(circle(r=1,$fn=3),rot_resample(tran, N=40));
// Example: If you specify a larger angle in turtle you need to use the long argument
// include<BOSL2/turtle3d.scad>
// tran=turtle3d(["arcsteps",1,"up", 10, "arczrot", 10,270],transforms=true);
// sweep(circle(r=1,$fn=3),rot_resample(tran, N=40,long=true));
// Example: And if the angle is over 360 you need to add turns to get the right result. Note long is false when the remaining angle after subtracting full turns is below 180:
// include<BOSL2/turtle3d.scad>
// tran=turtle3d(["arcsteps",1,"up", 10, "arczrot", 10,90+360],transforms=true);
// sweep(circle(r=1,$fn=3),rot_resample(tran, N=40,long=false,turns=1));
// Example: Here the remaining angle is 270, so long must be set to true
// include<BOSL2/turtle3d.scad>
// tran=turtle3d(["arcsteps",1,"up", 10, "arczrot", 10,270+360],transforms=true);
// sweep(circle(r=1,$fn=3),rot_resample(tran, N=40,long=true,turns=1));
// Example: Note the visible line at the scale transition
// include<BOSL2/turtle3d.scad>
// tran = turtle3d(["arcsteps",1,"arcup", 10, 90, "arcdown", 10, 90], transforms=true);
// rtran = rot_resample(tran,200,scale=[1,6]);
// sweep(circle(1,$fn=32),rtran);
// Example: Observe how using a large smoothlen value eases that transition
// include<BOSL2/turtle3d.scad>
// tran = turtle3d(["arcsteps",1,"arcup", 10, 90, "arcdown", 10, 90], transforms=true);
// rtran = rot_resample(tran,200,scale=[1,6],smoothlen=17);
// sweep(circle(1,$fn=32),rtran);
// Example: A similar issues can arise with twist, where a "line" is visible at the transition
// include<BOSL2/turtle3d.scad>
// tran = turtle3d(["arcsteps", 1, "arcup", 10, 90, "move", 10], transforms=true,state=[1,-.5,0]);
// rtran = rot_resample(tran,100,twist=[0,60],smoothlen=1);
// sweep(subdivide_path(rect([3,3]),40),rtran);
// Example: Here's the smoothed twist transition
// include<BOSL2/turtle3d.scad>
// tran = turtle3d(["arcsteps", 1, "arcup", 10, 90, "move", 10], transforms=true,state=[1,-.5,0]);
// rtran = rot_resample(tran,100,twist=[0,60],smoothlen=17);
// sweep(subdivide_path(rect([3,3]),40),rtran);
// Example: toothed belt based on list-comprehension-demos example. This version has a smoothed twist transition. Try changing smoothlen to 1 to see the more abrupt transition that occurs without smoothing.
// include<BOSL2/turtle3d.scad>
// r_small = 19; // radius of small curve
// r_large = 46; // radius of large curve
// flat_length = 100; // length of flat belt section
// teeth=42; // number of teeth
// belt_width = 12;
// tooth_height = 9;
// belt_thickness = 3;
// angle = 180 - 2*atan((r_large-r_small)/flat_length);
// beltprofile = path3d(subdivide_path(
// square([belt_width, belt_thickness],anchor=FWD),
// 20));
// beltrots =
// turtle3d(["arcsteps",1,
// "move", flat_length,
// "arcleft", r_small, angle,
// "move", flat_length,
// // Closing path will be interpolated
// // "arcleft", r_large, 360-angle
// ],transforms=true);
// beltpath = rot_resample(beltrots,teeth*4,
// twist=[180,0,-180,0],
// long=[false,false,false,true],
// smoothlen=15,closed=true);
// belt = [for(i=idx(beltpath))
// let(tooth = floor((i+$t*4)/2)%2)
// apply(beltpath[i]*
// yscale(tooth
// ? tooth_height/belt_thickness
// : 1),
// beltprofile)
// ];
// skin(belt,slices=0,closed=true);
function rot_resample(rotlist,N,twist,scale,smoothlen=1,long=false,turns=0,closed=false,method="length") =
assert(is_int(smoothlen) && smoothlen>0 && smoothlen%2==1, "smoothlen must be a positive odd integer")
assert(method=="length" || method=="count")
let(tcount = len(rotlist) + (closed?0:-1))
assert(method=="count" || is_int(N), "N must be an integer when method is \"length\"")
assert(is_int(N) || is_vector(N,tcount), str("N must be scalar or vector with length ",tcount))
let(
count = method=="length" ? (closed ? N+1 : N)
: (is_vector(N) ? sum(N) : tcount*N)+1 //(closed?0:1)
)
assert(is_bool(long) || len(long)==tcount,str("Input long must be a scalar or have length ",tcount))
let(
long = force_list(long,tcount),
turns = force_list(turns,tcount),
T = [for(i=[0:1:tcount-1]) rot_inverse(rotlist[i])*select(rotlist,i+1)],
parms = [for(i=idx(T))
let(tparm = rot_decode(T[i],long[i]))
[tparm[0]+turns[i]*360,tparm[1],tparm[2],tparm[3]]
],
radius = [for(i=idx(parms)) norm(parms[i][2])],
length = [for(i=idx(parms)) norm([norm(parms[i][3]), parms[i][0]/360*2*PI*radius[i]])]
)
assert(method=="count" || all_positive(length),
"Rotation list includes a repeated entry or a rotation around the origin, not allowed when method=\"length\"")
let(
cumlen = [0, each cumsum(length)],
totlen = last(cumlen),
stepsize = totlen/(count-1),
samples = method=="count"
? let( N = force_list(N,tcount))
[for(n=N) lerpn(0,1,n,endpoint=false)]
:[for(i=idx(parms))
let(
remainder = cumlen[i] % stepsize,
offset = remainder==0 ? 0
: stepsize-remainder,
num = ceil((length[i]-offset)/stepsize)
)
count(num,offset,stepsize)/length[i]],
twist = first_defined([twist,0]),
scale = first_defined([scale,1]),
needlast = !approx(last(last(samples)),1),
sampletwist = is_num(twist) ? lerpn(0,twist,count)
: let(
cumtwist = [0,each cumsum(twist)]
)
[for(i=idx(parms)) each lerp(cumtwist[i],cumtwist[i+1],samples[i]),
if (needlast) last(cumtwist)
],
samplescale = is_num(scale) ? lerp(1,scale,lerpn(0,1,count))
: let(
cumscale = [1,each cumprod(scale)]
)
[for(i=idx(parms)) each lerp(cumscale[i],cumscale[i+1],samples[i]),
if (needlast) last(cumscale)],
smoothtwist = _smooth(closed?select(sampletwist,0,-2):sampletwist,smoothlen,closed=closed,angle=true),
smoothscale = _smooth(samplescale,smoothlen,closed=closed),
interpolated = [
for(i=idx(parms))
each [for(u=samples[i]) rotlist[i] * move(u*parms[i][3]) * rot(a=u*parms[i][0],v=parms[i][1],cp=parms[i][2])],
if (needlast) last(rotlist)
]
)
[for(i=idx(interpolated,e=closed?-2:-1)) interpolated[i]*zrot(smoothtwist[i])*scale(smoothscale[i])];
// Section: Affine2d 3x3 Transformation Matrices

View file

@ -1985,4 +1985,29 @@ function is_matrix_symmetric(A,eps=1e-12) =
approx(A,transpose(A), eps);
// Function&Module: echo_matrix()
// Usage:
// echo_matrix(M, [description=], [sig=], [eps=]);
// dummy = echo_matrix(M, [description=], [sig=], [eps=]),
// Description:
// Display a numerical matrix in a readable columnar format with `sig` significant
// digits. Values smaller than eps display as zero. If you give a description
// it is displayed at the top.
function echo_matrix(M,description,sig=4,eps=1e-9) =
let(
horiz_line = chr(8213),
matstr = matrix_strings(M,sig=sig,eps=eps),
separator = str_join(repeat(horiz_line,10)),
dummy=echo(str(separator," ",is_def(description) ? description : ""))
[for(row=matstr) echo(row)]
)
echo(separator);
module echo_matrix(M,description,sig=4,eps=1e-9)
{
dummy = echo_matrix(M,description,sig,eps);
}
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -1759,5 +1759,181 @@ function _attachment_is_shown(tags) =
) shown && !hidden;
// Section: Visualizing Anchors
/// Internal Function: _standard_anchors()
/// Usage:
/// anchs = _standard_anchors([two_d]);
/// Description:
/// Return the vectors for all standard anchors.
/// Arguments:
/// two_d = If true, returns only the anchors where the Z component is 0. Default: false
function _standard_anchors(two_d=false) = [
for (
zv = [
if (!two_d) TOP,
CENTER,
if (!two_d) BOTTOM
],
yv = [FRONT, CENTER, BACK],
xv = [LEFT, CENTER, RIGHT]
) xv+yv+zv
];
// Module: show_anchors()
// Usage:
// ... show_anchors([s], [std=], [custom=]);
// Description:
// Show all standard anchors for the parent object.
// Arguments:
// s = Length of anchor arrows.
// ---
// std = If true (default), show standard anchors.
// custom = If true (default), show custom anchors.
// Example(FlatSpin,VPD=333):
// cube(50, center=true) show_anchors();
module show_anchors(s=10, std=true, custom=true) {
check = assert($parent_geom != undef) 1;
two_d = _attach_geom_2d($parent_geom);
if (std) {
for (anchor=_standard_anchors(two_d=two_d)) {
if(two_d) {
attach(anchor) anchor_arrow2d(s);
} else {
attach(anchor) anchor_arrow(s);
}
}
}
if (custom) {
for (anchor=last($parent_geom)) {
attach(anchor[0]) {
if(two_d) {
anchor_arrow2d(s, color="cyan");
} else {
anchor_arrow(s, color="cyan");
}
color("black")
noop($tags="anchor-arrow") {
xrot(two_d? 0 : 90) {
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);
}
}
}
}
}
}
children();
}
// Module: anchor_arrow()
// Usage:
// anchor_arrow([s], [color], [flag]);
// Description:
// Show an anchor orientation arrow. By default, tagged with the name "anchor-arrow".
// Arguments:
// s = Length of the arrows. Default: `10`
// color = Color of the arrow. Default: `[0.333, 0.333, 1]`
// flag = If true, draw the orientation flag on the arrowhead. Default: true
// Example:
// anchor_arrow(s=20);
module anchor_arrow(s=10, color=[0.333,0.333,1], flag=true, $tags="anchor-arrow") {
$fn=12;
recolor("gray") spheroid(d=s/6) {
attach(CENTER,BOT) recolor(color) cyl(h=s*2/3, d=s/15) {
attach(TOP,BOT) cyl(h=s/3, d1=s/5, d2=0) {
if(flag) {
position(BOT)
recolor([1,0.5,0.5])
cuboid([s/100, s/6, s/4], anchor=FRONT+BOT);
}
children();
}
}
}
}
// Module: anchor_arrow2d()
// Usage:
// anchor_arrow2d([s], [color], [flag]);
// Description:
// Show an anchor orientation arrow.
// Arguments:
// s = Length of the arrows.
// color = Color of the arrow.
// Example:
// anchor_arrow2d(s=20);
module anchor_arrow2d(s=15, color=[0.333,0.333,1], $tags="anchor-arrow") {
noop() color(color) stroke([[0,0],[0,s]], width=s/10, endcap1="butt", endcap2="arrow2");
}
// Module: expose_anchors()
// Usage:
// expose_anchors(opacity) {child1() show_anchors(); child2() show_anchors(); ...}
// Description:
// Used in combination with show_anchors() to display an object in transparent gray with its anchors in solid color.
// Children will appear transparent and any anchor arrows drawn with will appear in solid color.
// Arguments:
// opacity = The opacity of the children. 0.0 is invisible, 1.0 is opaque. Default: 0.2
// Example(FlatSpin,VPD=333):
// expose_anchors() cube(50, center=true) show_anchors();
module expose_anchors(opacity=0.2) {
show("anchor-arrow")
children();
hide("anchor-arrow")
color(is_undef($color)? [0,0,0] :
is_string($color)? $color :
point3d($color), opacity)
children();
}
// Module: frame_ref()
// Usage:
// frame_ref(s, opacity);
// Description:
// Displays X,Y,Z axis arrows in red, green, and blue respectively.
// Arguments:
// s = Length of the arrows.
// opacity = The opacity of the arrows. 0.0 is invisible, 1.0 is opaque. Default: 1.0
// Examples:
// frame_ref(25);
// frame_ref(30, opacity=0.5);
module frame_ref(s=15, opacity=1) {
cube(0.01, center=true) {
attach([1,0,0]) anchor_arrow(s=s, flag=false, color=[1.0, 0.3, 0.3, opacity]);
attach([0,1,0]) anchor_arrow(s=s, flag=false, color=[0.3, 1.0, 0.3, opacity]);
attach([0,0,1]) anchor_arrow(s=s, flag=false, color=[0.3, 0.3, 1.0, opacity]);
children();
}
}
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -1,350 +0,0 @@
//////////////////////////////////////////////////////////////////////
// LibFile: debug.scad
// Helpers to make debugging OpenScad code easier.
// Includes:
// include <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
// Function: standard_anchors()
// Usage:
// anchs = standard_anchors([two_d]);
// Description:
// Return the vectors for all standard anchors.
// Arguments:
// two_d = If true, returns only the anchors where the Z component is 0. Default: false
function standard_anchors(two_d=false) = [
for (
zv = [
if (!two_d) TOP,
CENTER,
if (!two_d) BOTTOM
],
yv = [FRONT, CENTER, BACK],
xv = [LEFT, CENTER, RIGHT]
) xv+yv+zv
];
// Module: anchor_arrow()
// Usage:
// anchor_arrow([s], [color], [flag]);
// Description:
// Show an anchor orientation arrow. By default, tagged with the name "anchor-arrow".
// Arguments:
// s = Length of the arrows. Default: `10`
// color = Color of the arrow. Default: `[0.333, 0.333, 1]`
// flag = If true, draw the orientation flag on the arrowhead. Default: true
// Example:
// anchor_arrow(s=20);
module anchor_arrow(s=10, color=[0.333,0.333,1], flag=true, $tags="anchor-arrow") {
$fn=12;
recolor("gray") spheroid(d=s/6) {
attach(CENTER,BOT) recolor(color) cyl(h=s*2/3, d=s/15) {
attach(TOP,BOT) cyl(h=s/3, d1=s/5, d2=0) {
if(flag) {
position(BOT)
recolor([1,0.5,0.5])
cuboid([s/100, s/6, s/4], anchor=FRONT+BOT);
}
children();
}
}
}
}
// Module: anchor_arrow2d()
// Usage:
// anchor_arrow2d([s], [color], [flag]);
// Description:
// Show an anchor orientation arrow.
// Arguments:
// s = Length of the arrows.
// color = Color of the arrow.
// Example:
// anchor_arrow2d(s=20);
module anchor_arrow2d(s=15, color=[0.333,0.333,1], $tags="anchor-arrow") {
noop() color(color) stroke([[0,0],[0,s]], width=s/10, endcap1="butt", endcap2="arrow2");
}
// Module: expose_anchors()
// Usage:
// expose_anchors(opacity) {...}
// Description:
// Makes the children transparent gray, while showing any anchor arrows that may exist.
// Arguments:
// opacity = The opacity of the arrow. 0.0 is invisible, 1.0 is opaque. Default: 0.2
// Example(FlatSpin,VPD=333):
// expose_anchors() cube(50, center=true) show_anchors();
module expose_anchors(opacity=0.2) {
show("anchor-arrow")
children();
hide("anchor-arrow")
color(is_undef($color)? [0,0,0] :
is_string($color)? $color :
point3d($color), opacity)
children();
}
// Module: show_anchors()
// Usage:
// ... show_anchors([s], [std=], [custom=]);
// Description:
// Show all standard anchors for the parent object.
// Arguments:
// s = Length of anchor arrows.
// ---
// std = If true (default), show standard anchors.
// custom = If true (default), show custom anchors.
// Example(FlatSpin,VPD=333):
// cube(50, center=true) show_anchors();
module show_anchors(s=10, std=true, custom=true) {
check = assert($parent_geom != undef) 1;
two_d = _attach_geom_2d($parent_geom);
if (std) {
for (anchor=standard_anchors(two_d=two_d)) {
if(two_d) {
attach(anchor) anchor_arrow2d(s);
} else {
attach(anchor) anchor_arrow(s);
}
}
}
if (custom) {
for (anchor=last($parent_geom)) {
attach(anchor[0]) {
if(two_d) {
anchor_arrow2d(s, color="cyan");
} else {
anchor_arrow(s, color="cyan");
}
color("black")
noop($tags="anchor-arrow") {
xrot(two_d? 0 : 90) {
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);
}
}
}
}
}
}
children();
}
// Module: frame_ref()
// Usage:
// frame_ref(s, opacity);
// Description:
// Displays X,Y,Z axis arrows in red, green, and blue respectively.
// Arguments:
// s = Length of the arrows.
// opacity = The opacity of the arrows. 0.0 is invisible, 1.0 is opaque. Default: 1.0
// Examples:
// frame_ref(25);
// frame_ref(30, opacity=0.5);
module frame_ref(s=15, opacity=1) {
cube(0.01, center=true) {
attach([1,0,0]) anchor_arrow(s=s, flag=false, color=[1.0, 0.3, 0.3, opacity]);
attach([0,1,0]) anchor_arrow(s=s, flag=false, color=[0.3, 1.0, 0.3, opacity]);
attach([0,0,1]) anchor_arrow(s=s, flag=false, color=[0.3, 0.3, 1.0, opacity]);
children();
}
}
// Module: ruler()
// Usage:
// ruler(length, width, [thickness=], [depth=], [labels=], [pipscale=], [maxscale=], [colors=], [alpha=], [unit=], [inch=]);
// Description:
// Creates a ruler for checking dimensions of the model
// Arguments:
// length = length of the ruler. Default 100
// width = width of the ruler. Default: size of the largest unit division
// ---
// thickness = thickness of the ruler. Default: 1
// depth = the depth of mark subdivisions. Default: 3
// labels = draw numeric labels for depths where labels are larger than 1. Default: false
// pipscale = width scale of the pips relative to the next size up. Default: 1/3
// maxscale = log10 of the maximum width divisions to display. Default: based on input length
// colors = colors to use for the ruler, a list of two values. Default: `["black","white"]`
// alpha = transparency value. Default: 1.0
// unit = unit to mark. Scales the ruler marks to a different length. Default: 1
// inch = set to true for a ruler scaled to inches (assuming base dimension is mm). Default: false
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `LEFT+BACK+TOP`
// 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`
// Examples(2D,Big):
// ruler(100,depth=3);
// ruler(100,depth=3,labels=true);
// ruler(27);
// ruler(27,maxscale=0);
// ruler(100,pipscale=3/4,depth=2);
// ruler(100,width=2,depth=2);
// Example(2D,Big): Metric vs Imperial
// ruler(12,width=50,inch=true,labels=true,maxscale=0);
// fwd(50)ruler(300,width=50,labels=true);
module ruler(length=100, width, thickness=1, depth=3, labels=false, pipscale=1/3, maxscale, colors=["black","white"], alpha=1.0, unit=1, inch=false, anchor=LEFT+BACK+TOP, spin=0, orient=UP)
{
inchfactor = 25.4;
assert(depth<=5, "Cannot render scales smaller than depth=5");
assert(len(colors)==2, "colors must contain a list of exactly two colors.");
length = inch ? inchfactor * length : length;
unit = inch ? inchfactor*unit : unit;
maxscale = is_def(maxscale)? maxscale : floor(log(length/unit-EPSILON));
scales = unit * [for(logsize = [maxscale:-1:maxscale-depth+1]) pow(10,logsize)];
widthfactor = (1-pipscale) / (1-pow(pipscale,depth));
width = default(width, scales[0]);
widths = width * widthfactor * [for(logsize = [0:-1:-depth+1]) pow(pipscale,-logsize)];
offsets = concat([0],cumsum(widths));
attachable(anchor,spin,orient, size=[length,width,thickness]) {
translate([-length/2, -width/2, 0])
for(i=[0:1:len(scales)-1]) {
count = ceil(length/scales[i]);
fontsize = 0.5*min(widths[i], scales[i]/ceil(log(count*scales[i]/unit)));
back(offsets[i]) {
xcopies(scales[i], n=count, sp=[0,0,0]) union() {
actlen = ($idx<count-1) || approx(length%scales[i],0) ? scales[i] : length % scales[i];
color(colors[$idx%2], alpha=alpha) {
w = i>0 ? quantup(widths[i],1/1024) : widths[i]; // What is the i>0 test supposed to do here?
cube([quantup(actlen,1/1024),quantup(w,1/1024),thickness], anchor=FRONT+LEFT);
}
mark =
i == 0 && $idx % 10 == 0 && $idx != 0 ? 0 :
i == 0 && $idx % 10 == 9 && $idx != count-1 ? 1 :
$idx % 10 == 4 ? 1 :
$idx % 10 == 5 ? 0 : -1;
flip = 1-mark*2;
if (mark >= 0) {
marklength = min(widths[i]/2, scales[i]*2);
markwidth = marklength*0.4;
translate([mark*scales[i], widths[i], 0]) {
color(colors[1-$idx%2], alpha=alpha) {
linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
polygon(scale([flip*markwidth, marklength],p=[[0,0], [1, -1], [0,-0.9]]));
}
}
}
}
if (labels && scales[i]/unit+EPSILON >= 1) {
color(colors[($idx+1)%2], alpha=alpha) {
linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
back(scales[i]*.02) {
text(text=str( $idx * scales[i] / unit), size=fontsize, halign="left", valign="baseline");
}
}
}
}
}
}
}
children();
}
}
// Function: mod_indent()
// Usage:
// str = mod_indent([indent]);
// Description:
// Returns a string that is the total indentation for the module level you are at.
// Arguments:
// indent = The string to indent each level by. Default: " " (Two spaces)
// Example:
// x = echo(str(mod_indent(), parent_module(0)));
function mod_indent(indent=" ") =
str_join([for (i=[1:1:$parent_modules-1]) indent]);
// Function: mod_trace()
// Usage:
// str = mod_trace([levs], [indent=], [modsep=]);
// Description:
// Returns a string that shows the current module and its parents, indented for each unprinted parent module.
// Arguments:
// levs = This is the number of levels to print the names of. Prints the N most nested module names. Default: 2
// ---
// indent = The string to indent each level by. Default: " " (Two spaces)
// modsep = Multiple module names will be separated by this string. Default: "->"
// Example:
// x = echo(mod_trace());
function mod_trace(levs=2, indent=" ", modsep="->") =
str(
str_join([for (i=[1:1:$parent_modules+1-levs]) indent]),
str_join([for (i=[min(levs-1,$parent_modules-1):-1:0]) parent_module(i)], modsep)
);
// Function&Module: echo_matrix()
// Usage:
// echo_matrix(M, [description=], [sig=], [eps=]);
// dummy = echo_matrix(M, [description=], [sig=], [eps=]),
// Description:
// Display a numerical matrix in a readable columnar format with `sig` significant
// digits. Values smaller than eps display as zero. If you give a description
// it is displayed at the top.
function echo_matrix(M,description,sig=4,eps=1e-9) =
let(
horiz_line = chr(8213),
matstr = matrix_strings(M,sig=sig,eps=eps),
separator = str_join(repeat(horiz_line,10)),
dummy=echo(str(separator," ",is_def(description) ? description : ""))
[for(row=matstr) echo(row)]
)
echo(separator);
module echo_matrix(M,description,sig=4,eps=1e-9)
{
dummy = echo_matrix(M,description,sig,eps);
}
// Function: random_polygon()
// Usage:
// points = random_polygon(n, size, [seed]);
// See Also: random_points(), gaussian_random_points(), spherical_random_points()
// Topics: Random, Polygon
// Description:
// Generate the `n` vertices of a random counter-clockwise simple 2d polygon
// inside a circle centered at the origin with radius `size`.
// Arguments:
// n = number of vertices of the polygon. Default: 3
// size = the radius of a circle centered at the origin containing the polygon. Default: 1
// seed = an optional seed for the random generation.
function random_polygon(n=3,size=1, seed) =
assert( is_int(n) && n>2, "Improper number of polygon vertices.")
assert( is_num(size) && size>0, "Improper size.")
let(
seed = is_undef(seed) ? rands(0,1,1)[0] : seed,
cumm = cumsum(rands(0.1,10,n+1,seed)),
angs = 360*cumm/cumm[n-1],
rads = rands(.01,size,n,seed+cumm[0])
)
[for(i=count(n)) rads[i]*[cos(angs[i]), sin(angs[i])] ];
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -2160,5 +2160,66 @@ function _support_diff(p1,p2,d) =
p1[search(max(p1d),p1d,1)[0]] - p2[search(min(p2d),p2d,1)[0]];
// Section: Rotation Decoding
// Function: rot_decode()
// Usage:
// info = rot_decode(rotation,[long]); // Returns: [angle,axis,cp,translation]
// Topics: Affine, Matrices, Transforms
// Description:
// Given an input 3D rigid transformation operator (one composed of just rotations and translations) represented
// as a 4x4 matrix, compute the rotation and translation parameters of the operator. Returns a list of the
// four parameters, the angle, in the interval [0,180], the rotation axis as a unit vector, a centerpoint for
// the rotation, and a translation. If you set `parms = rot_decode(rotation)` then the transformation can be
// reconstructed from parms as `move(parms[3]) * rot(a=parms[0],v=parms[1],cp=parms[2])`. This decomposition
// makes it possible to perform interpolation. If you construct a transformation using `rot` the decoding
// may flip the axis (if you gave an angle outside of [0,180]). The returned axis will be a unit vector, and
// the centerpoint lies on the plane through the origin that is perpendicular to the axis. It may be different
// than the centerpoint you used to construct the transformation.
// .
// If you set `long` to true then return the reversed rotation, with the angle in [180,360].
// Arguments:
// rotation = rigid transformation to decode
// long = if true return the "long way" around, with the angle in [180,360]. Default: false
// Example:
// info = rot_decode(rot(45));
// // Returns: [45, [0,0,1], [0,0,0], [0,0,0]]
// Example:
// info = rot_decode(rot(a=37, v=[1,2,3], cp=[4,3,-7])));
// // Returns: [37, [0.26, 0.53, 0.80], [4.8, 4.6, -4.6], [0,0,0]]
// Example:
// info = rot_decode(left(12)*xrot(-33));
// // Returns: [33, [-1,0,0], [0,0,0], [-12,0,0]]
// Example:
// info = rot_decode(translate([3,4,5]));
// // Returns: [0, [0,0,1], [0,0,0], [3,4,5]]
function rot_decode(M,long=false) =
assert(is_matrix(M,4,4) && approx(M[3],[0,0,0,1]), "Input matrix must be a 4x4 matrix representing a 3d transformation")
let(R = submatrix(M,[0:2],[0:2]))
assert(approx(det3(R),1) && approx(norm_fro(R * transpose(R)-ident(3)),0),"Input matrix is not a rotation")
let(
translation = [for(row=[0:2]) M[row][3]], // translation vector
largest = max_index([R[0][0], R[1][1], R[2][2]]),
axis_matrix = R + transpose(R) - (matrix_trace(R)-1)*ident(3), // Each row is on the rotational axis
// Construct quaternion q = c * [x sin(theta/2), y sin(theta/2), z sin(theta/2), cos(theta/2)]
q_im = axis_matrix[largest],
q_re = R[(largest+2)%3][(largest+1)%3] - R[(largest+1)%3][(largest+2)%3],
c_sin = norm(q_im), // c * sin(theta/2) for some c
c_cos = abs(q_re) // c * cos(theta/2)
)
approx(c_sin,0) ? [0,[0,0,1],[0,0,0],translation] :
let(
angle = 2*atan2(c_sin, c_cos), // This is supposed to be more accurate than acos or asin
axis = (q_re>=0 ? 1:-1)*q_im/c_sin,
tproj = translation - (translation*axis)*axis, // Translation perpendicular to axis determines centerpoint
cp = (tproj + cross(axis,tproj)*c_cos/c_sin)/2
)
[long ? 360-angle:angle,
long? -axis : axis,
cp,
(translation*axis)*axis];
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -588,29 +588,31 @@ function spherical_random_points(n, radius=1, seed) =
// Function: log_rands()
// Function: random_polygon()
// Usage:
// num = log_rands(minval, maxval, factor, [N], [seed]);
// points = random_polygon(n, size, [seed]);
// See Also: random_points(), gaussian_random_points(), spherical_random_points()
// Topics: Random, Polygon
// Description:
// Returns a single random number, with a logarithmic distribution.
// Generate the `n` vertices of a random counter-clockwise simple 2d polygon
// inside a circle centered at the origin with radius `size`.
// Arguments:
// minval = Minimum value to return.
// maxval = Maximum value to return. `minval` <= X < `maxval`.
// factor = Log factor to use. Values of X are returned `factor` times more often than X+1.
// N = Number of random numbers to return. Default: 1
// seed = If given, sets the random number seed.
function log_rands(minval, maxval, factor, N=1, seed=undef) =
assert( is_finite(minval+maxval+N)
&& (is_undef(seed) || is_finite(seed) )
&& factor>0,
"Input must be finite numbers. `factor` should be greater than zero.")
assert(maxval >= minval, "maxval cannot be smaller than minval")
let(
minv = 1-1/pow(factor,minval),
maxv = 1-1/pow(factor,maxval),
nums = is_undef(seed)? rands(minv, maxv, N) : rands(minv, maxv, N, seed)
) [for (num=nums) -ln(1-num)/ln(factor)];
// n = number of vertices of the polygon. Default: 3
// size = the radius of a circle centered at the origin containing the polygon. Default: 1
// seed = an optional seed for the random generation.
function random_polygon(n=3,size=1, seed) =
assert( is_int(n) && n>2, "Improper number of polygon vertices.")
assert( is_num(size) && size>0, "Improper size.")
let(
seed = is_undef(seed) ? rands(0,1,1)[0] : seed,
cumm = cumsum(rands(0.1,10,n+1,seed)),
angs = 360*cumm/cumm[n-1],
rads = rands(.01,size,n,seed+cumm[0])
)
[for(i=count(n)) rads[i]*[cos(angs[i]), sin(angs[i])] ];
@ -896,6 +898,38 @@ function convolve(p,q) =
// Section: Matrix math
// Function: ident()
// Usage:
// mat = ident(n);
// Topics: Affine, Matrices
// Description:
// Create an `n` by `n` square identity matrix.
// Arguments:
// n = The size of the identity matrix square, `n` by `n`.
// Example:
// mat = ident(3);
// // Returns:
// // [
// // [1, 0, 0],
// // [0, 1, 0],
// // [0, 0, 1]
// // ]
// Example:
// mat = ident(4);
// // Returns:
// // [
// // [1, 0, 0, 0],
// // [0, 1, 0, 0],
// // [0, 0, 1, 0],
// // [0, 0, 0, 1]
// // ]
function ident(n) = [
for (i = [0:1:n-1]) [
for (j = [0:1:n-1]) (i==j)? 1 : 0
]
];
// Function: linear_solve()
// Usage:
// solv = linear_solve(A,b)

View file

@ -2457,4 +2457,100 @@ function heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04
) reorient(anchor,spin,orient, vnf=vnf, p=vnf);
// Module: ruler()
// Usage:
// ruler(length, width, [thickness=], [depth=], [labels=], [pipscale=], [maxscale=], [colors=], [alpha=], [unit=], [inch=]);
// Description:
// Creates a ruler for checking dimensions of the model
// Arguments:
// length = length of the ruler. Default 100
// width = width of the ruler. Default: size of the largest unit division
// ---
// thickness = thickness of the ruler. Default: 1
// depth = the depth of mark subdivisions. Default: 3
// labels = draw numeric labels for depths where labels are larger than 1. Default: false
// pipscale = width scale of the pips relative to the next size up. Default: 1/3
// maxscale = log10 of the maximum width divisions to display. Default: based on input length
// colors = colors to use for the ruler, a list of two values. Default: `["black","white"]`
// alpha = transparency value. Default: 1.0
// unit = unit to mark. Scales the ruler marks to a different length. Default: 1
// inch = set to true for a ruler scaled to inches (assuming base dimension is mm). Default: false
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `LEFT+BACK+TOP`
// 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`
// Examples(2D,Big):
// ruler(100,depth=3);
// ruler(100,depth=3,labels=true);
// ruler(27);
// ruler(27,maxscale=0);
// ruler(100,pipscale=3/4,depth=2);
// ruler(100,width=2,depth=2);
// Example(2D,Big): Metric vs Imperial
// ruler(12,width=50,inch=true,labels=true,maxscale=0);
// fwd(50)ruler(300,width=50,labels=true);
module ruler(length=100, width, thickness=1, depth=3, labels=false, pipscale=1/3, maxscale,
colors=["black","white"], alpha=1.0, unit=1, inch=false, anchor=LEFT+BACK+TOP, spin=0, orient=UP)
{
inchfactor = 25.4;
assert(depth<=5, "Cannot render scales smaller than depth=5");
assert(len(colors)==2, "colors must contain a list of exactly two colors.");
length = inch ? inchfactor * length : length;
unit = inch ? inchfactor*unit : unit;
maxscale = is_def(maxscale)? maxscale : floor(log(length/unit-EPSILON));
scales = unit * [for(logsize = [maxscale:-1:maxscale-depth+1]) pow(10,logsize)];
widthfactor = (1-pipscale) / (1-pow(pipscale,depth));
width = default(width, scales[0]);
widths = width * widthfactor * [for(logsize = [0:-1:-depth+1]) pow(pipscale,-logsize)];
offsets = concat([0],cumsum(widths));
attachable(anchor,spin,orient, size=[length,width,thickness]) {
translate([-length/2, -width/2, 0])
for(i=[0:1:len(scales)-1]) {
count = ceil(length/scales[i]);
fontsize = 0.5*min(widths[i], scales[i]/ceil(log(count*scales[i]/unit)));
back(offsets[i]) {
xcopies(scales[i], n=count, sp=[0,0,0]) union() {
actlen = ($idx<count-1) || approx(length%scales[i],0) ? scales[i] : length % scales[i];
color(colors[$idx%2], alpha=alpha) {
w = i>0 ? quantup(widths[i],1/1024) : widths[i]; // What is the i>0 test supposed to do here?
cube([quantup(actlen,1/1024),quantup(w,1/1024),thickness], anchor=FRONT+LEFT);
}
mark =
i == 0 && $idx % 10 == 0 && $idx != 0 ? 0 :
i == 0 && $idx % 10 == 9 && $idx != count-1 ? 1 :
$idx % 10 == 4 ? 1 :
$idx % 10 == 5 ? 0 : -1;
flip = 1-mark*2;
if (mark >= 0) {
marklength = min(widths[i]/2, scales[i]*2);
markwidth = marklength*0.4;
translate([mark*scales[i], widths[i], 0]) {
color(colors[1-$idx%2], alpha=alpha) {
linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
polygon(scale([flip*markwidth, marklength],p=[[0,0], [1, -1], [0,-0.9]]));
}
}
}
}
if (labels && scales[i]/unit+EPSILON >= 1) {
color(colors[($idx+1)%2], alpha=alpha) {
linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
back(scales[i]*.02) {
text(text=str( $idx * scales[i] / unit), size=fontsize, halign="left", valign="baseline");
}
}
}
}
}
}
}
children();
}
}
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

214
skin.scad
View file

@ -1188,6 +1188,220 @@ function slice_profiles(profiles,slices,closed=false) =
concat(slicelist, closed?[]:[profiles[len(profiles)-1]]);
function _closest_angle(alpha,beta) =
is_vector(beta) ? [for(entry=beta) _closest_angle(alpha,entry)]
: beta-alpha > 180 ? beta - ceil((beta-alpha-180)/360) * 360
: beta-alpha < -180 ? beta + ceil((alpha-beta-180)/360) * 360
: beta;
// Smooth data with N point moving average. If angle=true handles data as angles.
// If closed=true assumes last point is adjacent to the first one.
// If closed=false pads data with left/right value (probably wrong behavior...should do linear interp)
function _smooth(data,len,closed=false,angle=false) =
let( halfwidth = floor(len/2),
result = closed ? [for(i=idx(data))
let(
window = angle ? _closest_angle(data[i],select(data,i-halfwidth,i+halfwidth))
: select(data,i-halfwidth,i+halfwidth)
)
mean(window)]
: [for(i=idx(data))
let(
window = select(data,max(i-halfwidth,0),min(i+halfwidth,len(data)-1)),
left = i-halfwidth<0,
pad = left ? data[0] : last(data)
)
sum(window)+pad*(len-len(window))] / len
)
result;
// Function: rot_resample()
// Usage:
// rlist = rot_resample(rotlist, N, [method], [twist], [scale], [smoothlen], [long], [turns], [closed])
// Description:
// Takes as input a list of rotation matrices in 3d. Produces as output a resampled
// list of rotation operators (4x4 matrixes) suitable for use with sweep(). You can optionally apply twist to
// the output with the twist parameter, which is either a scalar to apply a uniform
// overall twist, or a vector to apply twist non-uniformly. Similarly you can apply
// scaling either overall or with a vector. The smoothlen parameter applies smoothing
// to the twist and scaling to prevent abrupt changes. This is done by a moving average
// of the smoothing or scaling values. The default of 1 means no smoothing. The long parameter causes
// the interpolation to be done the "long" way around the rotation instead of the short way.
// Note that the rotation matrix cannot distinguish which way you rotate, only the place you
// end after rotation. Another ambiguity arises if your rotation is more than 360 degrees.
// You can add turns with the turns parameter, so giving turns=1 will add 360 degrees to the
// rotation so it completes one full turn plus the additional rotation given my the transform.
// You can give long as a scalar or as a vector. Finally if closed is true then the
// resampling will connect back to the beginning.
// .
// The default is to resample based on the length of the arc defined by each rotation operator. This produces
// uniform sampling over all of the transformations. It requires that each rotation has nonzero length.
// In this case N specifies the total number of samples. If you set method to "count" then N you get
// N samples for each transform. You can set N to a vector to vary the samples at each step.
// Arguments:
// rotlist = list of rotation operators in 3d to resample
// N = Number of rotations to produce as output when method is "length" or number for each transformation if method is "count". Can be a vector when method is "count"
// --
// method = sampling method, either "length" or "count"
// twist = scalar or vector giving twist to add overall or at each rotation. Default: none
// scale = scalar or vector giving scale factor to add overall or at each rotation. Default: none
// smoothlen = amount of smoothing to apply to scaling and twist. Should be an odd integer. Default: 1
// long = resample the "long way" around the rotation, a boolean or list of booleans. Default: false
// turns = add extra turns. If a scalar adds the turns to every rotation, or give a vector. Default: 0
// closed = if true then the rotation list is treated as closed. Default: false
// Example: Resampling the arc from a compound rotation with translations thrown in.
// tran = rot_resample([ident(4), back(5)*up(4)*xrot(-10)*zrot(-20)*yrot(117,cp=[10,0,0])], N=25);
// sweep(circle(r=1,$fn=3), tran);
// Example: Applying a scale factor
// tran = rot_resample([ident(4), back(5)*up(4)*xrot(-10)*zrot(-20)*yrot(117,cp=[10,0,0])], N=25, scale=2);
// sweep(circle(r=1,$fn=3), tran);
// Example: Applying twist
// tran = rot_resample([ident(4), back(5)*up(4)*xrot(-10)*zrot(-20)*yrot(117,cp=[10,0,0])], N=25, twist=60);
// sweep(circle(r=1,$fn=3), tran);
// Example: Going the long way
// tran = rot_resample([ident(4), back(5)*up(4)*xrot(-10)*zrot(-20)*yrot(117,cp=[10,0,0])], N=25, long=true);
// sweep(circle(r=1,$fn=3), tran);
// Example: Getting transformations from turtle3d
// include<BOSL2/turtle3d.scad>
// tran=turtle3d(["arcsteps",1,"up", 10, "arczrot", 10,170],transforms=true);
// sweep(circle(r=1,$fn=3),rot_resample(tran, N=40));
// Example: If you specify a larger angle in turtle you need to use the long argument
// include<BOSL2/turtle3d.scad>
// tran=turtle3d(["arcsteps",1,"up", 10, "arczrot", 10,270],transforms=true);
// sweep(circle(r=1,$fn=3),rot_resample(tran, N=40,long=true));
// Example: And if the angle is over 360 you need to add turns to get the right result. Note long is false when the remaining angle after subtracting full turns is below 180:
// include<BOSL2/turtle3d.scad>
// tran=turtle3d(["arcsteps",1,"up", 10, "arczrot", 10,90+360],transforms=true);
// sweep(circle(r=1,$fn=3),rot_resample(tran, N=40,long=false,turns=1));
// Example: Here the remaining angle is 270, so long must be set to true
// include<BOSL2/turtle3d.scad>
// tran=turtle3d(["arcsteps",1,"up", 10, "arczrot", 10,270+360],transforms=true);
// sweep(circle(r=1,$fn=3),rot_resample(tran, N=40,long=true,turns=1));
// Example: Note the visible line at the scale transition
// include<BOSL2/turtle3d.scad>
// tran = turtle3d(["arcsteps",1,"arcup", 10, 90, "arcdown", 10, 90], transforms=true);
// rtran = rot_resample(tran,200,scale=[1,6]);
// sweep(circle(1,$fn=32),rtran);
// Example: Observe how using a large smoothlen value eases that transition
// include<BOSL2/turtle3d.scad>
// tran = turtle3d(["arcsteps",1,"arcup", 10, 90, "arcdown", 10, 90], transforms=true);
// rtran = rot_resample(tran,200,scale=[1,6],smoothlen=17);
// sweep(circle(1,$fn=32),rtran);
// Example: A similar issues can arise with twist, where a "line" is visible at the transition
// include<BOSL2/turtle3d.scad>
// tran = turtle3d(["arcsteps", 1, "arcup", 10, 90, "move", 10], transforms=true,state=[1,-.5,0]);
// rtran = rot_resample(tran,100,twist=[0,60],smoothlen=1);
// sweep(subdivide_path(rect([3,3]),40),rtran);
// Example: Here's the smoothed twist transition
// include<BOSL2/turtle3d.scad>
// tran = turtle3d(["arcsteps", 1, "arcup", 10, 90, "move", 10], transforms=true,state=[1,-.5,0]);
// rtran = rot_resample(tran,100,twist=[0,60],smoothlen=17);
// sweep(subdivide_path(rect([3,3]),40),rtran);
// Example: toothed belt based on list-comprehension-demos example. This version has a smoothed twist transition. Try changing smoothlen to 1 to see the more abrupt transition that occurs without smoothing.
// include<BOSL2/turtle3d.scad>
// r_small = 19; // radius of small curve
// r_large = 46; // radius of large curve
// flat_length = 100; // length of flat belt section
// teeth=42; // number of teeth
// belt_width = 12;
// tooth_height = 9;
// belt_thickness = 3;
// angle = 180 - 2*atan((r_large-r_small)/flat_length);
// beltprofile = path3d(subdivide_path(
// square([belt_width, belt_thickness],anchor=FWD),
// 20));
// beltrots =
// turtle3d(["arcsteps",1,
// "move", flat_length,
// "arcleft", r_small, angle,
// "move", flat_length,
// // Closing path will be interpolated
// // "arcleft", r_large, 360-angle
// ],transforms=true);
// beltpath = rot_resample(beltrots,teeth*4,
// twist=[180,0,-180,0],
// long=[false,false,false,true],
// smoothlen=15,closed=true);
// belt = [for(i=idx(beltpath))
// let(tooth = floor((i+$t*4)/2)%2)
// apply(beltpath[i]*
// yscale(tooth
// ? tooth_height/belt_thickness
// : 1),
// beltprofile)
// ];
// skin(belt,slices=0,closed=true);
function rot_resample(rotlist,N,twist,scale,smoothlen=1,long=false,turns=0,closed=false,method="length") =
assert(is_int(smoothlen) && smoothlen>0 && smoothlen%2==1, "smoothlen must be a positive odd integer")
assert(method=="length" || method=="count")
let(tcount = len(rotlist) + (closed?0:-1))
assert(method=="count" || is_int(N), "N must be an integer when method is \"length\"")
assert(is_int(N) || is_vector(N,tcount), str("N must be scalar or vector with length ",tcount))
let(
count = method=="length" ? (closed ? N+1 : N)
: (is_vector(N) ? sum(N) : tcount*N)+1 //(closed?0:1)
)
assert(is_bool(long) || len(long)==tcount,str("Input long must be a scalar or have length ",tcount))
let(
long = force_list(long,tcount),
turns = force_list(turns,tcount),
T = [for(i=[0:1:tcount-1]) rot_inverse(rotlist[i])*select(rotlist,i+1)],
parms = [for(i=idx(T))
let(tparm = rot_decode(T[i],long[i]))
[tparm[0]+turns[i]*360,tparm[1],tparm[2],tparm[3]]
],
radius = [for(i=idx(parms)) norm(parms[i][2])],
length = [for(i=idx(parms)) norm([norm(parms[i][3]), parms[i][0]/360*2*PI*radius[i]])]
)
assert(method=="count" || all_positive(length),
"Rotation list includes a repeated entry or a rotation around the origin, not allowed when method=\"length\"")
let(
cumlen = [0, each cumsum(length)],
totlen = last(cumlen),
stepsize = totlen/(count-1),
samples = method=="count"
? let( N = force_list(N,tcount))
[for(n=N) lerpn(0,1,n,endpoint=false)]
:[for(i=idx(parms))
let(
remainder = cumlen[i] % stepsize,
offset = remainder==0 ? 0
: stepsize-remainder,
num = ceil((length[i]-offset)/stepsize)
)
count(num,offset,stepsize)/length[i]],
twist = first_defined([twist,0]),
scale = first_defined([scale,1]),
needlast = !approx(last(last(samples)),1),
sampletwist = is_num(twist) ? lerpn(0,twist,count)
: let(
cumtwist = [0,each cumsum(twist)]
)
[for(i=idx(parms)) each lerp(cumtwist[i],cumtwist[i+1],samples[i]),
if (needlast) last(cumtwist)
],
samplescale = is_num(scale) ? lerp(1,scale,lerpn(0,1,count))
: let(
cumscale = [1,each cumprod(scale)]
)
[for(i=idx(parms)) each lerp(cumscale[i],cumscale[i+1],samples[i]),
if (needlast) last(cumscale)],
smoothtwist = _smooth(closed?select(sampletwist,0,-2):sampletwist,smoothlen,closed=closed,angle=true),
smoothscale = _smooth(samplescale,smoothlen,closed=closed),
interpolated = [
for(i=idx(parms))
each [for(u=samples[i]) rotlist[i] * move(u*parms[i][3]) * rot(a=u*parms[i][0],v=parms[i][1],cp=parms[i][2])],
if (needlast) last(rotlist)
]
)
[for(i=idx(interpolated,e=closed?-2:-1)) interpolated[i]*zrot(smoothtwist[i])*scale(smoothscale[i])];
//////////////////////////////////////////////////////////////////
//
// Minimum Distance Mapping using Dynamic Programming

View file

@ -34,7 +34,6 @@ include <strings.scad>
include <skin.scad>
include <vnf.scad>
include <utility.scad>
include <debug.scad>
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -31,25 +31,6 @@ module test_is_2d_transform() {
test_is_2d_transform();
module test_is_affine() {
assert(is_affine(affine2d_scale([2,3])));
assert(is_affine(affine3d_scale([2,3,4])));
assert(!is_affine(affine3d_scale([2,3,4]),2));
assert(is_affine(affine2d_scale([2,3]),2));
assert(is_affine(affine3d_scale([2,3,4]),3));
assert(!is_affine(affine2d_scale([2,3]),3));
}
test_is_affine();
module test_affine2d_to_3d() {
assert(affine2d_to_3d(affine2d_identity()) == affine3d_identity());
assert(affine2d_to_3d(affine2d_translate([30,40])) == affine3d_translate([30,40,0]));
assert(affine2d_to_3d(affine2d_scale([3,4])) == affine3d_scale([3,4,1]));
assert(affine2d_to_3d(affine2d_zrot(30)) == affine3d_zrot(30));
}
test_affine2d_to_3d();
// 2D

View file

@ -1,11 +0,0 @@
include <../std.scad>
module test_standard_anchors() {
assert_equal(standard_anchors(), [[-1,-1,1],[0,-1,1],[1,-1,1],[-1,0,1],[0,0,1],[1,0,1],[-1,1,1],[0,1,1],[1,1,1],[-1,-1,0],[0,-1,0],[1,-1,0],[-1,0,0],[0,0,0],[1,0,0],[-1,1,0],[0,1,0],[1,1,0],[-1,-1,-1],[0,-1,-1],[1,-1,-1],[-1,0,-1],[0,0,-1],[1,0,-1],[-1,1,-1],[0,1,-1],[1,1,-1]]);
}
test_standard_anchors();
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -372,18 +372,6 @@ module test_gaussian_rands() {
test_gaussian_rands();
module test_log_rands() {
nums1 = log_rands(0,100,10,1000,seed=2189);
nums2 = log_rands(0,100,10,1000,seed=2310);
nums3 = log_rands(0,100,10,1000,seed=2189);
assert_equal(len(nums1), 1000);
assert_equal(len(nums2), 1000);
assert_equal(len(nums3), 1000);
assert_equal(nums1, nums3);
assert(nums1!=nums2);
}
test_log_rands();
module test_segs() {
assert_equal(segs(50,$fn=8), 8);

View file

@ -1260,4 +1260,84 @@ function skew(p, sxy=0, sxz=0, syx=0, syz=0, szx=0, szy=0, planar=false) =
[for (l=p) skew(sxy=sxy, sxz=sxz, syx=syx, syz=syz, szx=szx, szy=szy, planar=planar, p=l)];
// Section: Applying transformation matrices to
/// Internal Function: is_2d_transform()
/// Usage:
/// x = is_2d_transform(t);
/// Topics: Affine, Matrices, Transforms, Type Checking
/// See Also: is_affine(), is_matrix()
/// 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.
/// Arguments:
/// t = The transformation matrix to check.
/// Example:
/// b = is_2d_transform(zrot(45)); // Returns: true
/// b = is_2d_transform(yrot(45)); // Returns: false
/// b = is_2d_transform(xrot(45)); // Returns: false
/// b = is_2d_transform(move([10,20,0])); // Returns: true
/// b = is_2d_transform(move([10,20,30])); // Returns: false
/// b = is_2d_transform(scale([2,3,4])); // Returns: true
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][2]==1 || !(t[0][0]==1 && t[0][1]==0 && t[1][0]==0 && t[1][1]==1)); // But rule out zscale()
// Function: apply()
// Usage:
// pts = apply(transform, points);
// Topics: Affine, Matrices, Transforms
// Description:
// Applies the specified transformation matrix to a point, pointlist, bezier patch or VNF.
// Both inputs can be 2D or 3D, and it is also allowed to supply 3D transformations with 2D
// data as long as the the only action on the z coordinate is a simple scaling.
// .
// If you construct your own matrices you can also use a transform that acts like a projection
// with fewer rows to produce lower dimensional output.
// Arguments:
// transform = The 2D or 3D transformation matrix to apply to the point/points.
// points = The point, pointlist, bezier patch, or VNF to apply the transformation to.
// Example(3D):
// path1 = path3d(circle(r=40));
// tmat = xrot(45);
// path2 = apply(tmat, path1);
// #stroke(path1,closed=true);
// stroke(path2,closed=true);
// Example(2D):
// path1 = circle(r=40);
// tmat = translate([10,5]);
// path2 = apply(tmat, path1);
// #stroke(path1,closed=true);
// stroke(path2,closed=true);
// Example(2D):
// path1 = circle(r=40);
// tmat = rot(30) * back(15) * scale([1.5,0.5,1]);
// path2 = apply(tmat, path1);
// #stroke(path1,closed=true);
// stroke(path2,closed=true);
function apply(transform,points) =
points==[] ? [] :
is_vector(points)
? /* Point */ apply(transform, [points])[0] :
is_list(points) && len(points)==2 && is_path(points[0],3) && is_list(points[1]) && is_vector(points[1][0])
? /* VNF */ [apply(transform, points[0]), points[1]] :
is_list(points) && is_list(points[0]) && is_vector(points[0][0])
? /* BezPatch */ [for (x=points) apply(transform,x)] :
let(
tdim = len(transform[0])-1,
datadim = len(points[0]),
outdim = min(datadim,len(transform)),
matrix = [for(i=[0:1:tdim]) [for(j=[0:1:outdim-1]) transform[j][i]]]
)
tdim==datadim && (datadim==3 || datadim==2) ? [for(p=points) concat(p,1)] * matrix
: tdim == 3 && datadim == 2 ?
assert(is_2d_transform(transform), str("Transforms is 3d but points are 2d"))
[for(p=points) concat(p,[0,1])]*matrix
: assert(false, str("Unsupported combination: transform with dimension ",tdim,", data of dimension ",datadim));
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap