mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2025-01-01 09:49:45 +00:00
teste2
This commit is contained in:
parent
685e727d8d
commit
46b0f03af3
12 changed files with 299 additions and 335 deletions
21
affine.scad
21
affine.scad
|
@ -234,7 +234,6 @@ function rot_decode(M) =
|
|||
|
||||
|
||||
|
||||
|
||||
// Section: Affine2d 3x3 Transformation Matrices
|
||||
|
||||
|
||||
|
@ -632,19 +631,11 @@ 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]
|
||||
];
|
||||
)
|
||||
approx(from,[0,0,0])
|
||||
|| approx(to,[0,0,0])
|
||||
|| approx(from+to,[0,0,0])? affine3d_identity() :
|
||||
affine3d_mirror(from+to) * affine3d_mirror(from);
|
||||
|
||||
|
||||
// Function: affine3d_frame_map()
|
||||
|
@ -707,7 +698,6 @@ function affine3d_frame_map(x,y,z, reverse=false) =
|
|||
) : [for (r=transpose(map)) [for (c=r) c, 0], [0,0,0,1]];
|
||||
|
||||
|
||||
|
||||
// Function: affine3d_mirror()
|
||||
// Usage:
|
||||
// mat = affine3d_mirror(v);
|
||||
|
@ -875,5 +865,4 @@ function affine3d_skew_yz(ya=0, za=0) =
|
|||
];
|
||||
|
||||
|
||||
|
||||
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
||||
|
|
21
arrays.scad
21
arrays.scad
|
@ -40,8 +40,10 @@ function is_homogeneous(l, depth=10) =
|
|||
let( l0=l[0] )
|
||||
[] == [for(i=[1:len(l)-1]) if( ! _same_type(l[i],l0, depth+1) ) 0 ];
|
||||
|
||||
|
||||
function is_homogenous(l, depth=10) = is_homogeneous(l, depth);
|
||||
|
||||
|
||||
function _same_type(a,b, depth) =
|
||||
(depth==0) ||
|
||||
(is_undef(a) && is_undef(b)) ||
|
||||
|
@ -227,6 +229,8 @@ function in_list(val,list,idx) =
|
|||
// Usage:
|
||||
// idx = find_first_match(val, list, <start=>, <eps=>);
|
||||
// indices = find_first_match(val, list, all=true, <start=>, <eps=>);
|
||||
// Topics: List Handling
|
||||
// See Also: max_index(), list_increasing(), list_decreasing()
|
||||
// Description:
|
||||
// Finds the first item in `list` that matches `val`, returning the index.
|
||||
// Arguments:
|
||||
|
@ -360,7 +364,6 @@ function repeat(val, n, i=0) =
|
|||
// If both `n` and `e` are given, returns `n` values evenly spread from `s`
|
||||
// to `e`, and `step` is ignored.
|
||||
// Arguments:
|
||||
// ---
|
||||
// n = Desired number of values in returned list, if given.
|
||||
// s = Starting value. Default: 0
|
||||
// e = Ending value to stop at, if given.
|
||||
|
@ -488,6 +491,7 @@ function deduplicate(list, closed=false, eps=EPSILON) =
|
|||
// closed = If true, drops trailing indices if what they index matches what the first index indexes.
|
||||
// eps = The maximum difference to allow between numbers or vectors.
|
||||
// Examples:
|
||||
// list = [0,1,2,3];
|
||||
// a = deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1]); // Returns: [1,4,3,2,0,1]
|
||||
// b = deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1], closed=true); // Returns: [1,4,3,2,0]
|
||||
// c = deduplicate_indexed([[7,undef],[7,undef],[1,4],[1,4],[1,4+1e-12]],eps=0); // Returns: [0,2,4]
|
||||
|
@ -1287,6 +1291,8 @@ function permutations(l,n=2) =
|
|||
// pairs = zip(a,b);
|
||||
// triples = zip(a,b,c);
|
||||
// quads = zip([LIST1,LIST2,LIST3,LIST4]);
|
||||
// Topics: List Handling, Iteration
|
||||
// See Also: zip_long()
|
||||
// Description:
|
||||
// Zips together two or more lists into a single list. For example, if you have two
|
||||
// lists [3,4,5], and [8,7,6], and zip them together, you get [[3,8],[4,7],[5,6]].
|
||||
|
@ -1312,6 +1318,8 @@ function zip(a,b,c) =
|
|||
// pairs = zip_long(a,b);
|
||||
// triples = zip_long(a,b,c);
|
||||
// quads = zip_long([LIST1,LIST2,LIST3,LIST4]);
|
||||
// Topics: List Handling, Iteration
|
||||
// See Also: zip()
|
||||
// Description:
|
||||
// Zips together two or more lists into a single list. For example, if you have two
|
||||
// lists [3,4,5], and [8,7,6], and zip them together, you get [[3,8],[4,7],[5,6]].
|
||||
|
@ -1446,6 +1454,7 @@ function set_intersection(a, b) =
|
|||
// d = subindex(M,[1:3]); // Returns [[2, 3, 4], [6, 7, 8], [10, 11, 12], [14, 15, 16]]
|
||||
// N = [ [1,2], [3], [4,5], [6,7,8] ];
|
||||
// e = subindex(N,[0,1]); // Returns [ [1,2], [3,undef], [4,5], [6,7] ]
|
||||
// subindex(N,[0,1]); // Returns [ [1,2], [3,undef], [4,5], [6,7] ]
|
||||
function subindex(M, idx) =
|
||||
assert( is_list(M), "The input is not a list." )
|
||||
assert( !is_undef(idx) && _valid_idx(idx,0,1/0), "Invalid index input." )
|
||||
|
@ -1481,7 +1490,6 @@ function subindex(M, idx) =
|
|||
// [[4,2], 91, false],
|
||||
// [6, [3,4], undef]];
|
||||
// submatrix(A,[0,2],[1,2]); // Returns [[17, "test"], [[3, 4], undef]]
|
||||
|
||||
function submatrix(M,idx1,idx2) =
|
||||
[for(i=idx1) [for(j=idx2) M[i][j] ] ];
|
||||
|
||||
|
@ -1505,6 +1513,7 @@ function submatrix(M,idx1,idx2) =
|
|||
// Example:
|
||||
// M = ident(3);
|
||||
// v1 = [2,3,4];
|
||||
// v1 = [1,2,3,4];
|
||||
// v2 = [5,6,7];
|
||||
// v3 = [8,9,10];
|
||||
// a = hstack(v1,v2); // Returns [[2, 5], [3, 6], [4, 7]]
|
||||
|
@ -1584,7 +1593,6 @@ function block_matrix(M) =
|
|||
assert(badrows==[], "Inconsistent or invalid input")
|
||||
bigM;
|
||||
|
||||
|
||||
// Function: diagonal_matrix()
|
||||
// Usage:
|
||||
// mat = diagonal_matrix(diag, <offdiag>);
|
||||
|
@ -1633,11 +1641,11 @@ function submatrix_set(M,A,m=0,n=0) =
|
|||
// Function: array_group()
|
||||
// Usage:
|
||||
// groups = array_group(v, <cnt>, <dflt>);
|
||||
// Topics: Matrices, Array Handling
|
||||
// See Also: subindex(), submatrix(), hstack(), flatten(), full_flatten()
|
||||
// Description:
|
||||
// Takes a flat array of values, and groups items in sets of `cnt` length.
|
||||
// The opposite of this is `flatten()`.
|
||||
// Topics: Matrices, Array Handling
|
||||
// See Also: subindex(), submatrix(), hstack(), flatten(), full_flatten()
|
||||
// Arguments:
|
||||
// v = The list of items to group.
|
||||
// cnt = The number of items to put in each grouping. Default:2
|
||||
|
@ -1661,6 +1669,7 @@ function array_group(v, cnt=2, dflt=0) =
|
|||
// Arguments:
|
||||
// l = List to flatten.
|
||||
// Example:
|
||||
// flatten([[1,2,3], [4,5,[6,7,8]]]) returns [1,2,3,4,5,[6,7,8]]
|
||||
// l = flatten([[1,2,3], [4,5,[6,7,8]]]); // returns [1,2,3,4,5,[6,7,8]]
|
||||
function flatten(l) =
|
||||
!is_list(l)? l :
|
||||
|
@ -1810,7 +1819,7 @@ function transpose(arr, reverse=false) =
|
|||
// A = matrix to test
|
||||
// eps = epsilon for comparing equality. Default: 1e-12
|
||||
function is_matrix_symmetric(A,eps=1e-12) =
|
||||
approx(A,transpose(A));
|
||||
approx(A,transpose(A), eps);
|
||||
|
||||
|
||||
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
||||
|
|
21
beziers.scad
21
beziers.scad
|
@ -6,6 +6,7 @@
|
|||
// include <BOSL2/beziers.scad>
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// Terminology:
|
||||
// Path = A series of points joined by straight line segements.
|
||||
// Bezier Curve = A mathematical curve that joins two endpoints, following a curve determined by one or more control points.
|
||||
|
@ -377,7 +378,6 @@ function bezier_tangent(curve, u) =
|
|||
[for (v=res) unit(v)];
|
||||
|
||||
|
||||
|
||||
// Function: bezier_curvature()
|
||||
// Usage:
|
||||
// crv = bezier_curvature(curve, u);
|
||||
|
@ -407,6 +407,7 @@ function bezier_curvature(curve, u) =
|
|||
];
|
||||
|
||||
|
||||
|
||||
// Function: bezier_curve()
|
||||
// Usage:
|
||||
// path = bezier_curve(curve, n, <endpoint>);
|
||||
|
@ -416,9 +417,10 @@ function bezier_curvature(curve, u) =
|
|||
// Takes a list of bezier curve control points and generates n points along the bezier path.
|
||||
// Points start at the first control point and are sampled every `1/n`th
|
||||
// of the way along the bezier parameter, ending *before* the final control point by default.
|
||||
// The distance between the points will *not* be equidistant. If you wish to add the
|
||||
// endpoint you can set `endpoint` to true. The degree of the bezier curve is one
|
||||
// less than the number of points in `curve`.
|
||||
// If you wish to add the endpoint you can set `endpoint` to true.
|
||||
// The distance between the points will *not* be equidistant. The points are usually more
|
||||
// concentrated where the curve has a greater curvature. The degree of the bezier curve is
|
||||
// one less than the number of points in `curve`.
|
||||
// Arguments:
|
||||
// curve = The list of endpoints and control points for this bezier segment.
|
||||
// n = The number of points to generate along the bezier curve.
|
||||
|
@ -435,10 +437,12 @@ function bezier_curvature(curve, u) =
|
|||
// bez = [[0,0], [5,15], [40,20], [60,-15], [80,0]];
|
||||
// move_copies(bezier_curve(bez, 8)) sphere(r=1.5, $fn=12);
|
||||
// trace_bezier(bez, N=len(bez)-1);
|
||||
function bezier_curve(curve,n,endpoint) = [each bezier_points(curve, [0:1/n:(n-0.5)/n]),
|
||||
function bezier_curve(curve,n,endpoint=false) =
|
||||
[each bezier_points(curve, [0:1/n:(n-0.5)/n]),
|
||||
if (endpoint) curve[len(curve)-1]
|
||||
];
|
||||
|
||||
|
||||
// Function: bezier_segment_closest_point()
|
||||
// Usage:
|
||||
// u = bezier_segment_closest_point(bezier, pt, <max_err>);
|
||||
|
@ -524,7 +528,6 @@ function bezier_segment_length(curve, start_u=0, end_u=1, max_deflect=0.01) =
|
|||
]);
|
||||
|
||||
|
||||
|
||||
// Function: bezier_line_intersection()
|
||||
// Usage:
|
||||
// u = bezier_line_intersection(curve, line);
|
||||
|
@ -548,7 +551,6 @@ function bezier_line_intersection(curve, line) =
|
|||
[for(u=real_roots(q)) if (u>=0 && u<=1) u];
|
||||
|
||||
|
||||
|
||||
// Function: fillet3pts()
|
||||
// Usage:
|
||||
// bez_path_pts = fillet3pts(p0, p1, p2, r);
|
||||
|
@ -661,7 +663,6 @@ function bezier_path_closest_point(path, pt, N=3, max_err=0.01, seg=0, min_seg=u
|
|||
);
|
||||
|
||||
|
||||
|
||||
// Function: bezier_path_length()
|
||||
// Usage:
|
||||
// plen = bezier_path_length(path, <N>, <max_deflect>);
|
||||
|
@ -946,7 +947,6 @@ module bezier_polygon(bezier, splinesteps=16, N=3) {
|
|||
}
|
||||
|
||||
|
||||
|
||||
// Module: trace_bezier()
|
||||
// Usage:
|
||||
// trace_bezier(bez, <size>, <N=>) {
|
||||
|
@ -1294,6 +1294,7 @@ function patch_reverse(patch) =
|
|||
[for (row=patch) reverse(row)];
|
||||
|
||||
|
||||
|
||||
// Section: Bezier Surface Modules
|
||||
|
||||
|
||||
|
@ -1338,8 +1339,6 @@ function bezier_surface(patches=[], splinesteps=16, vnf=EMPTY_VNF, style="defaul
|
|||
bezier_surface(patches=patches, splinesteps=splinesteps, vnf=vnf, style=style, i=i+1);
|
||||
|
||||
|
||||
|
||||
|
||||
// Module: trace_bezier_patches()
|
||||
// Usage:
|
||||
// trace_bezier_patches(patches, [size], [splinesteps], [showcps], [showdots], [showpatch], [convexity], [style]);
|
||||
|
|
|
@ -214,7 +214,14 @@ function is_consistent(list, pattern) =
|
|||
is_list(list)
|
||||
&& (len(list)==0
|
||||
|| (let(pattern = is_undef(pattern) ? _list_pattern(list[0]): _list_pattern(pattern) )
|
||||
[]==[for(entry=0*list) if (entry != pattern) entry]));
|
||||
[]==[for(entry=0*list) if (entry != pattern) 0]));
|
||||
//
|
||||
// Note: in the event that 0*list produces a warning for non numeric /list/ in a future version,
|
||||
// the last line above may be rewriten as:
|
||||
// []==[for(entry=_list_pattern(list)) if (entry != pattern) entry]));
|
||||
|
||||
/*
|
||||
*/
|
||||
|
||||
//Internal function
|
||||
//Creates a list with the same structure of `list` with each of its elements replaced by 0.
|
||||
|
|
|
@ -1072,6 +1072,7 @@ function distance_from_plane(plane, point) =
|
|||
let( plane = normalize_plane(plane) )
|
||||
point3d(plane)* point - plane[3];
|
||||
|
||||
|
||||
// Returns [POINT, U] if line intersects plane at one point.
|
||||
// Returns [LINE, undef] if the line is on the plane.
|
||||
// Returns undef if line is parallel to, but not on the given plane.
|
||||
|
@ -1594,7 +1595,6 @@ function circle_circle_tangents(c1,r1,c2,r2,d1,d2) =
|
|||
];
|
||||
|
||||
|
||||
|
||||
// Function: circle_line_intersection()
|
||||
// Usage:
|
||||
// isect = circle_line_intersection(c,r,line,<bounded>,<eps>);
|
||||
|
@ -1627,7 +1627,6 @@ function circle_line_intersection(c,r,line,d,bounded=false,eps=EPSILON) =
|
|||
let( offset = sqrt(r*r-d*d),
|
||||
uvec=unit(line[1]-line[0])
|
||||
) [closest-offset*uvec, closest+offset*uvec]
|
||||
|
||||
)
|
||||
[for(p=isect)
|
||||
if ((!bounded[0] || (p-line[0])*(line[1]-line[0])>=0)
|
||||
|
@ -1635,7 +1634,6 @@ function circle_line_intersection(c,r,line,d,bounded=false,eps=EPSILON) =
|
|||
|
||||
|
||||
|
||||
|
||||
// Section: Pointlists
|
||||
|
||||
|
||||
|
|
69
math.scad
69
math.scad
|
@ -22,8 +22,7 @@ INF = 1/0;
|
|||
|
||||
// Constant: NAN
|
||||
// Description: The value `nan`, useful for comparisons.
|
||||
NAN = acos(2);
|
||||
|
||||
NAN = 0/0;
|
||||
|
||||
|
||||
// Section: Simple math
|
||||
|
@ -588,7 +587,9 @@ function sum(v, dflt=0) =
|
|||
is_vector(v) || is_matrix(v) ? [for(i=[1:len(v)]) 1]*v :
|
||||
_sum(v,v[0]*0);
|
||||
|
||||
function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
|
||||
function _sum(v,_total,_i=0) =
|
||||
_i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
|
||||
|
||||
|
||||
// Function: cumsum()
|
||||
// Usage:
|
||||
|
@ -985,20 +986,18 @@ function determinant(M) =
|
|||
// n = Is given, requires the matrix to have the given width.
|
||||
// square = If true, requires the matrix to have a width equal to its height. Default: false
|
||||
function is_matrix(A,m,n,square=false) =
|
||||
is_list(A)
|
||||
&& (( is_undef(m) && len(A) ) || len(A)==m)
|
||||
&& is_list(A[0])
|
||||
&& (( is_undef(n) && len(A[0]) ) || len(A[0])==n)
|
||||
is_consistent(A)
|
||||
&& len(A)>0
|
||||
&& is_vector(A[0],n)
|
||||
&& (!square || len(A) == len(A[0]))
|
||||
&& is_vector(A[0])
|
||||
&& is_consistent(A);
|
||||
&& ( is_undef(m) || len(A)==m );
|
||||
|
||||
|
||||
// Function: norm_fro()
|
||||
// Usage:
|
||||
// norm_fro(A)
|
||||
// Description:
|
||||
// Computes frobenius norm of input matrix. The frobenius norm is the square root of the sum of the
|
||||
// Computes Frobenius norm of input matrix. The Frobenius norm is the square root of the sum of the
|
||||
// squares of all of the entries of the matrix. On vectors it is the same as the usual 2-norm.
|
||||
// This is an easily computed norm that is convenient for comparing two matrices.
|
||||
function norm_fro(A) =
|
||||
|
@ -1006,6 +1005,17 @@ function norm_fro(A) =
|
|||
norm(flatten(A));
|
||||
|
||||
|
||||
// Function: norm_max()
|
||||
// Usage:
|
||||
// norm_max(v)
|
||||
// Description:
|
||||
// Computes maximum norm of input vector.
|
||||
// The maximum norm is the maximum of the absolute values of the coordinates of the vector.
|
||||
function norm_max(v) =
|
||||
assert(is_vector(v) && len(v)>0, "The norm_max requires a vector with length greater than 0.")
|
||||
max(max(v),max(-v));
|
||||
|
||||
|
||||
// Function: matrix_trace()
|
||||
// Usage:
|
||||
// matrix_trace(M)
|
||||
|
@ -1165,7 +1175,7 @@ function all_nonnegative(x) =
|
|||
// Arguments:
|
||||
// a = First value.
|
||||
// b = Second value.
|
||||
// eps = The maximum allowed difference between `a` and `b` that will return true.
|
||||
// eps = The maximum allowed difference between `a` and `b` that will return true. Default: EPSILON.
|
||||
// Example:
|
||||
// approx(-0.3333333333,-1/3); // Returns: true
|
||||
// approx(0.3333333333,1/3); // Returns: true
|
||||
|
@ -1175,6 +1185,7 @@ function all_nonnegative(x) =
|
|||
function approx(a,b,eps=EPSILON) =
|
||||
(a==b && is_bool(a) == is_bool(b)) ||
|
||||
(is_num(a) && is_num(b) && abs(a-b) <= eps) ||
|
||||
(is_vector(a) && is_vector(b,len(a)) && norm_max(a-b) <= eps ) ||
|
||||
(is_list(a) && is_list(b) && len(a) == len(b) && [] == [for (i=idx(a)) if (!approx(a[i],b[i],eps=eps)) 1]);
|
||||
|
||||
|
||||
|
@ -1485,9 +1496,15 @@ function complex(list) =
|
|||
// Multiplies two complex numbers, vectors or matrices, where complex numbers
|
||||
// or entries are represented as vectors: [REAL, IMAGINARY]. Note that all
|
||||
// entries in both arguments must be complex.
|
||||
// Multiplies two complex numbers represented by 2D vectors.
|
||||
// Returns a complex number as a 2D vector [REAL, IMAGINARY].
|
||||
// Arguments:
|
||||
// z1 = First complex number, vector or matrix
|
||||
// z2 = Second complex number, vector or matrix
|
||||
function c_mul(z1,z2) =
|
||||
is_matrix([z1,z2],2,2) ? _c_mul(z1,z2) :
|
||||
_combine_complex(_c_mul(_split_complex(z1), _split_complex(z2)));
|
||||
|
||||
|
||||
function _split_complex(data) =
|
||||
is_vector(data,2) ? data
|
||||
|
@ -1497,6 +1514,7 @@ function _split_complex(data) =
|
|||
[for(vec=data) vec * [0,1]]
|
||||
];
|
||||
|
||||
|
||||
function _combine_complex(data) =
|
||||
is_vector(data,2) ? data
|
||||
: is_num(data[0][0]) ? [for(i=[0:len(data[0])-1]) [data[0][i],data[1][i]]]
|
||||
|
@ -1504,13 +1522,10 @@ function _combine_complex(data) =
|
|||
[for(j=[0:1:len(data[0][0])-1])
|
||||
[data[0][i][j], data[1][i][j]]]];
|
||||
|
||||
|
||||
function _c_mul(z1,z2) =
|
||||
[ z1.x*z2.x - z1.y*z2.y, z1.x*z2.y + z1.y*z2.x ];
|
||||
|
||||
function c_mul(z1,z2) =
|
||||
is_matrix([z1,z2],2,2) ? _c_mul(z1,z2) :
|
||||
_combine_complex(_c_mul(_split_complex(z1), _split_complex(z2)));
|
||||
|
||||
|
||||
// Function: c_div()
|
||||
// Usage:
|
||||
|
@ -1538,6 +1553,7 @@ function c_conj(z) =
|
|||
is_vector(z,2) ? [z.x,-z.y] :
|
||||
[for(entry=z) c_conj(entry)];
|
||||
|
||||
|
||||
// Function: c_real()
|
||||
// Usage:
|
||||
// x = c_real(z)
|
||||
|
@ -1548,6 +1564,7 @@ function c_real(z) =
|
|||
: is_num(z[0][0]) ? z*[1,0]
|
||||
: [for(vec=z) vec * [1,0]];
|
||||
|
||||
|
||||
// Function: c_imag()
|
||||
// Usage:
|
||||
// x = c_imag(z)
|
||||
|
@ -1566,6 +1583,7 @@ function c_imag(z) =
|
|||
// Produce an n by n complex identity matrix
|
||||
function c_ident(n) = [for (i = [0:1:n-1]) [for (j = [0:1:n-1]) (i==j)?[1,0]:[0,0]]];
|
||||
|
||||
|
||||
// Function: c_norm()
|
||||
// Usage:
|
||||
// n = c_norm(z)
|
||||
|
@ -1618,12 +1636,12 @@ function quadratic_roots(a,b,c,real=false) =
|
|||
// where a_n is the z^n coefficient. Polynomial coefficients are real.
|
||||
// The result is a number if `z` is a number and a complex number otherwise.
|
||||
function polynomial(p,z,k,total) =
|
||||
is_undef(k)
|
||||
? assert( is_vector(p) , "Input polynomial coefficients must be a vector." )
|
||||
assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
|
||||
polynomial( _poly_trim(p), z, 0, is_num(z) ? 0 : [0,0])
|
||||
: k==len(p) ? total
|
||||
: polynomial(p,z,k+1, is_num(z) ? total*z+p[k] : c_mul(total,z)+[p[k],0]);
|
||||
is_undef(k)
|
||||
? assert( is_vector(p) , "Input polynomial coefficients must be a vector." )
|
||||
assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
|
||||
polynomial( _poly_trim(p), z, 0, is_num(z) ? 0 : [0,0])
|
||||
: k==len(p) ? total
|
||||
: polynomial(p,z,k+1, is_num(z) ? total*z+p[k] : c_mul(total,z)+[p[k],0]);
|
||||
|
||||
// Function: poly_mult()
|
||||
// Usage:
|
||||
|
@ -1633,12 +1651,11 @@ function polynomial(p,z,k,total) =
|
|||
// Given a list of polynomials represented as real coefficient lists, with the highest degree coefficient first,
|
||||
// computes the coefficient list of the product polynomial.
|
||||
function poly_mult(p,q) =
|
||||
is_undef(q) ?
|
||||
len(p)==2
|
||||
is_undef(q) ?
|
||||
len(p)==2
|
||||
? poly_mult(p[0],p[1])
|
||||
: poly_mult(p[0], poly_mult(select(p,1,-1)))
|
||||
:
|
||||
assert( is_vector(p) && is_vector(q),"Invalid arguments to poly_mult")
|
||||
: poly_mult(p[0], poly_mult(select(p,1,-1)))
|
||||
: assert( is_vector(p) && is_vector(q),"Invalid arguments to poly_mult")
|
||||
p*p==0 || q*q==0
|
||||
? [0]
|
||||
: _poly_trim(convolve(p,q));
|
||||
|
|
11
paths.scad
11
paths.scad
|
@ -989,7 +989,6 @@ module jittered_poly(path, dist=1/512) {
|
|||
|
||||
|
||||
|
||||
|
||||
// Section: 3D Modules
|
||||
|
||||
|
||||
|
@ -1008,11 +1007,11 @@ module jittered_poly(path, dist=1/512) {
|
|||
// xcopies(3) circle(3, $fn=32);
|
||||
// }
|
||||
module extrude_from_to(pt1, pt2, convexity, twist, scale, slices) {
|
||||
rtp = xyz_to_spherical(pt2-pt1);
|
||||
translate(pt1) {
|
||||
rotate([0, rtp[2], rtp[1]]) {
|
||||
if (rtp[0] > 0) {
|
||||
linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) {
|
||||
rot(from=[0,0,1],to=pt2-pt1) {
|
||||
h = norm(pt2-pt1);
|
||||
if (h > 0) {
|
||||
linear_extrude(height=h, convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) {
|
||||
children();
|
||||
}
|
||||
}
|
||||
|
@ -1021,7 +1020,6 @@ module extrude_from_to(pt1, pt2, convexity, twist, scale, slices) {
|
|||
}
|
||||
|
||||
|
||||
|
||||
// Module: spiral_sweep()
|
||||
// Description:
|
||||
// Takes a closed 2D polygon path, centered on the XY plane, and sweeps/extrudes it along a 3D spiral path.
|
||||
|
@ -1443,7 +1441,6 @@ function path_cut(path,cutdist,closed) =
|
|||
];
|
||||
|
||||
|
||||
|
||||
// Input `data` is a list that sums to an integer.
|
||||
// Returns rounded version of input data so that every
|
||||
// entry is rounded to an integer and the sum is the same as
|
||||
|
|
359
skin.scad
359
skin.scad
|
@ -1,11 +1,13 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
// LibFile: skin.scad
|
||||
// Functions to skin arbitrary 2D profiles/paths in 3-space.
|
||||
// Inspired by list-comprehension-demos skin():
|
||||
// - https://github.com/openscad/list-comprehension-demos/blob/master/skin.scad
|
||||
// Includes:
|
||||
// To use, add the following line to the beginning of your file:
|
||||
// ```
|
||||
// include <BOSL2/std.scad>
|
||||
// include <BOSL2/skin.scad>
|
||||
// ```
|
||||
// Inspired by list-comprehension-demos skin():
|
||||
// - https://github.com/openscad/list-comprehension-demos/blob/master/skin.scad
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
@ -13,15 +15,16 @@
|
|||
|
||||
// Function&Module: skin()
|
||||
// Usage: As module:
|
||||
// skin(profiles, slices, <z=>, <refine=>, <method=>, <sampling=>, <caps=>, <closed=>, <convexity=>, <anchor=>,<cp=>,<spin=>,<orient=>,<extent=>) <attachments>;
|
||||
// skin(profiles, [slices], [refine], [method], [sampling], [caps], [closed], [z], [convexity],
|
||||
// [anchor],[cp],[spin],[orient],[extent]);
|
||||
// Usage: As function:
|
||||
// vnf = skin(profiles, slices, <z=>, <refine=>, <method=>, <sampling=>, <caps=>, <closed=>);
|
||||
// vnf = skin(profiles, [slices], [refine], [method], [sampling], [caps], [closed], [z]);
|
||||
// Description:
|
||||
// Given a list of two or more path `profiles` in 3d space, produces faces to skin a surface between
|
||||
// the profiles. Optionally the first and last profiles can have endcaps, or the first and last profiles
|
||||
// can be connected together. Each profile should be roughly planar, but some variation is allowed.
|
||||
// Each profile must rotate in the same clockwise direction. If called as a function, returns a
|
||||
// [VNF structure](vnf.scad) `[VERTICES, FACES]`. If called as a module, creates a polyhedron
|
||||
// [VNF structure](vnf.scad) like `[VERTICES, FACES]`. If called as a module, creates a polyhedron
|
||||
// of the skinned profiles.
|
||||
// .
|
||||
// The profiles can be specified either as a list of 3d curves or they can be specified as
|
||||
|
@ -33,47 +36,45 @@
|
|||
// For this operation to be well-defined, the profiles must all have the same vertex count and
|
||||
// we must assume that profiles are aligned so that vertex `i` links to vertex `i` on all polygons.
|
||||
// Many interesting cases do not comply with this restriction. Two basic methods can handle
|
||||
// these cases: either subdivide edges (insert additional points along edges)
|
||||
// or duplicate vertcies (insert edges of length 0) so that both polygons have
|
||||
// the same number of points.
|
||||
// Duplicating vertices allows two distinct points in one polygon to connect to a single point
|
||||
// in the other one, creating
|
||||
// triangular faces. You can adjust non-matching polygons yourself
|
||||
// these cases: either add points to edges (resample) so that the profiles are compatible,
|
||||
// or repeat vertices. Repeating vertices allows two edges to terminate at the same point, creating
|
||||
// triangular faces. You can adjust non-matching profiles yourself
|
||||
// either by resampling them using `subdivide_path` or by duplicating vertices using
|
||||
// `repeat_entries`. It is OK to pass a polygon that has the same vertex repeated, such as
|
||||
// `repeat_entries`. It is OK to pass a profile that has the same vertex repeated, such as
|
||||
// a square with 5 points (two of which are identical), so that it can match up to a pentagon.
|
||||
// Such a combination would create a triangular face at the location of the duplicated vertex.
|
||||
// Alternatively, `skin` provides methods (described below) for inserting additional vertices
|
||||
// automatically to make incompatible paths match.
|
||||
// Alternatively, `skin` provides methods (described below) for matching up incompatible paths.
|
||||
// .
|
||||
// In order for skinned surfaces to look good it is usually necessary to use a fine sampling of
|
||||
// points on all of the profiles, and a large number of extra interpolated slices between the
|
||||
// profiles that you specify. It is generally best if the triangles forming your polyhedron
|
||||
// are approximately equilateral. The `slices` parameter specifies the number of slices to insert
|
||||
// between each pair of profiles, either a scalar to insert the same number everywhere, or a vector
|
||||
// to insert a different number between each pair.
|
||||
// .
|
||||
// Resampling may occur, depending on the `method` parameter, to make profiles compatible.
|
||||
// To force (possibly additional) resampling of the profiles to increase the point density you can set `refine=N`, which
|
||||
// will multiply the number of points on your profile by `N`. You can choose between two resampling
|
||||
// schemes using the `sampling` option, which you can set to `"length"` or `"segment"`.
|
||||
// The length resampling method resamples proportional to length.
|
||||
// The segment method divides each segment of a profile into the same number of points.
|
||||
// This means that if you refine a profile with the "segment" method you will get N points
|
||||
// on each edge, but if you refine a profile with the "length" method you will get new points
|
||||
// distributed around the profile based on length, so small segments will get fewer new points than longer ones.
|
||||
// A uniform division may be impossible, in which case the code computes an approximation, which may result
|
||||
// in arbitrary distribution of extra points. See `subdivide_path` for more details.
|
||||
// Note that when dealing with continuous curves it is always better to adjust the
|
||||
// to insert a different number between each pair. To resample the profiles you can use set
|
||||
// `refine=N` which will place `N` points on each edge of your profile. This has the effect of
|
||||
// multiplying the number of points by N, so a profile with 8 points will have 8*N points after
|
||||
// refinement. Note that when dealing with continuous curves it is always better to adjust the
|
||||
// sampling in your code to generate the desired sampling rather than using the `refine` argument.
|
||||
// .
|
||||
// You can choose from five methods for specifying alignment for incommensurate profiles.
|
||||
// The available methods are `"distance"`, `"fast_distance"`, `"tangent"`, `"direct"` and `"reindex"`.
|
||||
// Two methods are available for resampling, `"length"` and `"segment"`. Specify them using
|
||||
// the `sampling` argument. The length resampling method resamples proportional to length.
|
||||
// The segment method divides each segment of a profile into the same number of points.
|
||||
// A uniform division may be impossible, in which case the code computes an approximation.
|
||||
// See `subdivide_path` for more details.
|
||||
//
|
||||
// You can choose from four methods for specifying alignment for incommensurate profiles.
|
||||
// The available methods are `"distance"`, `"tangent"`, `"direct"` and `"reindex"`.
|
||||
// It is useful to distinguish between continuous curves like a circle and discrete profiles
|
||||
// like a hexagon or star, because the algorithms' suitability depend on this distinction.
|
||||
// .
|
||||
// The default method for aligning profiles is `method="direct"`.
|
||||
// If you simply supply a list of compatible profiles it will link them up
|
||||
// The "direct" and "reindex" methods work by resampling the profiles if necessary. As noted above,
|
||||
// for continuous input curves, it is better to generate your curves directly at the desired sample size,
|
||||
// but for mapping between a discrete profile like a hexagon and a circle, the hexagon must be resampled
|
||||
// to match the circle. You can do this in two different ways using the `sampling` parameter. The default
|
||||
// of `sampling="length"` approximates a uniform length sampling of the profile. The other option
|
||||
// is `sampling="segment"` which attempts to place the same number of new points on each segment.
|
||||
// If the segments are of varying length, this will produce a different result. Note that "direct" is
|
||||
// the default method. If you simply supply a list of compatible profiles it will link them up
|
||||
// exactly as you have provided them. You may find that profiles you want to connect define the
|
||||
// right shapes but the point lists don't start from points that you want aligned in your skinned
|
||||
// polyhedron. You can correct this yourself using `reindex_polygon`, or you can use the "reindex"
|
||||
|
@ -81,51 +82,22 @@
|
|||
// in the polyhedron---in will produce the least twisted possible result. This algorithm has quadratic
|
||||
// run time so it can be slow with very large profiles.
|
||||
// .
|
||||
// When the profiles are incommensurate, the "direct" and "reindex" resample them to match. As noted above,
|
||||
// for continuous input curves, it is better to generate your curves directly at the desired sample size,
|
||||
// but for mapping between a discrete profile like a hexagon and a circle, the hexagon must be resampled
|
||||
// to match the circle. When you use "direct" or "reindex" the default `sampling` value is
|
||||
// of `sampling="length"` to approximate a uniform length sampling of the profile. This will generally
|
||||
// produce the natural result for connecting two continuously sampled profiles or a continuous
|
||||
// profile and a polygonal one. However depending on your particular case,
|
||||
// `sampling="segment"` may produce a more pleasing result. These two approaches differ only when
|
||||
// the segments of your input profiles have unequal length.
|
||||
// .
|
||||
// The "distance", "fast_distance" and "tangent" methods work by duplicating vertices to create
|
||||
// triangular faces. In the skined object created by two polygons, every vertex of a polygon must
|
||||
// have an edge that connects to some vertex on the other one. If you connect two squares this can be
|
||||
// accomplished with four edges, but if you want to connect a square to a pentagon you must add a
|
||||
// fifth edge for the "extra" vertex on the pentagon. You must now decide which vertex on the square to
|
||||
// connect the "extra" edge to. How do you decide where to put that fifth edge? The "distance" method answers this
|
||||
// question by using an optimization: it minimizes the total length of all the edges connecting
|
||||
// the two polygons. This algorithm generally produces a good result when both profiles are discrete ones with
|
||||
// The "distance" and "tangent" methods are work by duplicating vertices to create
|
||||
// triangular faces. The "distance" method finds the global minimum distance method for connecting two
|
||||
// profiles. This algorithm generally produces a good result when both profiles are discrete ones with
|
||||
// a small number of vertices. It is computationally intensive (O(N^3)) and may be
|
||||
// slow on large inputs. The resulting surfaces generally have curved faces, so be
|
||||
// sure to select a sufficiently large value for `slices` and `refine`. Note that for
|
||||
// this method, `sampling` must be set to `"segment"`, and hence this is the default setting.
|
||||
// Using sampling by length would ignore the repeated vertices and ruin the alignment.
|
||||
// The "fast_distance" method restricts the optimization by assuming that an edge should connect
|
||||
// vertex 0 of the two polygons. This reduces the run time to O(N^2) and makes
|
||||
// the method usable on profiles with more points if you take care to index the inputs to match.
|
||||
// .
|
||||
// slow on large inputs. The resulting surfaces generally have curves faces, so be
|
||||
// sure to select a sufficiently large value for `slices` and `refine`.
|
||||
// The `"tangent"` method generally produces good results when
|
||||
// connecting a discrete polygon to a convex, finely sampled curve. Given a polygon and a curve, consider one edge
|
||||
// on the polygon. Find a plane passing through the edge that is tangent to the curve. The endpoints of the edge and
|
||||
// the point of tangency define a triangular face in the output polyhedron. If you work your way around the polygon
|
||||
// edges, you can establish a series of triangular faces in this way, with edges linking the polygon to the curve.
|
||||
// You can then complete the edge assignment by connecting all the edges in between the triangular faces together,
|
||||
// with many edges meeting at each polygon vertex. The result is an alternation of flat triangular faces with conical
|
||||
// curves joining them. Another way to think about it is that it splits the points on the curve up into groups and
|
||||
// connects all the points in one group to the same vertex on the polygon.
|
||||
// .
|
||||
// The "tangent" method may fail if the curved profile is non-convex, or doesn't have enough points to distinguish
|
||||
// all of the tangent points from each other. The algorithm treats whichever input profile has fewer points as the polygon
|
||||
// and the other one as the curve. Using `refine` with this method will have little effect on the model, so
|
||||
// connecting a discrete polygon to a convex, finely sampled curve. It works by finding
|
||||
// a plane that passed through each edge of the polygon that is tangent to
|
||||
// the curve. It may fail if the curved profile is non-convex, or doesn't have enough points to distinguish
|
||||
// all of the tangent points from each other. It connects all of the points of the curve to the corners of the discrete
|
||||
// polygon using triangular faces. Using `refine` with this method will have little effect on the model, so
|
||||
// you should do it only for agreement with other profiles, and these models are linear, so extra slices also
|
||||
// have no effect. For best efficiency set `refine=1` and `slices=0`. As with the "distance" method, refinement
|
||||
// must be done using the "segment" sampling scheme to preserve alignment across duplicated points.
|
||||
// Note that the "tangent" method produces similar results to the "distance" method on curved inputs. If this
|
||||
// method fails due to concavity, "fast_distance" may be a good option.
|
||||
// have no effect. For best efficiency set `refine=1` and `slices=0`. When you use refinement with either
|
||||
// of these methods, it is always the "segment" based resampling described above. This is necessary because
|
||||
// sampling by length will ignore the repeated vertices and break the alignment.
|
||||
// .
|
||||
// It is possible to specify `method` and `refine` as arrays, but it is important to observe
|
||||
// matching rules when you do this. If a pair of profiles is connected using "tangent" or "distance"
|
||||
|
@ -139,12 +111,11 @@
|
|||
// Arguments:
|
||||
// profiles = list of 2d or 3d profiles to be skinned. (If 2d must also give `z`.)
|
||||
// slices = scalar or vector number of slices to insert between each pair of profiles. Set to zero to use only the profiles you provided. Recommend starting with a value around 10.
|
||||
// ---
|
||||
// refine = resample profiles to this number of points per edge. Can be a list to give a refinement for each profile. Recommend using a value above 10 when using the "distance" or "fast_distance" methods. Default: 1.
|
||||
// sampling = sampling method to use with "direct" and "reindex" methods. Can be "length" or "segment". Ignored if any profile pair uses either the "distance", "fast_distance", or "tangent" methods. Default: "length".
|
||||
// refine = resample profiles to this number of points per edge. Can be a list to give a refinement for each profile. Recommend using a value above 10 when using the "distance" method. Default: 1.
|
||||
// sampling = sampling method to use with "direct" and "reindex" methods. Can be "length" or "segment". Ignored if any profile pair uses either the "distance" or "tangent" methods. Default: "length".
|
||||
// closed = set to true to connect first and last profile (to make a torus). Default: false
|
||||
// caps = true to create endcap faces when closed is false. Can be a length 2 boolean array. Default is true if closed is false.
|
||||
// method = method for connecting profiles, one of "distance", "fast_distance", "tangent", "direct" or "reindex". Default: "direct".
|
||||
// method = method for connecting profiles, one of "distance", "tangent", "direct" or "reindex". Default: "direct".
|
||||
// z = array of height values for each profile if the profiles are 2d
|
||||
// convexity = convexity setting for use with polyhedron. (module only) Default: 10
|
||||
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
|
||||
|
@ -161,21 +132,21 @@
|
|||
// Example: Offsetting the starting edge connects to circles in an interesting way:
|
||||
// circ = circle($fn=80, r=3);
|
||||
// skin([circ, rot(110,p=circ)], z=[0,5], slices=20);
|
||||
// Example(FlatSpin,VPD=20):
|
||||
// Example(FlatSpin):
|
||||
// skin([ yrot(37,p=path3d(circle($fn=128, r=4))), path3d(square(3),3)], method="reindex",slices=10);
|
||||
// Example(FlatSpin,VPD=16): Ellipses connected with twist
|
||||
// Example(FlatSpin): Ellipses connected with twist
|
||||
// ellipse = xscale(2.5,p=circle($fn=80));
|
||||
// skin([ellipse, rot(45,p=ellipse)], z=[0,1.5], slices=10);
|
||||
// Example(FlatSpin,VPD=16): Ellipses connected without a twist. (Note ellipses stay in the same position: just the connecting edges are different.)
|
||||
// Example(FlatSpin): Ellipses connected without a twist. (Note ellipses stay in the same position: just the connecting edges are different.)
|
||||
// ellipse = xscale(2.5,p=circle($fn=80));
|
||||
// skin([ellipse, rot(45,p=ellipse)], z=[0,1.5], slices=10, method="reindex");
|
||||
// Example(FlatSpin,VPD=500):
|
||||
// Example(FlatSpin):
|
||||
// $fn=24;
|
||||
// skin([
|
||||
// yrot(0, p=yscale(2,p=path3d(circle(d=75)))),
|
||||
// [[40,0,100], [35,-15,100], [20,-30,100],[0,-40,100],[-40,0,100],[0,40,100],[20,30,100], [35,15,100]]
|
||||
// ],slices=10);
|
||||
// Example(FlatSpin,VPD=600):
|
||||
// Example(FlatSpin):
|
||||
// $fn=48;
|
||||
// skin([
|
||||
// for (b=[0,90]) [
|
||||
|
@ -229,14 +200,14 @@
|
|||
// skin([for (i=[0:layers-1]) zrot(-30*i,p=path3d(hexagon(ir=r),i*height/layers))],slices=0);
|
||||
// up(height/layers) cylinder(r=holeradius, h=height);
|
||||
// }
|
||||
// Example(FlatSpin,VPD=300): A box that is octagonal on the outside and circular on the inside
|
||||
// Example(FlatSpin): A box that is octagonal on the outside and circular on the inside
|
||||
// height = 45;
|
||||
// sub_base = octagon(d=71, rounding=2, $fn=128);
|
||||
// base = octagon(d=75, rounding=2, $fn=128);
|
||||
// interior = regular_ngon(n=len(base), d=60);
|
||||
// right_half()
|
||||
// skin([ sub_base, base, base, sub_base, interior], z=[0,2,height, height, 2], slices=0, refine=1, method="reindex");
|
||||
// Example: Connecting a pentagon and circle with the "tangent" method produces large triangular faces and cone shaped corners.
|
||||
// Example: Connecting a pentagon and circle with the "tangent" method produces triangular faces.
|
||||
// skin([pentagon(4), circle($fn=80,r=2)], z=[0,3], slices=10, method="tangent");
|
||||
// Example: rounding corners of a square. Note that `$fn` makes the number of points constant, and avoiding the `rounding=0` case keeps everything simple. In this case, the connections between profiles are linear, so there is no benefit to setting `slices` bigger than zero.
|
||||
// shapes = [for(i=[.01:.045:2])zrot(-i*180/2,cp=[-8,0,0],p=xrot(90,p=path3d(regular_ngon(n=4, side=4, rounding=i, $fn=64))))];
|
||||
|
@ -247,53 +218,53 @@
|
|||
// Example: You can fix it by specifying "tangent" for the first method, but you still need "direct" for the rest.
|
||||
// shapes = [for(i=[0:.2:1]) path3d(regular_ngon(n=4, side=4, rounding=i, $fn=32),i*5)];
|
||||
// skin(shapes, slices=0, method=concat(["tangent"],repeat("direct",len(shapes)-2)));
|
||||
// Example(FlatSpin,VPD=35): Connecting square to pentagon using "direct" method.
|
||||
// Example(FlatSpin): Connecting square to pentagon using "direct" method.
|
||||
// skin([regular_ngon(n=4, r=4), regular_ngon(n=5,r=5)], z=[0,4], refine=10, slices=10);
|
||||
// Example(FlatSpin,VPD=35): Connecting square to shifted pentagon using "direct" method.
|
||||
// Example(FlatSpin): Connecting square to shifted pentagon using "direct" method.
|
||||
// skin([regular_ngon(n=4, r=4), right(4,p=regular_ngon(n=5,r=5))], z=[0,4], refine=10, slices=10);
|
||||
// Example(FlatSpin,VPD=35): To improve the look, you can actually rotate the polygons for a more symmetric pattern of lines. You have to resample yourself before calling `align_polygon` and you should choose a length that is a multiple of both polygon lengths.
|
||||
// Example(FlatSpin): To improve the look, you can actually rotate the polygons for a more symmetric pattern of lines. You have to resample yourself before calling `align_polygon` and you should choose a length that is a multiple of both polygon lengths.
|
||||
// sq = subdivide_path(regular_ngon(n=4, r=4),40);
|
||||
// pent = subdivide_path(regular_ngon(n=5,r=5),40);
|
||||
// skin([sq, align_polygon(sq,pent,[0:1:360/5])], z=[0,4], slices=10);
|
||||
// Example(FlatSpin,VPD=35): For the shifted pentagon we can also align, making sure to pass an appropriate centerpoint to `align_polygon`.
|
||||
// Example(FlatSpin): For the shifted pentagon we can also align, making sure to pass an appropriate centerpoint to `align_polygon`.
|
||||
// sq = subdivide_path(regular_ngon(n=4, r=4),40);
|
||||
// pent = right(4,p=subdivide_path(regular_ngon(n=5,r=5),40));
|
||||
// skin([sq, align_polygon(sq,pent,[0:1:360/5],cp=[4,0])], z=[0,4], refine=10, slices=10);
|
||||
// Example(FlatSpin,VPD=35): The "distance" method is a completely different approach.
|
||||
// Example(FlatSpin): The "distance" method is a completely different approach.
|
||||
// skin([regular_ngon(n=4, r=4), regular_ngon(n=5,r=5)], z=[0,4], refine=10, slices=10, method="distance");
|
||||
// Example(FlatSpin,VPD=35,VPT=[0,0,4]): Connecting pentagon to heptagon inserts two triangular faces on each side
|
||||
// Example(FlatSpin): Connecting pentagon to heptagon inserts two triangular faces on each side
|
||||
// small = path3d(circle(r=3, $fn=5));
|
||||
// big = up(2,p=yrot( 0,p=path3d(circle(r=3, $fn=7), 6)));
|
||||
// skin([small,big],method="distance", slices=10, refine=10);
|
||||
// Example(FlatSpin,VPD=35,VPT=[0,0,4]): But just a slight rotation of the top profile moves the two triangles to one end
|
||||
// Example(FlatSpin): But just a slight rotation of the top profile moves the two triangles to one end
|
||||
// small = path3d(circle(r=3, $fn=5));
|
||||
// big = up(2,p=yrot(14,p=path3d(circle(r=3, $fn=7), 6)));
|
||||
// skin([small,big],method="distance", slices=10, refine=10);
|
||||
// Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Another "distance" example:
|
||||
// Example(FlatSpin): Another "distance" example:
|
||||
// off = [0,2];
|
||||
// shape = turtle(["right",45,"move", "left",45,"move", "left",45, "move", "jump", [.5+sqrt(2)/2,8]]);
|
||||
// rshape = rot(180,cp=centroid(shape)+off, p=shape);
|
||||
// skin([shape,rshape],z=[0,4], method="distance",slices=10,refine=15);
|
||||
// Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Slightly shifting the profile changes the optimal linkage
|
||||
// Example(FlatSpin): Slightly shifting the profile changes the optimal linkage
|
||||
// off = [0,1];
|
||||
// shape = turtle(["right",45,"move", "left",45,"move", "left",45, "move", "jump", [.5+sqrt(2)/2,8]]);
|
||||
// rshape = rot(180,cp=centroid(shape)+off, p=shape);
|
||||
// skin([shape,rshape],z=[0,4], method="distance",slices=10,refine=15);
|
||||
// Example(FlatSpin,VPD=444,VPT=[0,0,50]): This optimal solution doesn't look terrible:
|
||||
// Example(FlatSpin): This optimal solution doesn't look terrible:
|
||||
// prof1 = path3d([[-50,-50], [-50,50], [50,50], [25,25], [50,0], [25,-25], [50,-50]]);
|
||||
// prof2 = path3d(regular_ngon(n=7, r=50),100);
|
||||
// skin([prof1, prof2], method="distance", slices=10, refine=10);
|
||||
// Example(FlatSpin,VPD=444,VPT=[0,0,50]): But this one looks better. The "distance" method doesn't find it because it uses two more edges, so it clearly has a higher total edge distance. We force it by doubling the first two vertices of one of the profiles.
|
||||
// Example(FlatSpin): But this one looks better. The "distance" method doesn't find it because it uses two more edges, so it clearly has a higher total edge distance. We force it by doubling the first two vertices of one of the profiles.
|
||||
// prof1 = path3d([[-50,-50], [-50,50], [50,50], [25,25], [50,0], [25,-25], [50,-50]]);
|
||||
// prof2 = path3d(regular_ngon(n=7, r=50),100);
|
||||
// skin([repeat_entries(prof1,[2,2,1,1,1,1,1]),
|
||||
// prof2],
|
||||
// method="distance", slices=10, refine=10);
|
||||
// Example(FlatSpin,VPD=80,VPT=[0,0,7]): The "distance" method will often produces results similar to the "tangent" method if you use it with a polygon and a curve, but the results can also look like this:
|
||||
// Example(FlatSpin): The "distance" method will often produces results similar to the "tangent" method if you use it with a polygon and a curve, but the results can also look like this:
|
||||
// skin([path3d(circle($fn=128, r=10)), xrot(39, p=path3d(square([8,10]),10))], method="distance", slices=0);
|
||||
// Example(FlatSpin,VPD=80,VPT=[0,0,7]): Using the "tangent" method produces:
|
||||
// Example(FlatSpin): Using the "tangent" method produces:
|
||||
// skin([path3d(circle($fn=128, r=10)), xrot(39, p=path3d(square([8,10]),10))], method="tangent", slices=0);
|
||||
// Example(FlatSpin,VPD=74): Torus using hexagons and pentagons, where `closed=true`
|
||||
// Example(FlatSpin): Torus using hexagons and pentagons, where `closed=true`
|
||||
// hex = right(7,p=path3d(hexagon(r=3)));
|
||||
// pent = right(7,p=path3d(pentagon(r=3)));
|
||||
// N=5;
|
||||
|
@ -316,7 +287,7 @@
|
|||
// rot(17,p=regular_ngon(n=6, r=3)),
|
||||
// rot(37,p=regular_ngon(n=4, r=3))],
|
||||
// z=[0,2,4,6,9], method="distance", slices=10, refine=10);
|
||||
// Example(FlatSpin,VPD=935,VPT=[75,0,123]): Vertex count of the polygon changes at every profile
|
||||
// Example(FlatSpin): Vertex count of the polygon changes at every profile
|
||||
// skin([
|
||||
// for (ang = [0:10:90])
|
||||
// rot([0,ang,0], cp=[200,0,0], p=path3d(circle(d=100,$fn=12-(ang/10))))
|
||||
|
@ -395,7 +366,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
|
|||
assert(len(bad)==0, str("Profiles ",bad," are not a paths or have length less than 3"))
|
||||
let(
|
||||
profcount = len(profiles) - (closed?0:1),
|
||||
legal_methods = ["direct","reindex","distance","fast_distance","tangent"],
|
||||
legal_methods = ["direct","reindex","distance","tangent"],
|
||||
caps = is_def(caps) ? caps :
|
||||
closed ? false : true,
|
||||
capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])),
|
||||
|
@ -423,15 +394,13 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
|
|||
assert(methodlistok==[], str("method list contains invalid method at ",methodlistok))
|
||||
assert(len(method) == profcount,"Method list is the wrong length")
|
||||
assert(in_list(sampling,["length","segment"]), "sampling must be set to \"length\" or \"segment\"")
|
||||
assert(sampling=="segment" || (!in_list("distance",method) && !in_list("fast_distance",method) && !in_list("tangent",method)), "sampling is set to \"length\" which is only allowed with methods \"direct\" and \"reindex\"")
|
||||
assert(sampling=="segment" || (!in_list("distance",method) && !in_list("tangent",method)), "sampling is set to \"length\" which is only allowed iwith methods \"direct\" and \"reindex\"")
|
||||
assert(capsOK, "caps must be boolean or a list of two booleans")
|
||||
assert(!closed || !caps, "Cannot make closed shape with caps")
|
||||
let(
|
||||
profile_dim=array_dim(profiles,2),
|
||||
profiles_zcheck = (profile_dim != 2) || (profile_dim==2 && is_list(z) && len(z)==len(profiles)),
|
||||
profiles_ok = (profile_dim==2 && is_list(z) && len(z)==len(profiles)) || profile_dim==3
|
||||
)
|
||||
assert(profiles_zcheck, "z parameter is invalid or has the wrong length.")
|
||||
assert(profiles_ok,"Profiles must all be 3d or must all be 2d, with matching length z parameter.")
|
||||
assert(is_undef(z) || profile_dim==2, "Do not specify z with 3d profiles")
|
||||
assert(profile_dim==3 || len(z)==len(profiles),"Length of z does not match length of profiles.")
|
||||
|
@ -470,7 +439,6 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
|
|||
let(
|
||||
pair =
|
||||
method[i]=="distance" ? _skin_distance_match(profiles[i],select(profiles,i+1)) :
|
||||
method[i]=="fast_distance" ? _skin_aligned_distance_match(profiles[i], select(profiles,i+1)) :
|
||||
method[i]=="tangent" ? _skin_tangent_match(profiles[i],select(profiles,i+1)) :
|
||||
/*method[i]=="reindex" || method[i]=="direct" ?*/
|
||||
let( p1 = subdivide_path(profiles[i],max_list[i], method=sampling),
|
||||
|
@ -542,9 +510,8 @@ function _skin_core(profiles, caps) =
|
|||
|
||||
|
||||
// Function: subdivide_and_slice()
|
||||
// Topics: Paths, Path Subdivision
|
||||
// Usage:
|
||||
// newprof = subdivide_and_slice(profiles, slices, <numpoints>, <method>, <closed>);
|
||||
// subdivide_and_slice(profiles, slices, [numpoints], [method], [closed])
|
||||
// Description:
|
||||
// Subdivides the input profiles to have length `numpoints` where `numpoints` must be at least as
|
||||
// big as the largest input profile. By default `numpoints` is set equal to the length of the
|
||||
|
@ -570,41 +537,9 @@ function subdivide_and_slice(profiles, slices, numpoints, method="length", close
|
|||
slice_profiles(fixpoly, slices, closed);
|
||||
|
||||
|
||||
// Function: subdivide_long_segments()
|
||||
// Topics: Paths, Path Subdivision
|
||||
// See Also: subdivide_path(), subdivide_and_slice(), path_add_jitter(), jittered_poly()
|
||||
// Usage:
|
||||
// spath = subdivide_long_segments(path, maxlen, <closed=>);
|
||||
// Description:
|
||||
// Evenly subdivides long `path` segments until they are all shorter than `maxlen`.
|
||||
// Arguments:
|
||||
// path = The path to subdivide.
|
||||
// maxlen = The maximum allowed path segment length.
|
||||
// ---
|
||||
// closed = If true, treat path like a closed polygon. Default: true
|
||||
// Example:
|
||||
// path = pentagon(d=100);
|
||||
// spath = subdivide_long_segments(path, 10, closed=true);
|
||||
// stroke(path);
|
||||
// color("lightgreen") move_copies(path) circle(d=5,$fn=12);
|
||||
// color("blue") move_copies(spath) circle(d=3,$fn=12);
|
||||
function subdivide_long_segments(path, maxlen, closed=false) =
|
||||
assert(is_path(path))
|
||||
assert(is_finite(maxlen))
|
||||
assert(is_bool(closed))
|
||||
[
|
||||
for (p=pair(path,closed)) let(
|
||||
steps = ceil(norm(p[1]-p[0])/maxlen)
|
||||
) each lerp(p[0],p[1],[0:1/steps:1-EPSILON]),
|
||||
if (!closed) last(path)
|
||||
];
|
||||
|
||||
|
||||
|
||||
// Function: slice_profiles()
|
||||
// Topics: Paths, Path Subdivision
|
||||
// Usage:
|
||||
// profs = slice_profiles(profiles, slices, <closed>);
|
||||
// profs = slice_profiles(profiles,slices,<closed>);
|
||||
// Description:
|
||||
// Given an input list of profiles, linearly interpolate between each pair to produce a
|
||||
// more finely sampled list. The parameters `slices` specifies the number of slices to
|
||||
|
@ -730,18 +665,18 @@ function _dp_extract_map(map) =
|
|||
if (i==0 && j==0) each [smallmap,bigmap]];
|
||||
|
||||
|
||||
/// Internal Function: _skin_distance_match(poly1,poly2)
|
||||
/// Usage:
|
||||
/// polys = _skin_distance_match(poly1,poly2);
|
||||
/// Description:
|
||||
/// Find a way of associating the vertices of poly1 and vertices of poly2
|
||||
/// that minimizes the sum of the length of the edges that connect the two polygons.
|
||||
/// Polygons can be in 2d or 3d. The algorithm has cubic run time, so it can be
|
||||
/// slow if you pass large polygons. The output is a pair of polygons with vertices
|
||||
/// duplicated as appropriate to be used as input to `skin()`.
|
||||
/// Arguments:
|
||||
/// poly1 = first polygon to match
|
||||
/// poly2 = second polygon to match
|
||||
// Internal Function: _skin_distance_match(poly1,poly2)
|
||||
// Usage:
|
||||
// polys = _skin_distance_match(poly1,poly2);
|
||||
// Description:
|
||||
// Find a way of associating the vertices of poly1 and vertices of poly2
|
||||
// that minimizes the sum of the length of the edges that connect the two polygons.
|
||||
// Polygons can be in 2d or 3d. The algorithm has cubic run time, so it can be
|
||||
// slow if you pass large polygons. The output is a pair of polygons with vertices
|
||||
// duplicated as appropriate to be used as input to `skin()`.
|
||||
// Arguments:
|
||||
// poly1 = first polygon to match
|
||||
// poly2 = second polygon to match
|
||||
function _skin_distance_match(poly1,poly2) =
|
||||
let(
|
||||
swap = len(poly1)>len(poly2),
|
||||
|
@ -774,37 +709,21 @@ function _skin_distance_match(poly1,poly2) =
|
|||
newbig = polygon_shift(repeat_entries(map_poly[1],unique_count(bigmap)[1]),bigshift)
|
||||
)
|
||||
swap ? [newbig, newsmall] : [newsmall,newbig];
|
||||
|
||||
|
||||
// This function associates vertices but with the assumption that index 0 is associated between the
|
||||
// two inputs. This gives only quadratic run time. As above, output is pair of polygons with
|
||||
// vertices duplicated as suited to use as input to skin().
|
||||
|
||||
function _skin_aligned_distance_match(poly1, poly2) =
|
||||
let(
|
||||
result = _dp_distance_array(poly1, poly2, abort_thresh=1/0),
|
||||
map = _dp_extract_map(result[1]),
|
||||
shift0 = len(map[0]) - max(max_index(map[0],all=true))-1,
|
||||
shift1 = len(map[1]) - max(max_index(map[1],all=true))-1,
|
||||
new0 = polygon_shift(repeat_entries(poly1,unique_count(map[0])[1]),shift0),
|
||||
new1 = polygon_shift(repeat_entries(poly2,unique_count(map[1])[1]),shift1)
|
||||
)
|
||||
[new0,new1];
|
||||
|
||||
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Internal Function: _skin_tangent_match()
|
||||
/// Usage:
|
||||
/// x = _skin_tangent_match(poly1, poly2)
|
||||
/// Description:
|
||||
/// Finds a mapping of the vertices of the larger polygon onto the smaller one. Whichever input is the
|
||||
/// shorter path is the polygon, and the longer input is the curve. For every edge of the polygon, the algorithm seeks a plane that contains that
|
||||
/// edge and is tangent to the curve. There will be more than one such point. To choose one, the algorithm centers the polygon and curve on their centroids
|
||||
/// and chooses the closer tangent point. The algorithm works its way around the polygon, computing a series of tangent points and then maps all of the
|
||||
/// points on the curve between two tangent points into one vertex of the polygon. This algorithm can fail if the curve has too few points or if it is concave.
|
||||
/// Arguments:
|
||||
/// poly1 = input polygon
|
||||
/// poly2 = input polygon
|
||||
//
|
||||
// Internal Function: _skin_tangent_match()
|
||||
// Usage:
|
||||
// x = _skin_tangent_match(poly1, poly2)
|
||||
// Description:
|
||||
// Finds a mapping of the vertices of the larger polygon onto the smaller one. Whichever input is the
|
||||
// shorter path is the polygon, and the longer input is the curve. For every edge of the polygon, the algorithm seeks a plane that contains that
|
||||
// edge and is tangent to the curve. There will be more than one such point. To choose one, the algorithm centers the polygon and curve on their centroids
|
||||
// and chooses the closer tangent point. The algorithm works its way around the polygon, computing a series of tangent points and then maps all of the
|
||||
// points on the curve between two tangent points into one vertex of the polygon. This algorithm can fail if the curve has too few points or if it is concave.
|
||||
// Arguments:
|
||||
// poly1 = input polygon
|
||||
// poly2 = input polygon
|
||||
function _skin_tangent_match(poly1, poly2) =
|
||||
let(
|
||||
swap = len(poly1)>len(poly2),
|
||||
|
@ -838,7 +757,7 @@ function _find_one_tangent(curve, edge, curve_offset=[0,0,0], closed=true) =
|
|||
|
||||
// Function: associate_vertices()
|
||||
// Usage:
|
||||
// newpoly = associate_vertices(polygons, split);
|
||||
// associate_vertices(polygons, split)
|
||||
// Description:
|
||||
// Takes as input a list of polygons and duplicates specified vertices in each polygon in the list through the series so
|
||||
// that the input can be passed to `skin()`. This allows you to decide how the vertices are linked up rather than accepting
|
||||
|
@ -854,26 +773,26 @@ function _find_one_tangent(curve, edge, curve_offset=[0,0,0], closed=true) =
|
|||
// Arguments:
|
||||
// polygons = list of polygons to split
|
||||
// split = list of lists of split vertices
|
||||
// Example(FlatSpin,VPD=17,VPT=[0,0,2]): If you skin together a square and hexagon using the optimal distance method you get two triangular faces on opposite sides:
|
||||
// Example(FlatSpin): If you skin together a square and hexagon using the optimal distance method you get two triangular faces on opposite sides:
|
||||
// sq = regular_ngon(4,side=2);
|
||||
// hex = apply(rot(15),hexagon(side=2));
|
||||
// skin([sq,hex], slices=10, refine=10, method="distance", z=[0,4]);
|
||||
// Example(FlatSpin,VPD=17,VPT=[0,0,2]): Using associate_vertices you can change the location of the triangular faces. Here they are connect to two adjacent vertices of the square:
|
||||
// Example(FlatSpin): Using associate_vertices you can change the location of the triangular faces. Here they are connect to two adjacent vertices of the square:
|
||||
// sq = regular_ngon(4,side=2);
|
||||
// hex = apply(rot(15),hexagon(side=2));
|
||||
// skin(associate_vertices([sq,hex],[[1,2]]), slices=10, refine=10, sampling="segment", z=[0,4]);
|
||||
// Example(FlatSpin,VPD=17,VPT=[0,0,2]): Here the two triangular faces connect to a single vertex on the square. Note that we had to rotate the hexagon to line them up because the vertices match counting forward, so in this case vertex 0 of the square matches to vertices 0, 1, and 2 of the hexagon.
|
||||
// Example(FlatSpin): Here the two triangular faces connect to a single vertex on the square. Note that we had to rotate the hexagon to line them up because the vertices match counting forward, so in this case vertex 0 of the square matches to vertices 0, 1, and 2 of the hexagon.
|
||||
// sq = regular_ngon(4,side=2);
|
||||
// hex = apply(rot(60),hexagon(side=2));
|
||||
// skin(associate_vertices([sq,hex],[[0,0]]), slices=10, refine=10, sampling="segment", z=[0,4]);
|
||||
// Example(3D): This example shows several polygons, with only a single vertex split at each step:
|
||||
// Example: This example shows several polygons, with only a single vertex split at each step:
|
||||
// sq = regular_ngon(4,side=2);
|
||||
// pent = pentagon(side=2);
|
||||
// hex = hexagon(side=2);
|
||||
// sep = regular_ngon(7,side=2);
|
||||
// profiles = associate_vertices([sq,pent,hex,sep], [1,3,4]);
|
||||
// skin(profiles ,slices=10, refine=10, method="distance", z=[0,2,4,6]);
|
||||
// Example(3D): The polygons cannot shrink, so if you want to have decreasing polygons you'll need to concatenate multiple results. Note that it is perfectly ok to duplicate a profile as shown here, where the pentagon is duplicated:
|
||||
// Example: The polygons cannot shrink, so if you want to have decreasing polygons you'll need to concatenate multiple results. Note that it is perfectly ok to duplicate a profile as shown here, where the pentagon is duplicated:
|
||||
// sq = regular_ngon(4,side=2);
|
||||
// pent = pentagon(side=2);
|
||||
// grow = associate_vertices([sq,pent], [1]);
|
||||
|
@ -883,7 +802,8 @@ function associate_vertices(polygons, split, curpoly=0) =
|
|||
curpoly==len(polygons)-1 ? polygons :
|
||||
let(
|
||||
polylen = len(polygons[curpoly]),
|
||||
cursplit = force_list(split[curpoly])
|
||||
cursplit = force_list(split[curpoly]),
|
||||
fdsa= echo(cursplit=cursplit)
|
||||
)
|
||||
assert(len(split)==len(polygons)-1,str(split,"Split list length mismatch: it has length ", len(split)," but must have length ",len(polygons)-1))
|
||||
assert(polylen<=len(polygons[curpoly+1]),str("Polygon ",curpoly," has more vertices than the next one."))
|
||||
|
@ -903,7 +823,7 @@ function associate_vertices(polygons, split, curpoly=0) =
|
|||
|
||||
// Function&Module: sweep()
|
||||
// Usage: As Module
|
||||
// sweep(shape, transforms, <closed>, <caps>, <convexity=>, <anchor=>, <spin=>, <orient=>, <extent=>) <attachments>;
|
||||
// sweep(shape, transforms, <closed>, <caps>)
|
||||
// Usage: As Function
|
||||
// vnf = sweep(shape, transforms, <closed>, <caps>);
|
||||
// Description:
|
||||
|
@ -925,7 +845,6 @@ function associate_vertices(polygons, split, curpoly=0) =
|
|||
// transforms = list of 4x4 matrices to apply
|
||||
// closed = set to true to form a closed (torus) model. Default: false
|
||||
// caps = true to create endcap faces when closed is false. Can be a singe boolean to specify endcaps at both ends, or a length 2 boolean array. Default is true if closed is false.
|
||||
// ---
|
||||
// convexity = convexity setting for use with polyhedron. (module only) Default: 10
|
||||
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
|
||||
// spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0
|
||||
|
@ -995,11 +914,10 @@ module sweep(shape, transforms, closed=false, caps, convexity=10,
|
|||
|
||||
|
||||
// Function&Module: path_sweep()
|
||||
// Usage: As module
|
||||
// path_sweep(shape, path, <method>, <normal=>, <closed=>, <twist=>, <twist_by_length=>, <symmetry=>, <last_normal=>, <tangent=>, <relaxed=>, <caps=>, <convexity=>, <transforms=>, <anchor=>, <cp=>, <spin=>, <orient=>, <extent=>) <attachments>;
|
||||
// vnf = path_sweep(shape, path, <method>, <normal=>, <closed=>, <twist=>, <twist_by_length=>, <symmetry=>, <last_normal=>, <tangent=>, <relaxed=>, <caps=>, <convexity=>, <transforms=>);
|
||||
// Usage:
|
||||
// path_sweep(shape, path, [method], [normal], [closed], [twist], [twist_by_length], [symmetry], [last_normal], [tangent], [relaxed], [caps], [convexity], [transforms])
|
||||
// Description:
|
||||
// Takes as input a 2D polygon path, and a 2d or 3d path and constructs a polyhedron by sweeping the shape along the path.
|
||||
// Takes as input a 2D polygon path or region, and a 2d or 3d path and constructs a polyhedron by sweeping the shape along the path.
|
||||
// When run as a module returns the polyhedron geometry. When run as a function returns a VNF by default or if you set `transforms=true`
|
||||
// then it returns a list of transformations suitable as input to `sweep`.
|
||||
// .
|
||||
|
@ -1049,7 +967,6 @@ module sweep(shape, transforms, closed=false, caps, convexity=10,
|
|||
// shape = A 2D polygon path or region describing the shape to be swept.
|
||||
// path = 2D or 3D path giving the path to sweep over
|
||||
// method = one of "incremental", "natural" or "manual". Default: "incremental"
|
||||
// ---
|
||||
// normal = normal vector for initializing the incremental method, or for setting normals with method="manual". Default: UP if the path makes an angle lower than 45 degrees to the xy plane, BACK otherwise.
|
||||
// closed = path is a closed loop. Default: false
|
||||
// twist = amount of twist to add in degrees. For closed sweeps must be a multiple of 360/symmetry. Default: 0
|
||||
|
@ -1249,7 +1166,7 @@ module sweep(shape, transforms, closed=false, caps, convexity=10,
|
|||
// points = 50; // points per loop
|
||||
// R = 400; r = 150; // Torus size
|
||||
// p = 2; q = 5; // Knot parameters
|
||||
// %torus(r_maj=R,r_min=r);
|
||||
// %torus(r=R,r2=r);
|
||||
// k = max(p,q) / gcd(p,q) * points;
|
||||
// knot_path = [ for (i=[0:k-1]) knot(360*i/k/gcd(p,q),R,r,p,q) ];
|
||||
// path_sweep(rot(90,p=ushape),knot_path, method="natural", closed=true);
|
||||
|
@ -1266,7 +1183,7 @@ module sweep(shape, transforms, closed=false, caps, convexity=10,
|
|||
// points = 50; // points per loop
|
||||
// R = 400; r = 150; // Torus size
|
||||
// p = 2; q = 5; // Knot parameters
|
||||
// %torus(r_maj=R,r_min=r);
|
||||
// %torus(r=R,r2=r);
|
||||
// k = max(p,q) / gcd(p,q) * points;
|
||||
// knot_path = [ for (i=[0:k-1]) knot(360*i/k/gcd(p,q),R,r,p,q) ];
|
||||
// normals = [ for (i=[0:k-1]) knot_normal(360*i/k/gcd(p,q),R,r,p,q) ];
|
||||
|
@ -1278,18 +1195,6 @@ module sweep(shape, transforms, closed=false, caps, convexity=10,
|
|||
// outside = [for(i=[0:len(trans)-1]) trans[i]*scale(lerp(1,1.5,i/(len(trans)-1)))];
|
||||
// inside = [for(i=[len(trans)-1:-1:0]) trans[i]*scale(lerp(1.1,1.4,i/(len(trans)-1)))];
|
||||
// sweep(shape, concat(outside,inside),closed=true);
|
||||
// Example: Using path_sweep on a region
|
||||
// rgn1 = [for (d=[10:10:60]) circle(d=d,$fn=8)];
|
||||
// rgn2 = [square(30,center=false)];
|
||||
// rgn3 = [for (size=[10:10:20]) move([15,15],p=square(size=size, center=true))];
|
||||
// mrgn = union(rgn1,rgn2);
|
||||
// orgn = difference(mrgn,rgn3);
|
||||
// path_sweep(orgn,arc(r=40,angle=180));
|
||||
// Example: A region with a twist
|
||||
// region = [for(i=pentagon(5)) move(i,p=circle(r=2,$fn=25))];
|
||||
// path_sweep(region,
|
||||
// circle(r=16,$fn=75),closed=true,
|
||||
// twist=360/5*2,symmetry=5);
|
||||
module path_sweep(shape, path, method="incremental", normal, closed=false, twist=0, twist_by_length=true,
|
||||
symmetry=1, last_normal, tangent, relaxed=false, caps, convexity=10,
|
||||
anchor="origin",cp,spin=0, orient=UP, extent=false)
|
||||
|
@ -1309,7 +1214,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
assert(!closed || twist % (360/symmetry)==0, str("For a closed sweep, twist must be a multiple of 360/symmetry = ",360/symmetry))
|
||||
assert(closed || symmetry==1, "symmetry must be 1 when closed is false")
|
||||
assert(is_integer(symmetry) && symmetry>0, "symmetry must be a positive integer")
|
||||
// let(shape = check_and_fix_path(shape,valid_dim=2,closed=true,name="shape"))
|
||||
assert(is_path(shape,2) || is_region(shape), "shape must be a 2d path or region.")
|
||||
assert(is_path(path), "input path is not a path")
|
||||
assert(!closed || !approx(path[0],select(path,-1)), "Closed path includes start point at the end")
|
||||
let(
|
||||
|
@ -1336,7 +1241,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
let(rotations =
|
||||
[for( i = 0,
|
||||
ynormal = normal - (normal * tangents[0])*tangents[0],
|
||||
rotation = affine3d_frame_map(y=ynormal, z=tangents[0])
|
||||
rotation = affine_frame_map(y=ynormal, z=tangents[0])
|
||||
;
|
||||
i < len(tangents) + (closed?1:0) ;
|
||||
rotation = i<len(tangents)-1+(closed?1:0)? rot(from=tangents[i],to=tangents[(i+1)%L])*rotation : undef,
|
||||
|
@ -1355,7 +1260,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
last_tangent = select(tangents,-1),
|
||||
lastynormal = last_normal - (last_normal * last_tangent) * last_tangent
|
||||
)
|
||||
affine3d_frame_map(y=lastynormal, z=last_tangent),
|
||||
affine_frame_map(y=lastynormal, z=last_tangent),
|
||||
mismatch = transpose(select(rotations,-1)) * reference_rot,
|
||||
correction_twist = atan2(mismatch[1][0], mismatch[0][0]),
|
||||
// Spread out this extra twist over the whole sweep so that it doesn't occur
|
||||
|
@ -1368,7 +1273,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
[for(i=[0:L-(closed?0:1)]) let(
|
||||
ynormal = relaxed ? normals[i%L] : normals[i%L] - (normals[i%L] * tangents[i%L])*tangents[i%L],
|
||||
znormal = relaxed ? tangents[i%L] - (normals[i%L] * tangents[i%L])*normals[i%L] : tangents[i%L],
|
||||
rotation = affine3d_frame_map(y=ynormal, z=znormal)
|
||||
rotation = affine_frame_map(y=ynormal, z=znormal)
|
||||
)
|
||||
assert(approx(ynormal*znormal,0),str("Supplied normal is parallel to the path tangent at point ",i))
|
||||
translate(path[i%L])*rotation*zrot(-twist*pathfrac[i]),
|
||||
|
@ -1380,27 +1285,25 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
dummy = min(testnormals) < .5 ? echo("WARNING: ***** Abrupt change in normal direction. Consider a different method *****") :0
|
||||
)
|
||||
[for(i=[0:L-(closed?0:1)]) let(
|
||||
rotation = affine3d_frame_map(x=pathnormal[i%L], z=tangents[i%L])
|
||||
rotation = affine_frame_map(x=pathnormal[i%L], z=tangents[i%L])
|
||||
)
|
||||
translate(path[i%L])*rotation*zrot(-twist*pathfrac[i])
|
||||
] :
|
||||
assert(false,"Unknown method or no method given")[], // unknown method
|
||||
ends_match = !closed ? true
|
||||
: let( rshape = is_path(shape) ? [path3d(shape)]
|
||||
: [for(s=shape) path3d(s)]
|
||||
ends_match = !closed ? true :
|
||||
let(
|
||||
start = apply(transform_list[0],path3d(shape)),
|
||||
end = reindex_polygon(start, apply(transform_list[L],path3d(shape)))
|
||||
)
|
||||
regions_equal(apply(transform_list[0], rshape),
|
||||
apply(transform_list[L], rshape)),
|
||||
all([for(i=idx(start)) approx(start[i],end[i])]),
|
||||
dummy = ends_match ? 0 : echo("WARNING: ***** The points do not match when closing the model *****")
|
||||
)
|
||||
transforms ? transform_list : sweep(is_path(shape)?clockwise_polygon(shape):shape, transform_list, closed=false, caps=fullcaps);
|
||||
transforms ? transform_list : sweep(clockwise_polygon(shape), transform_list, closed=false, caps=fullcaps);
|
||||
|
||||
|
||||
// Function&Module: path_sweep2d()
|
||||
// Usage: as module
|
||||
// path_sweep2d(shape, path, <closed>, <caps>, <quality>, <convexity=>, <anchor=>, <spin=>, <orient=>, <extent=>, <cp=>) <attachments>;
|
||||
// Usage: as function
|
||||
// vnf = path_sweep2d(shape, path, <closed>, <caps>, <quality>);
|
||||
// Usage:
|
||||
// path_sweep2d(shape, path, <closed>, <quality>)
|
||||
// Description:
|
||||
// Takes an input 2D polygon (the shape) and a 2d path and constructs a polyhedron by sweeping the shape along the path.
|
||||
// When run as a module returns the polyhedron geometry. When run as a function returns a VNF.
|
||||
|
@ -1417,7 +1320,6 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
// closed = path is a closed loop. Default: false
|
||||
// caps = true to create endcap faces when closed is false. Can be a length 2 boolean array. Default is true if closed is false.
|
||||
// quality = quality of offset used in calculation. Default: 1
|
||||
// ---
|
||||
// convexity = convexity parameter for polyhedron (module only) Default: 10
|
||||
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
|
||||
// spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0
|
||||
|
@ -1445,8 +1347,7 @@ function path_sweep2d(shape, path, closed=false, caps, quality=1) =
|
|||
caps = is_def(caps) ? caps
|
||||
: closed ? false : true,
|
||||
capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])),
|
||||
fullcaps = is_bool(caps) ? [caps,caps] : caps,
|
||||
shape = check_and_fix_path(shape,valid_dim=2,closed=true,name="shape")
|
||||
fullcaps = is_bool(caps) ? [caps,caps] : caps
|
||||
)
|
||||
assert(capsOK, "caps must be boolean or a list of two booleans")
|
||||
assert(!closed || !caps, "Cannot make closed shape with caps")
|
||||
|
|
29
tests/test_all.scad
Normal file
29
tests/test_all.scad
Normal file
|
@ -0,0 +1,29 @@
|
|||
|
||||
include<test_affine.scad>
|
||||
include<test_arrays.scad>
|
||||
include<test_common.scad>
|
||||
include<test_coords.scad>
|
||||
include<test_cubetruss.scad>
|
||||
include<test_debug.scad>
|
||||
include<test_edges.scad>
|
||||
include<test_geometry.scad>
|
||||
include<test_hull.scad>
|
||||
include<test_linear_bearings.scad>
|
||||
include<test_math.scad>
|
||||
include<test_mutators.scad>
|
||||
include<test_primitives.scad>
|
||||
include<test_quaternions.scad>
|
||||
include<test_queues.scad>
|
||||
include<test_shapes.scad>
|
||||
include<test_shapes2d.scad>
|
||||
include<test_skin.scad>
|
||||
include<test_stacks.scad>
|
||||
include<test_strings.scad>
|
||||
include<test_structs.scad>
|
||||
include<test_torx_drive.scad>
|
||||
include<test_transforms.scad>
|
||||
include<test_vectors.scad>
|
||||
include<test_version.scad>
|
||||
include<test_vnf.scad>
|
||||
|
||||
|
|
@ -221,10 +221,13 @@ module test_is_consistent() {
|
|||
assert(is_consistent([]));
|
||||
assert(is_consistent([[],[]]));
|
||||
assert(is_consistent([3,4,5]));
|
||||
assert(is_consistent([3,4,5], 1));
|
||||
assert(is_consistent([[3,4],[4,5],[6,7]]));
|
||||
assert(is_consistent([[[3],4],[[4],5]]));
|
||||
assert(!is_consistent(5));
|
||||
assert(!is_consistent(undef));
|
||||
assert(!is_consistent([3,4,undef], 0));
|
||||
assert(!is_consistent([[3,4,4],[1,2,3], [0,4,5]], [1,1]));
|
||||
assert(!is_consistent([[3,4,5],[3,4]]));
|
||||
assert(is_consistent([[3,[3,4,[5]]], [5,[2,9,[9]]]]));
|
||||
assert(!is_consistent([[3,[3,4,[5]]], [5,[2,9,9]]]));
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
// is_vector([1,1,1],all_nonzero=false); // Returns true
|
||||
// is_vector([],zero=false); // Returns false
|
||||
function is_vector(v, length, zero, all_nonzero=false, eps=EPSILON) =
|
||||
is_list(v) && len(v)>0 && []==[for(vi=v) if(!is_num(vi)) 0]
|
||||
is_list(v) && len(v)>0 && 0*v==[for(vi=v) 0]
|
||||
&& (is_undef(length) || len(v)==length)
|
||||
&& (is_undef(zero) || ((norm(v) >= eps) == !zero))
|
||||
&& (!all_nonzero || all_nonzero(v)) ;
|
||||
|
@ -128,8 +128,11 @@ function vceil(v) =
|
|||
// unit([0,0,0]); // Asserts an error.
|
||||
function unit(v, error=[[["ASSERT"]]]) =
|
||||
assert(is_vector(v), str("Expected a vector. Got: ",v))
|
||||
norm(v)<EPSILON? (error==[[["ASSERT"]]]? assert(norm(v)>=EPSILON,"Tried to normalize a zero vector") : error) :
|
||||
v/norm(v);
|
||||
norm(v)<EPSILON
|
||||
? error==[[["ASSERT"]]]
|
||||
? assert(norm(v)>=EPSILON,"Tried to normalize a zero vector")
|
||||
: error
|
||||
: v/norm(v);
|
||||
|
||||
|
||||
// Function: vector_angle()
|
||||
|
|
26
vnf.scad
26
vnf.scad
|
@ -766,6 +766,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
|
|||
sface2 = list_rotate(face2,min2)
|
||||
) if (sface1 == sface2)
|
||||
_vnf_validate_err("DUP_FACE", [for (i=sface1) varr[i]])
|
||||
|
||||
],
|
||||
issues = concat(issues, repeated_faces)
|
||||
) issues? issues :
|
||||
|
@ -895,6 +896,7 @@ function _vnf_validate_err(name, extra) =
|
|||
) concat(info, [extra]);
|
||||
|
||||
|
||||
|
||||
function _pts_not_reported(pts, varr, reports) =
|
||||
[
|
||||
for (i = pts, report = reports, pt = report[3])
|
||||
|
@ -941,16 +943,11 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false) {
|
|||
}
|
||||
|
||||
|
||||
|
||||
// Section: VNF Transformations
|
||||
|
||||
// Function: vnf_halfspace()
|
||||
// Usage:
|
||||
// vnf_halfspace([a,b,c,d], vnf)
|
||||
// Description:
|
||||
// returns the intersection of the VNF with the given half-space.
|
||||
// Arguments:
|
||||
// halfspace = half-space to intersect with, given as the four coefficients of the affine inequation a\*x+b\*y+c\*z≥ d.
|
||||
|
||||
/// Internal functions
|
||||
function _vnf_halfspace_pts(halfspace, points, faces,
|
||||
inside=undef, coords=[], map=[]) =
|
||||
/* Recursive function to compute the intersection of points (and edges,
|
||||
|
@ -991,6 +988,8 @@ function _vnf_halfspace_pts(halfspace, points, faces,
|
|||
(zi*points[j]-zj*pi)/(zi-zj)]),
|
||||
// map: we add the info
|
||||
concat(map, [[for(y=enumerate(adj2)) [y[1], n+y[0]]]]));
|
||||
|
||||
|
||||
function _vnf_halfspace_face(face, map, inside, i=0,
|
||||
newface=[], newedge=[], exit) =
|
||||
/* Recursive function to intersect a face of the VNF with the half-plane.
|
||||
|
@ -1029,6 +1028,8 @@ function _vnf_halfspace_face(face, map, inside, i=0,
|
|||
concat(newface0, [inter]),
|
||||
concat(newedge, [inter]),
|
||||
is_undef(exit) ? inside[p] : exit);
|
||||
|
||||
|
||||
function _vnf_halfspace_path_search_edge(edge, paths, i=0, ret=[undef,undef]) =
|
||||
/* given an oriented edge [x,y] and a set of oriented paths,
|
||||
* returns the indices [i,j] of paths [before, after] given edge
|
||||
|
@ -1038,6 +1039,8 @@ function _vnf_halfspace_path_search_edge(edge, paths, i=0, ret=[undef,undef]) =
|
|||
_vnf_halfspace_path_search_edge(edge, paths, i+1,
|
||||
[last(paths[i]) == edge[0] ? i : ret[0],
|
||||
paths[i][0] == edge[1] ? i : ret[1]]);
|
||||
|
||||
|
||||
function _vnf_halfspace_paths(edges, i=0, paths=[]) =
|
||||
/* given a set of oriented edges [x,y],
|
||||
returns all paths [x,y,z,..] that may be formed from these edges.
|
||||
|
@ -1060,6 +1063,15 @@ function _vnf_halfspace_paths(edges, i=0, paths=[]) =
|
|||
s[0] != s[1] ? [concat(paths[s[0]], paths[s[1]])] :
|
||||
// edge closes a loop
|
||||
[concat(paths[s[0]], [e[1]])]));
|
||||
|
||||
|
||||
// Function: vnf_halfspace()
|
||||
// Usage:
|
||||
// vnf_halfspace([a,b,c,d], vnf)
|
||||
// Description:
|
||||
// returns the intersection of the VNF with the given half-space.
|
||||
// Arguments:
|
||||
// halfspace = half-space to intersect with, given as the four coefficients of the affine inequation a\*x+b\*y+c\*z≥ d.
|
||||
function vnf_halfspace(_arg1=_undef, _arg2=_undef,
|
||||
halfspace=_undef, vnf=_undef) =
|
||||
// here is where we wish that OpenSCAD had array lvalues...
|
||||
|
|
Loading…
Reference in a new issue