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