mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2025-01-01 09:49:45 +00:00
Merge pull request #467 from adrianVmariano/master
fast_distance method for skin, regions for path_sweep & bug fixes
This commit is contained in:
commit
9c310d9c17
7 changed files with 320 additions and 78 deletions
13
arrays.scad
13
arrays.scad
|
@ -1682,4 +1682,17 @@ function transpose(arr, reverse=false) =
|
|||
arr;
|
||||
|
||||
|
||||
// Function: is_matrix_symmetric()
|
||||
// Usage:
|
||||
// b = is_matrix_symmetric(A,<eps>)
|
||||
// Description:
|
||||
// Returns true if the input matrix is symmetric, meaning it equals its transpose.
|
||||
// Matrix should have numerical entries.
|
||||
// Arguments:
|
||||
// A = matrix to test
|
||||
// eps = epsilon for comparing equality. Default: 1e-12
|
||||
function is_matrix_symmetric(A,eps=1e-12) =
|
||||
approx(A,transpose(A));
|
||||
|
||||
|
||||
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
||||
|
|
134
math.scad
134
math.scad
|
@ -585,11 +585,11 @@ function lcm(a,b=[]) =
|
|||
function sum(v, dflt=0) =
|
||||
v==[]? dflt :
|
||||
assert(is_consistent(v), "Input to sum is non-numeric or inconsistent")
|
||||
is_vector(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: cumsum()
|
||||
// Usage:
|
||||
// sums = cumsum(v);
|
||||
|
@ -1465,35 +1465,113 @@ function deriv3(data, h=1, closed=false) =
|
|||
|
||||
// Section: Complex Numbers
|
||||
|
||||
// Function: C_times()
|
||||
|
||||
// Function: complex()
|
||||
// Usage:
|
||||
// c = C_times(z1,z2)
|
||||
// z = complex(list)
|
||||
// Description:
|
||||
// Multiplies two complex numbers represented by 2D vectors.
|
||||
// Returns a complex number as a 2D vector [REAL, IMAGINARY].
|
||||
// Converts a real valued number, vector or matrix into its complex analog
|
||||
// by replacing all entries with a 2-vector that has zero imaginary part.
|
||||
function complex(list) =
|
||||
is_num(list) ? [list,0] :
|
||||
[for(entry=list) is_num(entry) ? [entry,0] : complex(entry)];
|
||||
|
||||
|
||||
// Function: c_mul()
|
||||
// Usage:
|
||||
// c = c_mul(z1,z2)
|
||||
// Description:
|
||||
// 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.
|
||||
// Arguments:
|
||||
// z1 = First complex number, given as a 2D vector [REAL, IMAGINARY]
|
||||
// z2 = Second complex number, given as a 2D vector [REAL, IMAGINARY]
|
||||
function C_times(z1,z2) =
|
||||
assert( is_matrix([z1,z2],2,2), "Complex numbers should be represented by 2D vectors" )
|
||||
// z1 = First complex number, vector or matrix
|
||||
// z2 = Second complex number, vector or matrix
|
||||
|
||||
function _split_complex(data) =
|
||||
is_vector(data,2) ? data
|
||||
: is_num(data[0][0]) ? [data*[1,0], data*[0,1]]
|
||||
: [
|
||||
[for(vec=data) vec * [1,0]],
|
||||
[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]]]
|
||||
: [for(i=[0:1:len(data[0])-1])
|
||||
[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_div()
|
||||
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:
|
||||
// x = C_div(z1,z2)
|
||||
// x = c_div(z1,z2)
|
||||
// Description:
|
||||
// Divides two complex numbers represented by 2D vectors.
|
||||
// Returns a complex number as a 2D vector [REAL, IMAGINARY].
|
||||
// Arguments:
|
||||
// z1 = First complex number, given as a 2D vector [REAL, IMAGINARY]
|
||||
// z2 = Second complex number, given as a 2D vector [REAL, IMAGINARY]
|
||||
function C_div(z1,z2) =
|
||||
function c_div(z1,z2) =
|
||||
assert( is_vector(z1,2) && is_vector(z2), "Complex numbers should be represented by 2D vectors." )
|
||||
assert( !approx(z2,0), "The divisor `z2` cannot be zero." )
|
||||
let(den = z2.x*z2.x + z2.y*z2.y)
|
||||
[(z1.x*z2.x + z1.y*z2.y)/den, (z1.y*z2.x - z1.x*z2.y)/den];
|
||||
|
||||
// For the sake of consistence with Q_mul and vmul, C_times should be called C_mul
|
||||
|
||||
// Function: c_conj()
|
||||
// Usage:
|
||||
// w = c_conj(z)
|
||||
// Description:
|
||||
// Computes the complex conjugate of the input, which can be a complex number,
|
||||
// complex vector or complex matrix.
|
||||
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)
|
||||
// Description:
|
||||
// Returns real part of a complex number, vector or matrix.
|
||||
function c_real(z) =
|
||||
is_vector(z,2) ? z.x
|
||||
: is_num(z[0][0]) ? z*[1,0]
|
||||
: [for(vec=z) vec * [1,0]];
|
||||
|
||||
// Function: c_imag()
|
||||
// Usage:
|
||||
// x = c_imag(z)
|
||||
// Description:
|
||||
// Returns imaginary part of a complex number, vector or matrix.
|
||||
function c_imag(z) =
|
||||
is_vector(z,2) ? z.y
|
||||
: is_num(z[0][0]) ? z*[0,1]
|
||||
: [for(vec=z) vec * [0,1]];
|
||||
|
||||
|
||||
// Function: c_ident()
|
||||
// Usage:
|
||||
// I = c_ident(n)
|
||||
// Description:
|
||||
// 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)
|
||||
// Description:
|
||||
// Compute the norm of a complex number or vector.
|
||||
function c_norm(z) = norm_fro(z);
|
||||
|
||||
|
||||
// Section: Polynomials
|
||||
|
||||
|
@ -1539,12 +1617,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_times(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:
|
||||
|
@ -1554,12 +1632,12 @@ 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));
|
||||
|
@ -1680,10 +1758,10 @@ function _poly_roots(p, pderiv, s, z, tol, i=0) =
|
|||
svals = [for(zk=z) tol*polynomial(s,norm(zk))],
|
||||
p_of_z = [for(zk=z) polynomial(p,zk)],
|
||||
done = [for(k=[0:n-1]) norm(p_of_z[k])<=svals[k]],
|
||||
newton = [for(k=[0:n-1]) C_div(p_of_z[k], polynomial(pderiv,z[k]))],
|
||||
zdiff = [for(k=[0:n-1]) sum([for(j=[0:n-1]) if (j!=k) C_div([1,0], z[k]-z[j])])],
|
||||
w = [for(k=[0:n-1]) done[k] ? [0,0] : C_div( newton[k],
|
||||
[1,0] - C_times(newton[k], zdiff[k]))]
|
||||
newton = [for(k=[0:n-1]) c_div(p_of_z[k], polynomial(pderiv,z[k]))],
|
||||
zdiff = [for(k=[0:n-1]) sum([for(j=[0:n-1]) if (j!=k) c_div([1,0], z[k]-z[j])])],
|
||||
w = [for(k=[0:n-1]) done[k] ? [0,0] : c_div( newton[k],
|
||||
[1,0] - c_mul(newton[k], zdiff[k]))]
|
||||
)
|
||||
all(done) ? z : _poly_roots(p,pderiv,s,z-w,tol,i+1);
|
||||
|
||||
|
|
93
regions.scad
93
regions.scad
|
@ -58,35 +58,34 @@ module region(r)
|
|||
|
||||
// Function: check_and_fix_path()
|
||||
// Usage:
|
||||
// check_and_fix_path(path, [valid_dim], [closed])
|
||||
// check_and_fix_path(path, [valid_dim], [closed], [name])
|
||||
// Description:
|
||||
// Checks that the input is a path. If it is a region with one component, converts it to a path.
|
||||
// Note that arbitrary paths must have at least two points, but closed paths need at least 3 points.
|
||||
// valid_dim specfies the allowed dimension of the points in the path.
|
||||
// If the path is closed, removed duplicate endpoint if present.
|
||||
// If the path is closed, removes duplicate endpoint if present.
|
||||
// Arguments:
|
||||
// path = path to process
|
||||
// valid_dim = list of allowed dimensions for the points in the path, e.g. [2,3] to require 2 or 3 dimensional input. If left undefined do not perform this check. Default: undef
|
||||
// closed = set to true if the path is closed, which enables a check for endpoint duplication
|
||||
function check_and_fix_path(path, valid_dim=undef, closed=false) =
|
||||
// name = parameter name to use for reporting errors. Default: "path"
|
||||
function check_and_fix_path(path, valid_dim=undef, closed=false, name="path") =
|
||||
let(
|
||||
path = is_region(path)? (
|
||||
assert(len(path)==1,"Region supplied as path does not have exactly one component")
|
||||
path[0]
|
||||
) : (
|
||||
assert(is_path(path), "Input is not a path")
|
||||
path
|
||||
),
|
||||
dim = array_dim(path)
|
||||
path =
|
||||
is_region(path)?
|
||||
assert(len(path)==1,str("Region ",name," supplied as path does not have exactly one component"))
|
||||
path[0]
|
||||
:
|
||||
assert(is_path(path), str("Input ",name," is not a path"))
|
||||
path
|
||||
)
|
||||
assert(dim[0]>1,"Path must have at least 2 points")
|
||||
assert(len(dim)==2,"Invalid path: path is either a list of scalars or a list of matrices")
|
||||
assert(is_def(dim[1]), "Invalid path: entries in the path have variable length")
|
||||
let(valid=is_undef(valid_dim) || in_list(dim[1],valid_dim))
|
||||
assert(len(path)>(closed?2:1),closed?str("Closed path ",name," must have at least 3 points")
|
||||
:str("Path ",name," must have at least 2 points"))
|
||||
let(valid=is_undef(valid_dim) || in_list(len(path[0]),force_list(valid_dim)))
|
||||
assert(
|
||||
valid, str(
|
||||
"The points on the path have length ",
|
||||
dim[1], " but length must be ",
|
||||
len(valid_dim)==1? valid_dim[0] : str("one of ",valid_dim)
|
||||
"Input ",name," must has dimension ", len(path[0])," but dimension must be ",
|
||||
is_list(valid_dim) ? str("one of ",valid_dim) : valid_dim
|
||||
)
|
||||
)
|
||||
closed && approx(path[0],select(path,-1))? slice(path,0,-2) : path;
|
||||
|
@ -223,6 +222,64 @@ function split_nested_region(region) =
|
|||
) outs;
|
||||
|
||||
|
||||
function find_first_approx(val, list, start=0, all=false, eps=EPSILON) =
|
||||
all? [for (i=[start:1:len(list)-1]) if(approx(val, list[i], eps=eps)) i] :
|
||||
__find_first_approx(val, list, eps=eps, i=start);
|
||||
|
||||
function __find_first_approx(val, list, eps, i=0) =
|
||||
i >= len(list)? undef :
|
||||
approx(val, list[i], eps=eps)? i :
|
||||
__find_first_approx(val, list, eps=eps, i=i+1);
|
||||
|
||||
|
||||
// Function: polygons_equal()
|
||||
// Usage:
|
||||
// b = polygons_equal(poly1, poly2, <eps>)
|
||||
// Description:
|
||||
// Returns true if the components of region1 and region2 are the same polygons
|
||||
// within given epsilon tolerance.
|
||||
// Arguments:
|
||||
// poly1 = first polygon
|
||||
// poly2 = second polygon
|
||||
// eps = tolerance for comparison
|
||||
// Example(NORENDER):
|
||||
// polygons_equal(pentagon(r=4),
|
||||
// rot(360/5, p=pentagon(r=4))); // returns true
|
||||
// polygons_equal(pentagon(r=4),
|
||||
// rot(90, p=pentagon(r=4))); // returns false
|
||||
function polygons_equal(poly1, poly2, eps=EPSILON) =
|
||||
let( l1 = len(poly1), l2 = len(poly2))
|
||||
l1 != l2 ? false :
|
||||
let( maybes = find_first_approx(poly1[0], poly2, eps=eps, all=true) )
|
||||
maybes == []? false :
|
||||
[for (i=maybes) if (__polygons_equal(poly1, poly2, eps, i)) 1] != [];
|
||||
|
||||
function __polygons_equal(poly1, poly2, eps, st) =
|
||||
max([for(d=poly1-select(poly2,st,st-1)) d*d])<eps*eps;
|
||||
|
||||
// Function: regions_equal()
|
||||
// Usage:
|
||||
// b = regions_equal(region1, region2, <eps>)
|
||||
// Description:
|
||||
// Returns true if the components of region1 and region2 are the same polygons
|
||||
// within given epsilon tolerance.
|
||||
// Arguments:
|
||||
// poly1 = first polygon
|
||||
// poly2 = second polygon
|
||||
// eps = tolerance for comparison
|
||||
function regions_equal(region1, region2) =
|
||||
assert(is_region(region1) && is_region(region2))
|
||||
len(region1)==len(region2) &&
|
||||
[
|
||||
for (a=region1)
|
||||
if (1!=sum(
|
||||
[for(b=region2)
|
||||
if (polygons_equal(path3d(a), path3d(b)))
|
||||
1]
|
||||
)
|
||||
) 1
|
||||
] == [];
|
||||
|
||||
|
||||
// Section: Region Extrusion and VNFs
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ include <structs.scad>
|
|||
// circular roundovers. For continuous curvature roundovers `$fs` and `$fn` are used and `$fa` is
|
||||
// ignored. Note that $fn is interpreted as the number of points on the roundover curve, which is
|
||||
// not equivalent to its meaning for rounding circles because roundovers are usually small fractions
|
||||
// of a circular arc. When doing continuous curvature rounding be sure to use lots of segments or the effect
|
||||
// of a circular arc. As usual, $fn overrides $fs. When doing continuous curvature rounding be sure to use lots of segments or the effect
|
||||
// will be hidden by the discretization. Note that if you use $fn with "smooth" then $fn points are added at each corner, even
|
||||
// if the "corner" is flat, with collinear points, so this guarantees a specific output length.
|
||||
//
|
||||
|
@ -264,8 +264,7 @@ function round_corners(path, method="circle", radius, cut, joint, k, closed=true
|
|||
let(
|
||||
pathbit = select(path,i-1,i+1),
|
||||
angle = approx(pathbit[0],pathbit[1]) || approx(pathbit[1],pathbit[2]) ? undef
|
||||
: vector_angle(select(path,i-1,i+1))/2,
|
||||
f=echo(angle=angle)
|
||||
: vector_angle(select(path,i-1,i+1))/2
|
||||
)
|
||||
(!closed && (i==0 || i==len(path)-1)) ? [0] : // Force zeros at ends for non-closed
|
||||
parm[i]==0 ? [0] : // If no rounding requested then don't try to compute parameters
|
||||
|
|
|
@ -554,7 +554,7 @@ function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, l
|
|||
)
|
||||
assert(is_vector(cp,2),"Centerpoint must be a 2d vector")
|
||||
assert(angle!=0, "Arc has zero length")
|
||||
assert(r>0, "Arc radius invalid")
|
||||
assert(is_def(r) && r>0, "Arc radius invalid")
|
||||
let(
|
||||
N = max(3, is_undef(N)? ceil(segs(r)*abs(angle)/360) : N),
|
||||
arcpoints = [for(i=[0:N-1]) let(theta = start + i*angle/(N-1)) r*[cos(theta),sin(theta)]+cp],
|
||||
|
|
76
skin.scad
76
skin.scad
|
@ -63,8 +63,8 @@
|
|||
// 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 four methods for specifying alignment for incommensurate profiles.
|
||||
// The available methods are `"distance"`, `"tangent"`, `"direct"` and `"reindex"`.
|
||||
// You can choose from five methods for specifying alignment for incommensurate profiles.
|
||||
// The available methods are `"distance"`, `"fast_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.
|
||||
// .
|
||||
|
@ -87,14 +87,17 @@
|
|||
// `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" and "tangent" methods work by duplicating vertices to create
|
||||
// The "distance", "fast_distance" and "tangent" methods 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.
|
||||
// Using sampling by length would ignore the repeated vertices and ruin the alignment.
|
||||
// The "fast_distance" method is similar to "distance", but it makes the assumption that an edge should
|
||||
// connect the first vertices 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.
|
||||
// .
|
||||
// The `"tangent"` method generally produces good results when
|
||||
// connecting a discrete polygon to a convex, finely sampled curve. It works by finding
|
||||
|
@ -104,7 +107,9 @@
|
|||
// 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.
|
||||
// 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.
|
||||
// .
|
||||
// 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"
|
||||
|
@ -119,11 +124,11 @@
|
|||
// 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" 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".
|
||||
// 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".
|
||||
// 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", "tangent", "direct" or "reindex". Default: "direct".
|
||||
// method = method for connecting profiles, one of "distance", "fast_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"
|
||||
|
@ -374,7 +379,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","tangent"],
|
||||
legal_methods = ["direct","reindex","distance","fast_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])),
|
||||
|
@ -402,7 +407,7 @@ 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("tangent",method)), "sampling is set to \"length\" which is only allowed iwith methods \"direct\" and \"reindex\"")
|
||||
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(capsOK, "caps must be boolean or a list of two booleans")
|
||||
assert(!closed || !caps, "Cannot make closed shape with caps")
|
||||
let(
|
||||
|
@ -449,6 +454,7 @@ 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),
|
||||
|
@ -720,6 +726,23 @@ function _skin_distance_match(poly1,poly2) =
|
|||
)
|
||||
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:
|
||||
|
@ -927,7 +950,7 @@ module sweep(shape, transforms, closed=false, caps, convexity=10,
|
|||
// 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=>);
|
||||
// Description:
|
||||
// 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.
|
||||
// Takes as input a 2D polygon path, 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`.
|
||||
// .
|
||||
|
@ -1206,6 +1229,18 @@ 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)
|
||||
|
@ -1225,7 +1260,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")
|
||||
assert(is_path(shape,2) || is_region(shape), "shape must be a 2d path or region.")
|
||||
// let(shape = check_and_fix_path(shape,valid_dim=2,closed=true,name="shape"))
|
||||
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(
|
||||
|
@ -1301,15 +1336,15 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
translate(path[i%L])*rotation*zrot(-twist*pathfrac[i])
|
||||
] :
|
||||
assert(false,"Unknown method or no method given")[], // unknown method
|
||||
ends_match = !closed ? true :
|
||||
let(
|
||||
start = apply(transform_list[0],path3d(shape)),
|
||||
end = reindex_polygon(start, apply(transform_list[L],path3d(shape)))
|
||||
)
|
||||
all([for(i=idx(start)) approx(start[i],end[i])]),
|
||||
ends_match = !closed ? true
|
||||
: let( rshape = is_path(shape) ? [path3d(shape)]
|
||||
: [for(s=shape) path3d(s)]
|
||||
)
|
||||
regions_equal(apply(transform_list[0], rshape),
|
||||
apply(transform_list[L], rshape)),
|
||||
dummy = ends_match ? 0 : echo("WARNING: ***** The points do not match when closing the model *****")
|
||||
)
|
||||
transforms ? transform_list : sweep(clockwise_polygon(shape), transform_list, closed=false, caps=fullcaps);
|
||||
transforms ? transform_list : sweep(is_path(shape)?clockwise_polygon(shape):shape, transform_list, closed=false, caps=fullcaps);
|
||||
|
||||
|
||||
// Function&Module: path_sweep2d()
|
||||
|
@ -1361,7 +1396,8 @@ 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
|
||||
fullcaps = is_bool(caps) ? [caps,caps] : caps,
|
||||
shape = check_and_fix_path(shape,valid_dim=2,closed=true,name="shape")
|
||||
)
|
||||
assert(capsOK, "caps must be boolean or a list of two booleans")
|
||||
assert(!closed || !caps, "Cannot make closed shape with caps")
|
||||
|
|
|
@ -824,19 +824,78 @@ module test_lcm() {
|
|||
test_lcm();
|
||||
|
||||
|
||||
module test_C_times() {
|
||||
assert_equal(C_times([4,5],[9,-4]), [56,29]);
|
||||
assert_equal(C_times([-7,2],[24,3]), [-174, 27]);
|
||||
module test_c_mul() {
|
||||
assert_equal(c_mul([4,5],[9,-4]), [56,29]);
|
||||
assert_equal(c_mul([-7,2],[24,3]), [-174, 27]);
|
||||
assert_equal(c_mul([3,4], [[3,-7], [4,9], [4,8]]), [[37,-9],[-24,43], [-20,40]]);
|
||||
assert_equal(c_mul([[3,-7], [4,9], [4,8]], [[1,1],[3,4],[-3,4]]), [-58,31]);
|
||||
M = [
|
||||
[ [3,4], [9,-1], [4,3] ],
|
||||
[ [2,9], [4,9], [3,-1] ]
|
||||
];
|
||||
assert_equal(c_mul(M, [ [3,4], [4,4],[5,5]]), [[38,91], [-30, 97]]);
|
||||
assert_equal(c_mul([[4,4],[9,1]], M), [[5,111],[67,117], [32,22]]);
|
||||
assert_equal(c_mul(M,transpose(M)), [ [[80,30], [30, 117]], [[30,117], [-134, 102]]]);
|
||||
assert_equal(c_mul(transpose(M),M), [ [[-84,60],[-42,87],[15,50]], [[-42,87],[15,54],[60,46]], [[15,50],[60,46],[15,18]]]);
|
||||
}
|
||||
test_C_times();
|
||||
test_c_mul();
|
||||
|
||||
|
||||
module test_C_div() {
|
||||
assert_equal(C_div([56,29],[9,-4]), [4,5]);
|
||||
assert_equal(C_div([-174,27],[-7,2]), [24,3]);
|
||||
module test_c_div() {
|
||||
assert_equal(c_div([56,29],[9,-4]), [4,5]);
|
||||
assert_equal(c_div([-174,27],[-7,2]), [24,3]);
|
||||
}
|
||||
test_C_div();
|
||||
test_c_div();
|
||||
|
||||
module test_c_conj(){
|
||||
assert_equal(c_conj([3,4]), [3,-4]);
|
||||
assert_equal(c_conj( [ [2,9], [4,9], [3,-1] ]), [ [2,-9], [4,-9], [3,1] ]);
|
||||
M = [
|
||||
[ [3,4], [9,-1], [4,3] ],
|
||||
[ [2,9], [4,9], [3,-1] ]
|
||||
];
|
||||
Mc = [
|
||||
[ [3,-4], [9,1], [4,-3] ],
|
||||
[ [2,-9], [4,-9], [3,1] ]
|
||||
];
|
||||
assert_equal(c_conj(M), Mc);
|
||||
}
|
||||
test_c_conj();
|
||||
|
||||
module test_c_real(){
|
||||
M = [
|
||||
[ [3,4], [9,-1], [4,3] ],
|
||||
[ [2,9], [4,9], [3,-1] ]
|
||||
];
|
||||
assert_equal(c_real(M), [[3,9,4],[2,4,3]]);
|
||||
assert_equal(c_real( [ [3,4], [9,-1], [4,3] ]), [3,9,4]);
|
||||
assert_equal(c_real([3,4]),3);
|
||||
}
|
||||
test_c_real();
|
||||
|
||||
|
||||
module test_c_imag(){
|
||||
M = [
|
||||
[ [3,4], [9,-1], [4,3] ],
|
||||
[ [2,9], [4,9], [3,-1] ]
|
||||
];
|
||||
assert_equal(c_imag(M), [[4,-1,3],[9,9,-1]]);
|
||||
assert_equal(c_imag( [ [3,4], [9,-1], [4,3] ]), [4,-1,3]);
|
||||
assert_equal(c_imag([3,4]),4);
|
||||
}
|
||||
test_c_imag();
|
||||
|
||||
|
||||
module test_c_ident(){
|
||||
assert_equal(c_ident(3), [[[1, 0], [0, 0], [0, 0]], [[0, 0], [1, 0], [0, 0]], [[0, 0], [0, 0], [1, 0]]]);
|
||||
}
|
||||
test_c_ident();
|
||||
|
||||
module test_c_norm(){
|
||||
assert_equal(c_norm([3,4]), 5);
|
||||
assert_approx(c_norm([[3,4],[5,6]]), 9.273618495495704);
|
||||
}
|
||||
test_c_norm();
|
||||
|
||||
module test_back_substitute(){
|
||||
R = [[12,4,3,2],
|
||||
|
|
Loading…
Reference in a new issue