From f1b9d04a3d33ff0c75f52f9cdfec6e1bdd02e1cc Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Tue, 28 Dec 2021 17:05:37 -0500 Subject: [PATCH 1/5] strip down apply() so projections are banned, and 2d acting on 3d is banned. clarify docs --- drawing.scad | 8 +-- linalg.scad | 5 +- shapes2d.scad | 32 ++++----- tests/test_transforms.scad | 2 +- transforms.scad | 141 ++++++++++++++++++++++++------------- 5 files changed, 117 insertions(+), 71 deletions(-) diff --git a/drawing.scad b/drawing.scad index 72f27cc..da464ae 100644 --- a/drawing.scad +++ b/drawing.scad @@ -988,8 +988,8 @@ function _turtle_command(command, parm, parm2, state, index) = command=="jump" ? list_set(state, path, concat(state[path],[parm])): command=="xjump" ? list_set(state, path, concat(state[path],[[parm,lastpt.y]])): command=="yjump" ? list_set(state, path, concat(state[path],[[lastpt.x,parm]])): - command=="turn" || command=="left" ? list_set(state, step, rot(default(parm,state[angle]),p=state[step],planar=true)) : - command=="right" ? list_set(state, step, rot(-default(parm,state[angle]),p=state[step],planar=true)) : + command=="turn" || command=="left" ? list_set(state, step, rot(default(parm,state[angle]),p=state[step])) : + command=="right" ? list_set(state, step, rot(-default(parm,state[angle]),p=state[step])) : command=="angle" ? list_set(state, angle, parm) : command=="setdir" ? ( is_vector(parm) ? @@ -1020,7 +1020,7 @@ function _turtle_command(command, parm, parm2, state, index) = list_set( state, [path,step], [ concat(state[path], list_tail(arcpath)), - rot(lrsign * myangle,p=state[step],planar=true) + rot(lrsign * myangle,p=state[step]) ] ) : command=="arcleftto" || command=="arcrightto" ? @@ -1046,7 +1046,7 @@ function _turtle_command(command, parm, parm2, state, index) = list_set( state, [path,step], [ concat(state[path], list_tail(arcpath)), - rot(delta_angle,p=state[step],planar=true) + rot(delta_angle,p=state[step]) ] ) : assert(false,str("Unknown turtle command \"",command,"\" at index",index)) diff --git a/linalg.scad b/linalg.scad index 9303fad..ca89e3b 100644 --- a/linalg.scad +++ b/linalg.scad @@ -94,11 +94,11 @@ module echo_matrix(M,description,sig=4,eps=1e-9) // Topics: Matrices, List Handling // See Also: select(), slice() // Description: -// Extracts entry i from each list in M, or equivalently column i from the matrix M, and returns it as a vector. +// Extracts entry `i` from each list in M, or equivalently column i from the matrix M, and returns it as a vector. // This function will return `undef` at all entry positions indexed by i not found in M. // Arguments: // M = The given list of lists. -// idx = The index, list of indices, or range of indices to fetch. +// i = The index to fetch // Example: // M = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]; // a = column(M,2); // Returns [3, 7, 11, 15] @@ -114,7 +114,6 @@ function column(M, i) = [for(row=M) row[i]]; - // Function: submatrix() // Usage: // mat = submatrix(M, idx1, idx2); diff --git a/shapes2d.scad b/shapes2d.scad index cbfd98b..23c5879 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -459,10 +459,10 @@ function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false let( inset = opp_ang_to_hyp(rounding, (180-360/n)/2), mat = !is_undef(_mat) ? _mat : - ( realign? rot(-180/n, planar=true) : affine2d_identity() ) * ( - !is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip), planar=true) : - !is_undef(align_side)? rot(from=RIGHT, to=point2d(align_side), planar=true) * rot(180/n, planar=true) : - affine2d_identity() + ( realign? zrot(-180/n) : ident(4)) * ( + !is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip)) : + !is_undef(align_side)? rot(from=RIGHT, to=point2d(align_side)) * zrot(180/n) : + 1 ), path4 = rounding==0? ellipse(r=r, $fn=n) : ( let( @@ -504,10 +504,10 @@ module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, side = is_finite(side)? side/2/sin(180/n) : undef; r = get_radius(r1=ir, r2=or, r=r, d1=id, d2=od, d=d, dflt=side); assert(!is_undef(r), "regular_ngon(): need to specify one of r, d, or, od, ir, id, side."); - mat = ( realign? rot(-180/n, planar=true) : affine2d_identity() ) * ( - !is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip), planar=true) : - !is_undef(align_side)? rot(from=RIGHT, to=point2d(align_side), planar=true) * rot(180/n, planar=true) : - affine2d_identity() + mat = ( realign? zrot(-180/n) : ident(4) ) * ( + !is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip)) : + !is_undef(align_side)? rot(from=RIGHT, to=point2d(align_side)) * zrot(180/n) : + 1 ); inset = opp_ang_to_hyp(rounding, (180-360/n)/2); anchors = [ @@ -930,10 +930,10 @@ function star(n, r, ir, d, or, od, id, step, realign=false, align_tip, align_pit : str("Parameter 'step' must be between 2 and ",floor(n/2-1/2)," for ",n," point stars")) let( mat = !is_undef(_mat) ? _mat : - ( realign? rot(-180/n, planar=true) : affine2d_identity() ) * ( - !is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip), planar=true) : - !is_undef(align_pit)? rot(from=RIGHT, to=point2d(align_pit), planar=true) * rot(180/n, planar=true) : - affine2d_identity() + ( realign? zrot(-180/n) : ident(4) ) * ( + !is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip)) : + !is_undef(align_pit)? rot(from=RIGHT, to=point2d(align_pit)) * zrot(180/n) : + 1 ), stepr = is_undef(step)? r : r*cos(180*step/n)/cos(180*(step-1)/n), ir = get_radius(r=ir, d=id, dflt=stepr), @@ -967,10 +967,10 @@ module star(n, r, ir, d, or, od, id, step, realign=false, align_tip, align_pit, r = get_radius(r1=or, d1=od, r=r, d=d, dflt=undef); stepr = is_undef(step)? r : r*cos(180*step/n)/cos(180*(step-1)/n); ir = get_radius(r=ir, d=id, dflt=stepr); - mat = ( realign? rot(-180/n, planar=true) : affine2d_identity() ) * ( - !is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip), planar=true) : - !is_undef(align_pit)? rot(from=RIGHT, to=point2d(align_pit), planar=true) * rot(180/n, planar=true) : - affine2d_identity() + mat = ( realign? zrot(-180/n) : ident(4) ) * ( + !is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip)) : + !is_undef(align_pit)? rot(from=RIGHT, to=point2d(align_pit)) * zrot(180/n) : + 1 ); anchors = [ for (i = [0:1:n-1]) let( diff --git a/tests/test_transforms.scad b/tests/test_transforms.scad index c9016d4..89d5b51 100644 --- a/tests/test_transforms.scad +++ b/tests/test_transforms.scad @@ -109,7 +109,7 @@ module test_scale() { cb = cube(1); vals = [[-1,-2,-3],[1,1,1],[3,6,2],[1,2,3],[243,75,147]]; for (val=vals) { - assert_equal(scale(point2d(val)), [[val.x,0,0],[0,val.y,0],[0,0,1]]); + assert_equal(scale(point2d(val)), [[val.x,0,0,0],[0,val.y,0,0],[0,0,1,0],[0,0,0,1]]); assert_equal(scale(val), [[val.x,0,0,0],[0,val.y,0,0],[0,0,val.z,0],[0,0,0,1]]); assert_equal(scale(val, p=[1,2,3]), v_mul([1,2,3], val)); scale(val) union(){}; diff --git a/transforms.scad b/transforms.scad index b9d9380..00134d8 100644 --- a/transforms.scad +++ b/transforms.scad @@ -20,15 +20,57 @@ // FileFootnotes: STD=Included in std.scad ////////////////////////////////////////////////////////////////////// +// Section: Affine Transformations +// OpenSCAD provides various built-in modules to transform geometry by +// translation, scaling, rotation, and mirroring. All of these operations +// are affine transformations. A three-dimensional affine transformation +// can be represented by a 4x4 matrix. The transformation shortcuts in this +// file generally have three modes of operation. They can operate +// directly on geometry like their OpenSCAD built-in equivalents. For example, +// `left(10) cube()`. They can operate on a list of points (or various other +// types of geometric data). For example, operating on a list of points: `points = left(10, [[1,2,3],[4,5,6]])`. +// The third option is that the shortcut can return the transformation matrix +// corresponding to its action. For example, `M=left(10)`. +// . +// This capability allows you to store and manipulate transformations, and can +// be useful in more advanced modeling. You can multiply these matrices +// together, analogously to applying a sequence of operations with the +// built-in transformations. So you can write `zrot(37)left(5)cube()` +// to perform two operations on a cube. You can also store +// that same transformation by multiplying the matrices together: `M = zrot(37) * left(5)`. +// Note that the order is exactly the same as the order used to apply the transformation. +// . +// Suppose you have constructed `M` as above. What now? You can use +// the OpensCAD built-in `multmatrix` to apply it to some geometry: `multmatrix(M) cube()`. +// Alternative you can use the BOSL2 function `apply` to apply `M` to a point, a list +// of points, a bezier patch, or a VNF. For example, `points = apply(M, [[3,4,5],[5,6,7]])`. +// Note that the `apply` function can work on both 2D and 3D data, but if you want to +// operate on 2D data, you must choose transformations that don't modify z +// . +// You can use matrices as described above without understanding the details, just +// treating a matrix as a box that stores a transformation. The OpenSCAD manual section for multmatrix +// gives some details of how this works. We'll elaborate a bit more below. An affine transformation +// matrix for three dimensional data is a 4x4 matrix. The top left 3x3 portion gives the linear +// transformation to apply to the data. For example, it could be a rotation or scaling, or combination of both. +// The 3x1 column at the top right gives the translation to apply. The bottom row should be `[0,0,0,1]`. That +// bottom row is only present to enable +// the matrices to be multiplied together. OpenSCAD ignores it and in fact `multmatrix` will +// accept a 3x4 matrix, where that row is missing. In order for a matrix to act on a point you have to +// augment the point with an extra 1, making it a length 4 vector. In OpenSCAD you can then compute the +// the affine transformed point as `tran_point = M * point`. However, this syntax hides a complication that +// arises if you have a list of points. A list of points like `[[1,2,3,1],[4,5,6,1],[7,8,9,1]]` has the augmented points +// as row vectors on the list. In order to transform such a list, it needs to be muliplied on the right +// side, not the left side. -////////////////////////////////////////////////////////////////////// -// Section: Translations -////////////////////////////////////////////////////////////////////// _NO_ARG = [true,[123232345],false]; +////////////////////////////////////////////////////////////////////// +// Section: Translations +////////////////////////////////////////////////////////////////////// + // Function&Module: move() // Aliases: translate() // @@ -98,13 +140,14 @@ _NO_ARG = [true,[123232345],false]; // mat3d = move([2,3,4]); // Returns: [[1,0,0,2],[0,1,0,3],[0,0,1,4],[0,0,0,1]] module move(v=[0,0,0], p, x=0, y=0, z=0) { assert(is_undef(p), "Module form `move()` does not accept p= argument."); + assert(is_vector(v) && (len(v)==3 || len(v)==2), "Invalid value for `v`") translate(point3d(v)+[x,y,z]) children(); } function move(v=[0,0,0], p=_NO_ARG, x=0, y=0, z=0) = + assert(is_vector(v) && (len(v)==3 || len(v)==2), "Invalid value for `v`") let( - m = len(v)==2? affine2d_translate(v+[x,y]) : - affine3d_translate(point3d(v)+[x,y,z]) + m = affine3d_translate(point3d(v)+[x,y,z]) ) p==_NO_ARG ? m : apply(m, p); @@ -143,10 +186,12 @@ function translate(v=[0,0,0], p=_NO_ARG) = move(v=v, p=p); // mat3d = left(4); // Returns: [[1,0,0,-4],[0,1,0,0],[0,0,1,0],[0,0,0,1]] module left(x=0, p) { assert(is_undef(p), "Module form `left()` does not accept p= argument."); + assert(is_finite(x), "Invalid number") translate([-x,0,0]) children(); } -function left(x=0, p=_NO_ARG) = move([-x,0,0],p=p); +function left(x=0, p=_NO_ARG) = assert(is_finite(x), "Invalid number") + move([-x,0,0],p=p); // Function&Module: right() @@ -181,10 +226,12 @@ function left(x=0, p=_NO_ARG) = move([-x,0,0],p=p); // mat3d = right(4); // Returns: [[1,0,0,4],[0,1,0,0],[0,0,1,0],[0,0,0,1]] module right(x=0, p) { assert(is_undef(p), "Module form `right()` does not accept p= argument."); + assert(is_finite(x), "Invalid number") translate([x,0,0]) children(); } -function right(x=0, p=_NO_ARG) = move([x,0,0],p=p); +function right(x=0, p=_NO_ARG) = assert(is_finite(x), "Invalid number") + move([x,0,0],p=p); // Function&Module: fwd() @@ -219,10 +266,12 @@ function right(x=0, p=_NO_ARG) = move([x,0,0],p=p); // mat3d = fwd(4); // Returns: [[1,0,0,0],[0,1,0,-4],[0,0,1,0],[0,0,0,1]] module fwd(y=0, p) { assert(is_undef(p), "Module form `fwd()` does not accept p= argument."); + assert(is_finite(y), "Invalid number") translate([0,-y,0]) children(); } -function fwd(y=0, p=_NO_ARG) = move([0,-y,0],p=p); +function fwd(y=0, p=_NO_ARG) = assert(is_finite(y), "Invalid number") + move([0,-y,0],p=p); // Function&Module: back() @@ -257,10 +306,12 @@ function fwd(y=0, p=_NO_ARG) = move([0,-y,0],p=p); // mat3d = back(4); // Returns: [[1,0,0,0],[0,1,0,4],[0,0,1,0],[0,0,0,1]] module back(y=0, p) { assert(is_undef(p), "Module form `back()` does not accept p= argument."); + assert(is_finite(y), "Invalid number") translate([0,y,0]) children(); } -function back(y=0,p=_NO_ARG) = move([0,y,0],p=p); +function back(y=0,p=_NO_ARG) = assert(is_finite(y), "Invalid number") + move([0,y,0],p=p); // Function&Module: down() @@ -331,10 +382,12 @@ function down(z=0, p=_NO_ARG) = move([0,0,-z],p=p); // mat3d = up(4); // Returns: [[1,0,0,0],[0,1,0,0],[0,0,1,4],[0,0,0,1]] module up(z=0, p) { assert(is_undef(p), "Module form `up()` does not accept p= argument."); + assert(is_finite(z), "Invalid number"); translate([0,0,z]) children(); } -function up(z=0, p=_NO_ARG) = move([0,0,z],p=p); +function up(z=0, p=_NO_ARG) = assert(is_finite(z), "Invalid number") + move([0,0,z],p=p); @@ -447,7 +500,10 @@ function rot(a=0, v, cp, from, to, reverse=false, planar=false, p=_NO_ARG, _m) = cp = is_undef(cp)? undef : point3d(cp), m1 = !is_undef(from)? ( assert(is_num(a)) - affine3d_rot_from_to(from,to) * affine3d_rot_by_axis(from,a) + (from.z == 0 && to.z == 0 + ? affine3d_zrot(v_theta(point2d(to)) - v_theta(point2d(from))) + : affine3d_rot_from_to(from,to) + ) * affine3d_rot_by_axis(from,a) ) : !is_undef(v)? assert(is_num(a)) affine3d_rot_by_axis(v,a) : is_num(a)? affine3d_zrot(a) : @@ -645,19 +701,11 @@ function scale(v=1, p=_NO_ARG, cp=[0,0,0]) = assert(is_vector(cp)) let( v = is_num(v)? [v,v,v] : v, - m = len(v)==2? ( - cp==[0,0,0] || cp == [0,0] ? affine2d_scale(v) : ( - affine2d_translate(point2d(cp)) * - affine2d_scale(v) * - affine2d_translate(point2d(-cp)) - ) - ) : ( - cp==[0,0,0] ? affine3d_scale(v) : ( - affine3d_translate(point3d(cp)) * - affine3d_scale(v) * - affine3d_translate(point3d(-cp)) - ) - ) + m = cp==[0,0,0] + ? affine3d_scale(v) + : affine3d_translate(point3d(cp)) + * affine3d_scale(v) + * affine3d_translate(point3d(-cp)) ) p==_NO_ARG? m : apply(m, p) ; @@ -1278,15 +1326,17 @@ function is_2d_transform(t) = // z-parameters are zero, except we allow t[2][ // 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. +// Applies the specified transformation matrix `transform` to a point, point list, bezier patch or VNF. +// When `points` contains 2D or 3D points the transform matrix may be a 4x4 affine matrix or a 3x4 matrix--- +// the 4x4 matrix with its final row removed. When the data is 2D the matrix must not operate on the Z axis, +// except possibly by scaling it. When points contains 2D data you can also supply the transform as +// a 3x3 affine transformation matrix or the corresponding 2x3 matrix with the last row deleted. // . -// 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. +// Any other combination of matrices will produce an error. The output of apply is always the same +// dimension as the input---projections are not supported. // 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. +// transform = The 2D (3x3 or 2x3) or 3D (4x4 or 3x4) transformation matrix to apply. +// points = The point, point list, bezier patch, or VNF to apply the transformation to. // Example(3D): // path1 = path3d(circle(r=40)); // tmat = xrot(45); @@ -1319,30 +1369,27 @@ function apply(transform,points) = : _apply(transform,points); + + function _apply(transform,points) = assert(is_matrix(transform),"Invalid transformation matrix") assert(is_matrix(points),"Invalid points list") 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]]] + datadim = len(points[0]) ) - tdim==datadim && (datadim==3 || datadim==2) + assert(len(transform)==tdim || len(transform)-1==tdim, "transform matrix height not compatible with width") + assert(datadim==2 || datadim==3,"Data must be 2D or 3D") + let( + matrix = [for(i=[0:1:tdim]) [for(j=[0:1:datadim-1]) transform[j][i]]] + ) + tdim==datadim ? [for(p=points) concat(p,1)] * matrix - : tdim == 3 && datadim == 2 ? - assert(is_2d_transform(transform), str("Transforms is 3d but points are 2d")) + : tdim == 3 && datadim == 2 ? + assert(is_2d_transform(transform), str("Transforms is 3D and acts on Z, but points are 2D")) [for(p=points) concat(p,[0,1])]*matrix - : tdim == 2 && datadim == 3 ? - let( - matrix3d =[[ matrix[0][0], matrix[0][1], 0], - [ matrix[1][0], matrix[1][1], 0], - [ 0, 0, 1], - [ matrix[2][0], matrix[2][1], 0]] - ) - [for(p=points) concat(p,1)] * matrix3d - : assert(false, str("Unsupported combination: transform with dimension ",tdim,", data of dimension ",datadim)); - + : assert(false, str("Unsupported combination: ",len(transform),"x",len(transform[0])," transform (dimension ",tdim, + "), data of dimension ",datadim)); // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap From 88e0c6aa15f5f269e3f54e7134451c707dcb8d83 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Tue, 28 Dec 2021 17:07:28 -0500 Subject: [PATCH 2/5] doc fix --- transforms.scad | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transforms.scad b/transforms.scad index 00134d8..2707ab4 100644 --- a/transforms.scad +++ b/transforms.scad @@ -1332,8 +1332,8 @@ function is_2d_transform(t) = // z-parameters are zero, except we allow t[2][ // except possibly by scaling it. When points contains 2D data you can also supply the transform as // a 3x3 affine transformation matrix or the corresponding 2x3 matrix with the last row deleted. // . -// Any other combination of matrices will produce an error. The output of apply is always the same -// dimension as the input---projections are not supported. +// Any other combination of matrices will produce an error, including acting with a 2D matrix (3x3) on 3D data. +// The output of apply is always the same dimension as the input---projections are not supported. // Arguments: // transform = The 2D (3x3 or 2x3) or 3D (4x4 or 3x4) transformation matrix to apply. // points = The point, point list, bezier patch, or VNF to apply the transformation to. From 1d72c9145409bcb07e4b15821c934a2d001bf9b4 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 29 Dec 2021 17:55:29 -0500 Subject: [PATCH 3/5] apply tweaks --- affine.scad | 2 ++ linalg.scad | 2 +- transforms.scad | 23 +++++++++++------------ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/affine.scad b/affine.scad index 7867628..3bf47c8 100644 --- a/affine.scad +++ b/affine.scad @@ -404,6 +404,8 @@ function affine3d_rot_from_to(from, to) = from = unit(point3d(from)), to = unit(point3d(to)) ) approx(from,to)? affine3d_identity() : + from.z==0 && to.z==0 ? affine3d_zrot(v_theta(point2d(to)) - v_theta(point2d(from))) + : let( u = vector_axis(from,to), ang = vector_angle(from,to), diff --git a/linalg.scad b/linalg.scad index ca89e3b..f42d43b 100644 --- a/linalg.scad +++ b/linalg.scad @@ -462,7 +462,7 @@ function matrix_inverse(A) = // 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. +// so it may include a translation. This is faster and likely to be more accurate than using `matrix_inverse()`. function rot_inverse(T) = assert(is_matrix(T,square=true),"Matrix must be square") let( n = len(T)) diff --git a/transforms.scad b/transforms.scad index 2707ab4..c2f51ab 100644 --- a/transforms.scad +++ b/transforms.scad @@ -493,23 +493,21 @@ function rot(a=0, v, cp, from, to, reverse=false, planar=false, p=_NO_ARG, _m) = v_theta(from) ), m2 = is_undef(cp)? m1 : (move(cp) * m1 * move(-cp)), - m3 = reverse? matrix_inverse(m2) : m2 + m3 = reverse? rot_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)? ( + m1 = !is_undef(from) ? assert(is_num(a)) - (from.z == 0 && to.z == 0 - ? affine3d_zrot(v_theta(point2d(to)) - v_theta(point2d(from))) - : affine3d_rot_from_to(from,to) - ) * affine3d_rot_by_axis(from,a) - ) : - !is_undef(v)? assert(is_num(a)) affine3d_rot_by_axis(v,a) : - is_num(a)? affine3d_zrot(a) : - affine3d_zrot(a.z) * affine3d_yrot(a.y) * affine3d_xrot(a.x), + affine3d_rot_from_to(from,to) * affine3d_rot_by_axis(from,a) + : !is_undef(v)? + assert(is_num(a)) + 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 = reverse? rot_inverse(m2) : m2 ) m3 ) p==_NO_ARG ? m : apply(m, p); @@ -1381,7 +1379,8 @@ function _apply(transform,points) = assert(len(transform)==tdim || len(transform)-1==tdim, "transform matrix height not compatible with width") assert(datadim==2 || datadim==3,"Data must be 2D or 3D") let( - matrix = [for(i=[0:1:tdim]) [for(j=[0:1:datadim-1]) transform[j][i]]] + scale = len(transform)==tdim ? 1 : transform[tdim][tdim], + matrix = [for(i=[0:1:tdim]) [for(j=[0:1:datadim-1]) transform[j][i]/scale]] ) tdim==datadim ? [for(p=points) concat(p,1)] * matrix From 6e2929ff9b130b71a002b3cba12e0804c37be95d Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 29 Dec 2021 18:01:23 -0500 Subject: [PATCH 4/5] apply tweak --- transforms.scad | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/transforms.scad b/transforms.scad index c2f51ab..9210a8d 100644 --- a/transforms.scad +++ b/transforms.scad @@ -1380,14 +1380,13 @@ function _apply(transform,points) = assert(datadim==2 || datadim==3,"Data must be 2D or 3D") let( scale = len(transform)==tdim ? 1 : transform[tdim][tdim], - matrix = [for(i=[0:1:tdim]) [for(j=[0:1:datadim-1]) transform[j][i]/scale]] + matrix = [for(i=[0:1:tdim]) [for(j=[0:1:datadim-1]) transform[j][i]]] / scale ) - tdim==datadim - ? [for(p=points) concat(p,1)] * matrix - : tdim == 3 && datadim == 2 ? + tdim==datadim ? [for(p=points) concat(p,1)] * matrix + : tdim == 3 && datadim == 2 ? assert(is_2d_transform(transform), str("Transforms is 3D and acts on Z, but points are 2D")) [for(p=points) concat(p,[0,1])]*matrix - : assert(false, str("Unsupported combination: ",len(transform),"x",len(transform[0])," transform (dimension ",tdim, + : assert(false, str("Unsupported combination: ",len(transform),"x",len(transform[0])," transform (dimension ",tdim, "), data of dimension ",datadim)); From 13096a8874d937a2b7717660a6285318a9638e83 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 29 Dec 2021 18:21:12 -0500 Subject: [PATCH 5/5] improve polyhedron child alignment to face edge --- polyhedra.scad | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/polyhedra.scad b/polyhedra.scad index 937aa04..38fd491 100644 --- a/polyhedra.scad +++ b/polyhedra.scad @@ -344,12 +344,12 @@ module regular_polyhedron( $center = -mean(facepts); cfacepts = move($center, p=facepts); $face = rotate_children - ? path2d(rot(from=face_normals[i], to=[0,0,1], p=cfacepts)) + ? path2d(frame_map(z=face_normals[i], x=facepts[0]-facepts[1], reverse=true, p=cfacepts)) : cfacepts; $faceindex = i; translate(-$center) if (rotate_children) { - rot(from=[0,0,1], to=face_normals[i]) + frame_map(z=face_normals[i], x=facepts[0]-facepts[1]) children(i % $children); } else { children(i % $children);