mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
Merge branch 'master' of https://github.com/revarbat/BOSL2
This commit is contained in:
commit
0cfa56d6a7
15 changed files with 245 additions and 192 deletions
3
.nodocsfiles
Normal file
3
.nodocsfiles
Normal file
|
@ -0,0 +1,3 @@
|
|||
bosl1compat.scad
|
||||
std.scad
|
||||
version.scad
|
|
@ -12,8 +12,8 @@ A library for OpenSCAD, filled with useful tools, shapes, masks, math and manipu
|
|||
## Installation
|
||||
|
||||
1. Download the .zip or .tar.gz release file for this library.
|
||||
2. Unpack it. It should create a `BOSL-v2.0` directory with the library files within it.
|
||||
3. Rename the directory to `BOSL2`.
|
||||
2. Unpack it. Make sure that you unpack the whole file structure. Some zipfile unpackers call this option "Use folder names". It should create either a `BOSL-v2.0` or `BOSL2-master` directory with the library files within it. You should see "examples", "scripts", "tests", and other subdirectories.
|
||||
3. Rename the unpacked main directory to `BOSL2`.
|
||||
4. Move the `BOSL2` directory into the apropriate OpenSCAD library directory for your platform:
|
||||
- Windows: `My Documents\OpenSCAD\libraries\`
|
||||
- Linux: `$HOME/.local/share/OpenSCAD/libraries/`
|
||||
|
|
11
arrays.scad
11
arrays.scad
|
@ -101,6 +101,15 @@ function select(list, start, end=undef) =
|
|||
// last(l); // Returns 9.
|
||||
function last(list) = list[len(list)-1];
|
||||
|
||||
// Function: delete_last()
|
||||
// Description:
|
||||
// Returns a list of all but the last entry. If input is empty, returns empty list.
|
||||
// Usage:
|
||||
// delete_last(list)
|
||||
function delete_last(list) =
|
||||
assert(is_list(list))
|
||||
list==[] ? [] : slice(list,0,-2);
|
||||
|
||||
// Function: slice()
|
||||
// Description:
|
||||
// Returns a slice of a list. The first item is index 0.
|
||||
|
@ -281,7 +290,7 @@ function list_range(n=undef, s=0, e=undef, step=undef) =
|
|||
// Example:
|
||||
// reverse([3,4,5,6]); // Returns [6,5,4,3]
|
||||
function reverse(x) =
|
||||
assert(is_list(x)||is_string(x))
|
||||
assert(is_list(x)||is_string(x), "Input to reverse must be a list or string")
|
||||
let (elems = [ for (i = [len(x)-1 : -1 : 0]) x[i] ])
|
||||
is_string(x)? str_join(elems) : elems;
|
||||
|
||||
|
|
|
@ -282,7 +282,7 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
|
|||
// r = Most general radius.
|
||||
// d = Most general diameter.
|
||||
// dflt = Value to return if all other values given are `undef`.
|
||||
function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) =
|
||||
function get_radius(r1, r2, r, d1, d2, d, dflt) =
|
||||
assert(num_defined([r1,d1,r2,d2])<2, "Conflicting or redundant radius/diameter arguments given.")
|
||||
!is_undef(r1) ? assert(is_finite(r1), "Invalid radius r1." ) r1
|
||||
: !is_undef(r2) ? assert(is_finite(r2), "Invalid radius r2." ) r2
|
||||
|
|
288
geometry.scad
288
geometry.scad
|
@ -26,12 +26,12 @@ function point_on_segment2d(point, edge, eps=EPSILON) =
|
|||
assert( _valid_line(edge,2,eps=eps), "Invalid segment." )
|
||||
let( dp = point-edge[0],
|
||||
de = edge[1]-edge[0],
|
||||
ne = norm(de) )
|
||||
( dp*de >= -eps*ne )
|
||||
ne = norm(de) )
|
||||
( dp*de >= -eps*ne )
|
||||
&& ( (dp-de)*de <= eps*ne ) // point projects on the segment
|
||||
&& _dist2line(point-edge[0],unit(de))<eps; // point is on the line
|
||||
|
||||
|
||||
&& _dist2line(point-edge[0],unit(de))<eps; // point is on the line
|
||||
|
||||
|
||||
//Internal - distance from point `d` to the line passing through the origin with unit direction n
|
||||
//_dist2line works for any dimension
|
||||
function _dist2line(d,n) = norm(d-(d * n) * n);
|
||||
|
@ -39,15 +39,15 @@ function _dist2line(d,n) = norm(d-(d * n) * n);
|
|||
// Internal non-exposed function.
|
||||
function _point_above_below_segment(point, edge) =
|
||||
let( edge = edge - [point, point] )
|
||||
edge[0].y <= 0
|
||||
edge[0].y <= 0
|
||||
? (edge[1].y > 0 && cross(edge[0], edge[1]-edge[0]) > 0) ? 1 : 0
|
||||
: (edge[1].y <= 0 && cross(edge[0], edge[1]-edge[0]) < 0) ? -1 : 0 ;
|
||||
|
||||
//Internal
|
||||
function _valid_line(line,dim,eps=EPSILON) =
|
||||
is_matrix(line,2,dim)
|
||||
&& ! approx(norm(line[1]-line[0]), 0, eps);
|
||||
|
||||
function _valid_line(line,dim,eps=EPSILON) =
|
||||
is_matrix(line,2,dim)
|
||||
&& ! approx(norm(line[1]-line[0]), 0, eps);
|
||||
|
||||
//Internal
|
||||
function _valid_plane(p, eps=EPSILON) = is_vector(p,4) && ! approx(norm(p),0,eps);
|
||||
|
||||
|
@ -79,13 +79,13 @@ function point_left_of_line2d(point, line) =
|
|||
// eps = Acceptable variance. Default: `EPSILON` (1e-9)
|
||||
function collinear(a, b, c, eps=EPSILON) =
|
||||
assert( is_path([a,b,c],dim=undef)
|
||||
|| ( is_undef(b) && is_undef(c) && is_path(a,dim=undef) ),
|
||||
|| ( is_undef(b) && is_undef(c) && is_path(a,dim=undef) ),
|
||||
"Input should be 3 points or a list of points with same dimension.")
|
||||
assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
|
||||
let( points = is_def(c) ? [a,b,c]: a )
|
||||
len(points)<3 ? true
|
||||
: noncollinear_triple(points,error=false,eps=eps)==[];
|
||||
|
||||
|
||||
|
||||
// Function: distance_from_line()
|
||||
// Usage:
|
||||
|
@ -98,11 +98,11 @@ function collinear(a, b, c, eps=EPSILON) =
|
|||
// Example:
|
||||
// distance_from_line([[-10,0], [10,0]], [3,8]); // Returns: 8
|
||||
function distance_from_line(line, pt) =
|
||||
assert( _valid_line(line) && is_vector(pt,len(line[0])),
|
||||
assert( _valid_line(line) && is_vector(pt,len(line[0])),
|
||||
"Invalid line, invalid point or incompatible dimensions." )
|
||||
_dist2line(pt-line[0],unit(line[1]-line[0]));
|
||||
|
||||
|
||||
|
||||
|
||||
// Function: line_normal()
|
||||
// Usage:
|
||||
// line_normal([P1,P2])
|
||||
|
@ -121,9 +121,9 @@ function distance_from_line(line, pt) =
|
|||
// color("blue") move_copies([p1,p2]) circle(d=2, $fn=12);
|
||||
function line_normal(p1,p2) =
|
||||
is_undef(p2)
|
||||
? assert( len(p1)==2 && !is_undef(p1[1]) , "Invalid input." )
|
||||
line_normal(p1[0],p1[1])
|
||||
: assert( _valid_line([p1,p2],dim=2), "Invalid line." )
|
||||
? assert( len(p1)==2 && !is_undef(p1[1]) , "Invalid input." )
|
||||
line_normal(p1[0],p1[1])
|
||||
: assert( _valid_line([p1,p2],dim=2), "Invalid line." )
|
||||
unit([p1.y-p2.y,p2.x-p1.x]);
|
||||
|
||||
|
||||
|
@ -157,7 +157,7 @@ function _general_line_intersection(s1,s2,eps=EPSILON) =
|
|||
function line_intersection(l1,l2,eps=EPSILON) =
|
||||
assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
|
||||
assert( _valid_line(l1,dim=2,eps=eps) &&_valid_line(l2,dim=2,eps=eps), "Invalid line(s)." )
|
||||
let(isect = _general_line_intersection(l1,l2,eps=eps))
|
||||
let(isect = _general_line_intersection(l1,l2,eps=eps))
|
||||
isect[0];
|
||||
|
||||
|
||||
|
@ -176,7 +176,7 @@ function line_ray_intersection(line,ray,eps=EPSILON) =
|
|||
assert( _valid_line(line,dim=2,eps=eps) && _valid_line(ray,dim=2,eps=eps), "Invalid line or ray." )
|
||||
let(
|
||||
isect = _general_line_intersection(line,ray,eps=eps)
|
||||
)
|
||||
)
|
||||
is_undef(isect[0]) ? undef :
|
||||
(isect[2]<0-eps) ? undef : isect[0];
|
||||
|
||||
|
@ -217,7 +217,7 @@ function ray_intersection(r1,r2,eps=EPSILON) =
|
|||
assert( _valid_line(r1,dim=2,eps=eps) && _valid_line(r2,dim=2,eps=eps), "Invalid ray(s)." )
|
||||
let(
|
||||
isect = _general_line_intersection(r1,r2,eps=eps)
|
||||
)
|
||||
)
|
||||
is_undef(isect[0]) ? undef :
|
||||
isect[1]<0-eps || isect[2]<0-eps ? undef : isect[0];
|
||||
|
||||
|
@ -237,7 +237,7 @@ function ray_segment_intersection(ray,segment,eps=EPSILON) =
|
|||
assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
|
||||
let(
|
||||
isect = _general_line_intersection(ray,segment,eps=eps)
|
||||
)
|
||||
)
|
||||
is_undef(isect[0]) ? undef :
|
||||
isect[1]<0-eps || isect[2]<0-eps || isect[2]>1+eps ? undef :
|
||||
isect[0];
|
||||
|
@ -258,7 +258,7 @@ function segment_intersection(s1,s2,eps=EPSILON) =
|
|||
assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
|
||||
let(
|
||||
isect = _general_line_intersection(s1,s2,eps=eps)
|
||||
)
|
||||
)
|
||||
is_undef(isect[0]) ? undef :
|
||||
isect[1]<0-eps || isect[1]>1+eps || isect[2]<0-eps || isect[2]>1+eps ? undef :
|
||||
isect[0];
|
||||
|
@ -449,7 +449,7 @@ function segment_closest_point(seg,pt) =
|
|||
projection>=seglen ? seg[1] :
|
||||
seg[0] + projection*segvec;
|
||||
|
||||
|
||||
|
||||
// Function: line_from_points()
|
||||
// Usage:
|
||||
// line_from_points(points, [fast], [eps]);
|
||||
|
@ -586,25 +586,25 @@ function tri_calc(ang,ang2,adj,opp,hyp) =
|
|||
assert(num_defined([ang,ang2,adj,opp,hyp])==2, "Exactly two arguments must be given.")
|
||||
let(
|
||||
ang = ang!=undef
|
||||
? assert(ang>0&&ang<90, "The input angles should be acute angles." ) ang
|
||||
: ang2!=undef ? (90-ang2)
|
||||
: adj==undef ? asin(constrain(opp/hyp,-1,1))
|
||||
: opp==undef ? acos(constrain(adj/hyp,-1,1))
|
||||
? assert(ang>0&&ang<90, "The input angles should be acute angles." ) ang
|
||||
: ang2!=undef ? (90-ang2)
|
||||
: adj==undef ? asin(constrain(opp/hyp,-1,1))
|
||||
: opp==undef ? acos(constrain(adj/hyp,-1,1))
|
||||
: atan2(opp,adj),
|
||||
ang2 = ang2!=undef
|
||||
? assert(ang2>0&&ang2<90, "The input angles should be acute angles." ) ang2
|
||||
? assert(ang2>0&&ang2<90, "The input angles should be acute angles." ) ang2
|
||||
: (90-ang),
|
||||
adj = adj!=undef
|
||||
? assert(adj>0, "Triangle side lengths should be positive." ) adj
|
||||
? assert(adj>0, "Triangle side lengths should be positive." ) adj
|
||||
: (opp!=undef? (opp/tan(ang)) : (hyp*cos(ang))),
|
||||
opp = opp!=undef
|
||||
? assert(opp>0, "Triangle side lengths should be positive." ) opp
|
||||
? assert(opp>0, "Triangle side lengths should be positive." ) opp
|
||||
: (adj!=undef? (adj*tan(ang)) : (hyp*sin(ang))),
|
||||
hyp = hyp!=undef
|
||||
? assert(hyp>0, "Triangle side lengths should be positive." )
|
||||
? assert(hyp>0, "Triangle side lengths should be positive." )
|
||||
assert(adj<hyp && opp<hyp, "Hyphotenuse length should be greater than the other sides." )
|
||||
hyp
|
||||
: (adj!=undef? (adj/cos(ang))
|
||||
hyp
|
||||
: (adj!=undef? (adj/cos(ang))
|
||||
: (opp/sin(ang)))
|
||||
)
|
||||
[adj, opp, hyp, ang, ang2];
|
||||
|
@ -622,7 +622,7 @@ function tri_calc(ang,ang2,adj,opp,hyp) =
|
|||
// Example:
|
||||
// hyp = hyp_opp_to_adj(5,3); // Returns: 4
|
||||
function hyp_opp_to_adj(hyp,opp) =
|
||||
assert(is_finite(hyp+opp) && hyp>=0 && opp>=0,
|
||||
assert(is_finite(hyp+opp) && hyp>=0 && opp>=0,
|
||||
"Triangle side lengths should be a positive numbers." )
|
||||
sqrt(hyp*hyp-opp*opp);
|
||||
|
||||
|
@ -672,7 +672,7 @@ function opp_ang_to_adj(opp,ang) =
|
|||
// Example:
|
||||
// opp = hyp_adj_to_opp(5,4); // Returns: 3
|
||||
function hyp_adj_to_opp(hyp,adj) =
|
||||
assert(is_finite(hyp) && hyp>=0 && is_finite(adj) && adj>=0,
|
||||
assert(is_finite(hyp) && hyp>=0 && is_finite(adj) && adj>=0,
|
||||
"Triangle side lengths should be a positive numbers." )
|
||||
sqrt(hyp*hyp-adj*adj);
|
||||
|
||||
|
@ -720,7 +720,7 @@ function adj_ang_to_opp(adj,ang) =
|
|||
// Example:
|
||||
// hyp = adj_opp_to_hyp(3,4); // Returns: 5
|
||||
function adj_opp_to_hyp(adj,opp) =
|
||||
assert(is_finite(opp) && opp>=0 && is_finite(adj) && adj>=0,
|
||||
assert(is_finite(opp) && opp>=0 && is_finite(adj) && adj>=0,
|
||||
"Triangle side lengths should be a positive numbers." )
|
||||
norm([opp,adj]);
|
||||
|
||||
|
@ -768,7 +768,7 @@ function opp_ang_to_hyp(opp,ang) =
|
|||
// Example:
|
||||
// ang = hyp_adj_to_ang(8,4); // Returns: 60 degrees
|
||||
function hyp_adj_to_ang(hyp,adj) =
|
||||
assert(is_finite(hyp) && hyp>0 && is_finite(adj) && adj>=0,
|
||||
assert(is_finite(hyp) && hyp>0 && is_finite(adj) && adj>=0,
|
||||
"Triangle side lengths should be positive numbers." )
|
||||
acos(adj/hyp);
|
||||
|
||||
|
@ -784,7 +784,7 @@ function hyp_adj_to_ang(hyp,adj) =
|
|||
// Example:
|
||||
// ang = hyp_opp_to_ang(8,4); // Returns: 30 degrees
|
||||
function hyp_opp_to_ang(hyp,opp) =
|
||||
assert(is_finite(hyp+opp) && hyp>0 && opp>=0,
|
||||
assert(is_finite(hyp+opp) && hyp>0 && opp>=0,
|
||||
"Triangle side lengths should be positive numbers." )
|
||||
asin(opp/hyp);
|
||||
|
||||
|
@ -800,7 +800,7 @@ function hyp_opp_to_ang(hyp,opp) =
|
|||
// Example:
|
||||
// ang = adj_opp_to_ang(sqrt(3)/2,0.5); // Returns: 30 degrees
|
||||
function adj_opp_to_ang(adj,opp) =
|
||||
assert(is_finite(adj+opp) && adj>0 && opp>=0,
|
||||
assert(is_finite(adj+opp) && adj>0 && opp>=0,
|
||||
"Triangle side lengths should be positive numbers." )
|
||||
atan2(opp,adj);
|
||||
|
||||
|
@ -814,10 +814,10 @@ function adj_opp_to_ang(adj,opp) =
|
|||
// Examples:
|
||||
// triangle_area([0,0], [5,10], [10,0]); // Returns -50
|
||||
// triangle_area([10,0], [5,10], [0,0]); // Returns 50
|
||||
function triangle_area(a,b,c) =
|
||||
assert( is_path([a,b,c]), "Invalid points or incompatible dimensions." )
|
||||
len(a)==3
|
||||
? 0.5*norm(cross(c-a,c-b))
|
||||
function triangle_area(a,b,c) =
|
||||
assert( is_path([a,b,c]), "Invalid points or incompatible dimensions." )
|
||||
len(a)==3
|
||||
? 0.5*norm(cross(c-a,c-b))
|
||||
: 0.5*cross(c-a,c-b);
|
||||
|
||||
|
||||
|
@ -830,7 +830,7 @@ function triangle_area(a,b,c) =
|
|||
// plane3pt(p1, p2, p3);
|
||||
// Description:
|
||||
// Generates the normalized cartesian equation of a plane from three 3d points.
|
||||
// Returns [A,B,C,D] where Ax + By + Cz = D is the equation of a plane.
|
||||
// Returns [A,B,C,D] where Ax + By + Cz = D is the equation of a plane.
|
||||
// Returns [], if the points are collinear.
|
||||
// Arguments:
|
||||
// p1 = The first point on the plane.
|
||||
|
@ -838,11 +838,11 @@ function triangle_area(a,b,c) =
|
|||
// p3 = The third point on the plane.
|
||||
function plane3pt(p1, p2, p3) =
|
||||
assert( is_path([p1,p2,p3],dim=3) && len(p1)==3,
|
||||
"Invalid points or incompatible dimensions." )
|
||||
"Invalid points or incompatible dimensions." )
|
||||
let(
|
||||
crx = cross(p3-p1, p2-p1),
|
||||
nrm = norm(crx)
|
||||
)
|
||||
)
|
||||
approx(nrm,0) ? [] :
|
||||
concat(crx, crx*p1)/nrm;
|
||||
|
||||
|
@ -869,7 +869,7 @@ function plane3pt_indexed(points, i1, i2, i3) =
|
|||
p1 = points[i1],
|
||||
p2 = points[i2],
|
||||
p3 = points[i3]
|
||||
)
|
||||
)
|
||||
plane3pt(p1,p2,p3);
|
||||
|
||||
|
||||
|
@ -881,7 +881,7 @@ function plane3pt_indexed(points, i1, i2, i3) =
|
|||
// Example:
|
||||
// plane_from_normal([0,0,1], [2,2,2]); // Returns the xy plane passing through the point (2,2,2)
|
||||
function plane_from_normal(normal, pt=[0,0,0]) =
|
||||
assert( is_matrix([normal,pt],2,3) && !approx(norm(normal),0),
|
||||
assert( is_matrix([normal,pt],2,3) && !approx(norm(normal),0),
|
||||
"Inputs `normal` and `pt` should 3d vectors/points and `normal` cannot be zero." )
|
||||
concat(normal, normal*pt) / norm(normal);
|
||||
|
||||
|
@ -946,7 +946,7 @@ function plane_from_polygon(poly, fast=false, eps=EPSILON) =
|
|||
poly = deduplicate(poly),
|
||||
n = polygon_normal(poly),
|
||||
plane = [n.x, n.y, n.z, n*poly[0]]
|
||||
)
|
||||
)
|
||||
fast? plane: coplanar(poly,eps=eps)? plane: [];
|
||||
|
||||
|
||||
|
@ -955,7 +955,7 @@ function plane_from_polygon(poly, fast=false, eps=EPSILON) =
|
|||
// plane_normal(plane);
|
||||
// Description:
|
||||
// Returns the unit length normal vector for the given plane.
|
||||
function plane_normal(plane) =
|
||||
function plane_normal(plane) =
|
||||
assert( _valid_plane(plane), "Invalid input plane." )
|
||||
unit([plane.x, plane.y, plane.z]);
|
||||
|
||||
|
@ -964,10 +964,10 @@ function plane_normal(plane) =
|
|||
// Usage:
|
||||
// d = plane_offset(plane);
|
||||
// Description:
|
||||
// Returns coeficient D of the normalized plane equation `Ax+By+Cz=D`, or the scalar offset of the plane from the origin.
|
||||
// Returns coeficient D of the normalized plane equation `Ax+By+Cz=D`, or the scalar offset of the plane from the origin.
|
||||
// This value may be negative.
|
||||
// The absolute value of this coefficient is the distance of the plane from the origin.
|
||||
function plane_offset(plane) =
|
||||
function plane_offset(plane) =
|
||||
assert( _valid_plane(plane), "Invalid input plane." )
|
||||
plane[3]/norm([plane.x, plane.y, plane.z]);
|
||||
|
||||
|
@ -994,7 +994,7 @@ function plane_transform(plane) =
|
|||
plane = normalize_plane(plane),
|
||||
n = point3d(plane),
|
||||
cp = n * plane[3]
|
||||
)
|
||||
)
|
||||
rot(from=n, to=UP) * move(-cp);
|
||||
|
||||
|
||||
|
@ -1002,7 +1002,7 @@ function plane_transform(plane) =
|
|||
// Usage:
|
||||
// projection_on_plane(points);
|
||||
// Description:
|
||||
// Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the 3D orthogonal
|
||||
// Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the 3D orthogonal
|
||||
// projection of the points on the plane.
|
||||
// Arguments:
|
||||
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
|
||||
|
@ -1014,13 +1014,13 @@ function plane_transform(plane) =
|
|||
function projection_on_plane(plane, points) =
|
||||
assert( _valid_plane(plane), "Invalid plane." )
|
||||
assert( is_path(points), "Invalid list of points or dimension." )
|
||||
let(
|
||||
let(
|
||||
p = len(points[0])==2
|
||||
? [for(pi=points) point3d(pi) ]
|
||||
: points,
|
||||
: points,
|
||||
plane = normalize_plane(plane),
|
||||
n = point3d(plane)
|
||||
)
|
||||
)
|
||||
[for(pi=p) pi - (pi*n - plane[3])*n];
|
||||
|
||||
|
||||
|
@ -1069,7 +1069,7 @@ function closest_point_on_plane(plane, point) =
|
|||
let( plane = normalize_plane(plane),
|
||||
n = point3d(plane),
|
||||
d = n*point - plane[3] // distance from plane
|
||||
)
|
||||
)
|
||||
point - n*d;
|
||||
|
||||
|
||||
|
@ -1078,7 +1078,7 @@ function closest_point_on_plane(plane, point) =
|
|||
// Returns undef if line is parallel to, but not on the given plane.
|
||||
function _general_plane_line_intersection(plane, line, eps=EPSILON) =
|
||||
let(
|
||||
a = plane*[each line[0],-1], // evaluation of the plane expression at line[0]
|
||||
a = plane*[each line[0],-1], // evaluation of the plane expression at line[0]
|
||||
b = plane*[each(line[1]-line[0]),0] // difference between the plane expression evaluation at line[1] and at line[0]
|
||||
)
|
||||
approx(b,0,eps) // is (line[1]-line[0]) "parallel" to the plane ?
|
||||
|
@ -1086,10 +1086,10 @@ function _general_plane_line_intersection(plane, line, eps=EPSILON) =
|
|||
? [line,undef] // line is on the plane
|
||||
: undef // line is parallel but not on the plane
|
||||
: [ line[0]-a/b*(line[1]-line[0]), -a/b ];
|
||||
|
||||
|
||||
|
||||
|
||||
// Function: normalize_plane()
|
||||
// Usage:
|
||||
// Usage:
|
||||
// nplane = normalize_plane(plane);
|
||||
// Description:
|
||||
// Returns a new representation [A,B,C,D] of `plane` where norm([A,B,C]) is equal to one.
|
||||
|
@ -1099,12 +1099,12 @@ function normalize_plane(plane) =
|
|||
|
||||
|
||||
// Function: plane_line_angle()
|
||||
// Usage:
|
||||
// Usage:
|
||||
// angle = plane_line_angle(plane,line);
|
||||
// Description:
|
||||
// Compute the angle between a plane [A, B, C, D] and a line, specified as a pair of points [p1,p2].
|
||||
// The resulting angle is signed, with the sign positive if the vector p2-p1 lies on
|
||||
// the same side of the plane as the plane's normal vector.
|
||||
// The resulting angle is signed, with the sign positive if the vector p2-p1 lies on
|
||||
// the same side of the plane as the plane's normal vector.
|
||||
function plane_line_angle(plane, line) =
|
||||
assert( _valid_plane(plane), "Invalid plane." )
|
||||
assert( _valid_line(line), "Invalid line." )
|
||||
|
@ -1113,7 +1113,7 @@ function plane_line_angle(plane, line) =
|
|||
normal = plane_normal(plane),
|
||||
sin_angle = linedir*normal,
|
||||
cos_angle = norm(cross(linedir,normal))
|
||||
)
|
||||
)
|
||||
atan2(sin_angle,cos_angle);
|
||||
|
||||
|
||||
|
@ -1189,19 +1189,19 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
|
|||
inside = [for (part = parts)
|
||||
if (point_in_polygon(mean(part), poly2d)>0) part
|
||||
]
|
||||
)
|
||||
)
|
||||
!inside? undef :
|
||||
let(
|
||||
isegs = [for (seg = inside) lift_plane(seg, plane) ]
|
||||
)
|
||||
)
|
||||
isegs
|
||||
)
|
||||
)
|
||||
: bounded[0] && res[1]<0? undef :
|
||||
bounded[1] && res[1]>1? undef :
|
||||
let(
|
||||
proj = clockwise_polygon(project_plane(poly, p1, p2, p3)),
|
||||
pt = project_plane(res[0], p1, p2, p3)
|
||||
)
|
||||
)
|
||||
point_in_polygon(pt, proj) < 0 ? undef : res[0];
|
||||
|
||||
|
||||
|
@ -1212,7 +1212,7 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
|
|||
// Compute the point which is the intersection of the three planes, or the line intersection of two planes.
|
||||
// If you give three planes the intersection is returned as a point. If you give two planes the intersection
|
||||
// is returned as a list of two points on the line of intersection. If any two input planes are parallel
|
||||
// or coincident then returns undef.
|
||||
// or coincident then returns undef.
|
||||
function plane_intersection(plane1,plane2,plane3) =
|
||||
assert( _valid_plane(plane1) && _valid_plane(plane2) && (is_undef(plane3) ||_valid_plane(plane3)),
|
||||
"The input must be 2 or 3 planes." )
|
||||
|
@ -1220,15 +1220,15 @@ function plane_intersection(plane1,plane2,plane3) =
|
|||
? let(
|
||||
matrix = [for(p=[plane1,plane2,plane3]) point3d(p)],
|
||||
rhs = [for(p=[plane1,plane2,plane3]) p[3]]
|
||||
)
|
||||
)
|
||||
linear_solve(matrix,rhs)
|
||||
: let( normal = cross(plane_normal(plane1), plane_normal(plane2)) )
|
||||
: let( normal = cross(plane_normal(plane1), plane_normal(plane2)) )
|
||||
approx(norm(normal),0) ? undef :
|
||||
let(
|
||||
matrix = [for(p=[plane1,plane2]) point3d(p)],
|
||||
rhs = [plane1[3], plane2[3]],
|
||||
point = linear_solve(matrix,rhs)
|
||||
)
|
||||
)
|
||||
point==[]? undef: [point, point+normal];
|
||||
|
||||
|
||||
|
@ -1244,13 +1244,13 @@ function coplanar(points, eps=EPSILON) =
|
|||
assert( is_path(points,dim=3) , "Input should be a list of 3D points." )
|
||||
assert( is_finite(eps) && eps>=0, "The tolerance should be a non-negative number." )
|
||||
len(points)<=2 ? false
|
||||
: let( ip = noncollinear_triple(points,error=false,eps=eps) )
|
||||
ip == [] ? false :
|
||||
let( plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]),
|
||||
: let( ip = noncollinear_triple(points,error=false,eps=eps) )
|
||||
ip == [] ? false :
|
||||
let( plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]),
|
||||
normal = point3d(plane) )
|
||||
max( points*normal ) - plane[3]< eps*norm(normal);
|
||||
|
||||
|
||||
|
||||
// Function: points_on_plane()
|
||||
// Usage:
|
||||
// points_on_plane(points, plane, <eps>);
|
||||
|
@ -1355,11 +1355,11 @@ function in_front_of_plane(plane, point) =
|
|||
function circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
|
||||
let(r = get_radius(r=r, d=d, dflt=undef))
|
||||
assert(r!=undef, "Must specify either r or d.")
|
||||
assert( ( is_path(pt1) && len(pt1)==3 && is_undef(pt2) && is_undef(pt3))
|
||||
assert( ( is_path(pt1) && len(pt1)==3 && is_undef(pt2) && is_undef(pt3))
|
||||
|| (is_matrix([pt1,pt2,pt3]) && (len(pt1)==2 || len(pt1)==3) ),
|
||||
"Invalid input points." )
|
||||
is_undef(pt2)
|
||||
? circle_2tangents(pt1[0], pt1[1], pt1[2], r=r, tangents=tangents)
|
||||
is_undef(pt2)
|
||||
? circle_2tangents(pt1[0], pt1[1], pt1[2], r=r, tangents=tangents)
|
||||
: collinear(pt1, pt2, pt3)? undef :
|
||||
let(
|
||||
v1 = unit(pt1 - pt2),
|
||||
|
@ -1369,7 +1369,7 @@ function circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
|
|||
a = vector_angle(v1, v2),
|
||||
hyp = r / sin(a/2),
|
||||
cp = pt2 + hyp * vmid
|
||||
)
|
||||
)
|
||||
!tangents ? [cp, n] :
|
||||
let(
|
||||
x = hyp * cos(a/2),
|
||||
|
@ -1377,7 +1377,7 @@ function circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
|
|||
tp2 = pt2 + x * v2,
|
||||
dang1 = vector_angle(tp1-cp,pt2-cp),
|
||||
dang2 = vector_angle(tp2-cp,pt2-cp)
|
||||
)
|
||||
)
|
||||
[cp, n, tp1, tp2, dang1, dang2];
|
||||
|
||||
module circle_2tangents(pt1, pt2, pt3, r, d, h, center=false) {
|
||||
|
@ -1438,8 +1438,8 @@ module circle_2tangents(pt1, pt2, pt3, r, d, h, center=false) {
|
|||
// move_copies(pts) color("cyan") sphere(d=3, $fn=12);
|
||||
function circle_3points(pt1, pt2, pt3) =
|
||||
(is_undef(pt2) && is_undef(pt3) && is_list(pt1))
|
||||
? circle_3points(pt1[0], pt1[1], pt1[2])
|
||||
: assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3)
|
||||
? circle_3points(pt1[0], pt1[1], pt1[2])
|
||||
: assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3)
|
||||
&& max(len(pt1),len(pt2),len(pt3))<=3 && min(len(pt1),len(pt2),len(pt3))>=2,
|
||||
"Invalid point(s)." )
|
||||
collinear(pt1,pt2,pt3)? [undef,undef,undef] :
|
||||
|
@ -1447,17 +1447,17 @@ function circle_3points(pt1, pt2, pt3) =
|
|||
v = [ point3d(pt1), point3d(pt2), point3d(pt3) ], // triangle vertices
|
||||
ed = [for(i=[0:2]) v[(i+1)%3]-v[i] ], // triangle edge vectors
|
||||
pm = [for(i=[0:2]) v[(i+1)%3]+v[i] ]/2, // edge mean points
|
||||
es = sortidx( [for(di=ed) norm(di) ] ),
|
||||
es = sortidx( [for(di=ed) norm(di) ] ),
|
||||
e1 = ed[es[1]], // take the 2 longest edges
|
||||
e2 = ed[es[2]],
|
||||
n0 = vector_axis(e1,e2), // normal standardization
|
||||
n0 = vector_axis(e1,e2), // normal standardization
|
||||
n = n0.z<0? -n0 : n0,
|
||||
sc = plane_intersection(
|
||||
sc = plane_intersection(
|
||||
[ each e1, e1*pm[es[1]] ], // planes orthogonal to 2 edges
|
||||
[ each e2, e2*pm[es[2]] ],
|
||||
[ each n, n*v[0] ]
|
||||
), // triangle plane
|
||||
cp = len(pt1)+len(pt2)+len(pt3)>6 ? sc : [sc.x, sc.y],
|
||||
cp = len(pt1)+len(pt2)+len(pt3)>6 ? sc : [sc.x, sc.y],
|
||||
r = norm(sc-v[0])
|
||||
) [ cp, r, n ];
|
||||
|
||||
|
@ -1522,8 +1522,8 @@ function circle_point_tangents(r, d, cp, pt) =
|
|||
// circle 1. If the circles intersect then the interior tangents don't exist and the function
|
||||
// returns only two entries. If one circle is inside the other one then no tangents exist
|
||||
// so the function returns the empty set. When the circles are tangent a degenerate tangent line
|
||||
// passes through the point of tangency of the two circles: this degenerate line is NOT returned.
|
||||
// Example(2D): Four tangents, first in green, second in black, third in blue, last in red.
|
||||
// passes through the point of tangency of the two circles: this degenerate line is NOT returned.
|
||||
// Example(2D): Four tangents, first in green, second in black, third in blue, last in red.
|
||||
// $fn=32;
|
||||
// c1 = [3,4]; r1 = 2;
|
||||
// c2 = [7,10]; r2 = 3;
|
||||
|
@ -1541,7 +1541,7 @@ function circle_point_tangents(r, d, cp, pt) =
|
|||
// move(c2) stroke(circle(r=r2), width=.1, closed=true);
|
||||
// colors = ["green","black","blue","red"];
|
||||
// for(i=[0:len(pts)-1]) color(colors[i]) stroke(pts[i],width=.1);
|
||||
// Example(2D): Circles are tangent. Only exterior tangents are returned. The degenerate internal tangent is not returned.
|
||||
// Example(2D): Circles are tangent. Only exterior tangents are returned. The degenerate internal tangent is not returned.
|
||||
// $fn=32;
|
||||
// c1 = [4,4]; r1 = 4;
|
||||
// c2 = [4,10]; r2 = 2;
|
||||
|
@ -1550,7 +1550,7 @@ function circle_point_tangents(r, d, cp, pt) =
|
|||
// move(c2) stroke(circle(r=r2), width=.1, closed=true);
|
||||
// colors = ["green","black","blue","red"];
|
||||
// for(i=[0:1:len(pts)-1]) color(colors[i]) stroke(pts[i],width=.1);
|
||||
// Example(2D): One circle is inside the other: no tangents exist. If the interior circle is tangent the single degenerate tangent will not be returned.
|
||||
// Example(2D): One circle is inside the other: no tangents exist. If the interior circle is tangent the single degenerate tangent will not be returned.
|
||||
// $fn=32;
|
||||
// c1 = [4,4]; r1 = 4;
|
||||
// c2 = [5,5]; r2 = 2;
|
||||
|
@ -1608,12 +1608,12 @@ function noncollinear_triple(points,error=true,eps=EPSILON) =
|
|||
[]
|
||||
: let(
|
||||
n = (pb-pa)/nrm,
|
||||
distlist = [for(i=[0:len(points)-1]) _dist2line(points[i]-pa, n)]
|
||||
distlist = [for(i=[0:len(points)-1]) _dist2line(points[i]-pa, n)]
|
||||
)
|
||||
max(distlist)<eps
|
||||
? assert(!error, "Cannot find three noncollinear points in pointlist.")
|
||||
[]
|
||||
: [0,b,max_index(distlist)];
|
||||
: [0,b,max_index(distlist)];
|
||||
|
||||
|
||||
// Function: pointlist_bounds()
|
||||
|
@ -1626,7 +1626,7 @@ function noncollinear_triple(points,error=true,eps=EPSILON) =
|
|||
// Arguments:
|
||||
// pts = List of points.
|
||||
function pointlist_bounds(pts) =
|
||||
assert(is_matrix(pts) && len(pts)>0 && len(pts[0])>0 , "Invalid pointlist." )
|
||||
assert(is_matrix(pts) && len(pts)>0 && len(pts[0])>0 , "Invalid pointlist." )
|
||||
let(ptsT = transpose(pts))
|
||||
[
|
||||
[for(row=ptsT) min(row)],
|
||||
|
@ -1669,7 +1669,7 @@ function furthest_point(pt, points) =
|
|||
// Usage:
|
||||
// area = polygon_area(poly);
|
||||
// Description:
|
||||
// Given a 2D or 3D planar polygon, returns the area of that polygon.
|
||||
// Given a 2D or 3D planar polygon, returns the area of that polygon.
|
||||
// If the polygon is self-crossing, the results are undefined. For non-planar points the result is undef.
|
||||
// When `signed` is true, a signed area is returned; a positive area indicates a counterclockwise polygon.
|
||||
// Arguments:
|
||||
|
@ -1678,14 +1678,23 @@ function furthest_point(pt, points) =
|
|||
function polygon_area(poly, signed=false) =
|
||||
assert(is_path(poly), "Invalid polygon." )
|
||||
len(poly)<3 ? 0 :
|
||||
let( cpoly = close_path(simplify_path(poly)) )
|
||||
len(poly[0])==2
|
||||
? sum([for(i=[1:1:len(poly)-2]) cross(poly[i]-poly[0],poly[i+1]-poly[0]) ])/2
|
||||
: let( plane = plane_from_points(poly) )
|
||||
plane==undef? undef :
|
||||
let( n = unit(plane_normal(plane)),
|
||||
total = sum([for(i=[1:1:len(poly)-1]) cross(poly[i]-poly[0],poly[i+1]-poly[0])*n ])/2
|
||||
)
|
||||
signed ? total : abs(total);
|
||||
? sum([for(i=[1:1:len(poly)-2]) cross(poly[i]-poly[0],poly[i+1]-poly[0]) ])/2
|
||||
: let( plane = plane_from_points(poly) )
|
||||
plane==undef? undef :
|
||||
let(
|
||||
n = unit(plane_normal(plane)),
|
||||
total = sum([
|
||||
for(i=[1:1:len(cpoly)-2])
|
||||
let(
|
||||
v1 = cpoly[i] - cpoly[0],
|
||||
v2 = cpoly[i+1] - cpoly[0]
|
||||
)
|
||||
cross(v1,v2) * n
|
||||
])/2
|
||||
)
|
||||
signed ? total : abs(total);
|
||||
|
||||
|
||||
// Function: is_convex_polygon()
|
||||
|
@ -1693,7 +1702,7 @@ function polygon_area(poly, signed=false) =
|
|||
// is_convex_polygon(poly);
|
||||
// Description:
|
||||
// Returns true if the given 2D polygon is convex. The result is meaningless if the polygon is not simple (self-intersecting).
|
||||
// If the points are collinear the result is true.
|
||||
// If the points are collinear the result is true.
|
||||
// Example:
|
||||
// is_convex_polygon(circle(d=50)); // Returns: true
|
||||
// Example:
|
||||
|
@ -1702,14 +1711,14 @@ function polygon_area(poly, signed=false) =
|
|||
function is_convex_polygon(poly) =
|
||||
assert(is_path(poly,dim=2), "The input should be a 2D polygon." )
|
||||
let( l = len(poly) )
|
||||
len([for( i = l-1,
|
||||
c = cross(poly[(i+1)%l]-poly[i], poly[(i+2)%l]-poly[(i+1)%l]),
|
||||
len([for( i = l-1,
|
||||
c = cross(poly[(i+1)%l]-poly[i], poly[(i+2)%l]-poly[(i+1)%l]),
|
||||
s = sign(c);
|
||||
i>=0 && sign(c)==s;
|
||||
i = i-1,
|
||||
c = i<0? 0: cross(poly[(i+1)%l]-poly[i],poly[(i+2)%l]-poly[(i+1)%l]),
|
||||
i = i-1,
|
||||
c = i<0? 0: cross(poly[(i+1)%l]-poly[i],poly[(i+2)%l]-poly[(i+1)%l]),
|
||||
s = s==0 ? sign(c) : s
|
||||
) i
|
||||
) i
|
||||
])== l;
|
||||
|
||||
|
||||
|
@ -1748,7 +1757,7 @@ function polygon_shift_to_closest_point(poly, pt) =
|
|||
// newpoly = reindex_polygon(reference, poly);
|
||||
// Description:
|
||||
// Rotates and possibly reverses the point order of a 2d or 3d polygon path to optimize its pairwise point
|
||||
// association with a reference polygon. The two polygons must have the same number of vertices and be the same dimension.
|
||||
// association with a reference polygon. The two polygons must have the same number of vertices and be the same dimension.
|
||||
// The optimization is done by computing the distance, norm(reference[i]-poly[i]), between
|
||||
// corresponding pairs of vertices of the two polygons and choosing the polygon point order that
|
||||
// makes the total sum over all pairs as small as possible. Returns the reindexed polygon. Note
|
||||
|
@ -1772,7 +1781,7 @@ function polygon_shift_to_closest_point(poly, pt) =
|
|||
// move_copies(concat(circ,pent)) circle(r=.1,$fn=32);
|
||||
// color("red") move_copies([pent[0],circ[0]]) circle(r=.1,$fn=32);
|
||||
// color("blue") translate(reindexed[0])circle(r=.1,$fn=32);
|
||||
function reindex_polygon(reference, poly, return_error=false) =
|
||||
function reindex_polygon(reference, poly, return_error=false) =
|
||||
assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
|
||||
"Invalid polygon(s) or incompatible dimensions. " )
|
||||
assert(len(reference)==len(poly), "The polygons must have the same length.")
|
||||
|
@ -1784,14 +1793,14 @@ function reindex_polygon(reference, poly, return_error=false) =
|
|||
? clockwise_polygon(poly)
|
||||
: ccw_polygon(poly),
|
||||
I = [for(i=[0:N-1]) 1],
|
||||
val = [ for(k=[0:N-1])
|
||||
[for(i=[0:N-1])
|
||||
val = [ for(k=[0:N-1])
|
||||
[for(i=[0:N-1])
|
||||
(reference[i]*poly[(i+k)%N]) ] ]*I,
|
||||
optimal_poly = polygon_shift(fixpoly, max_index(val))
|
||||
)
|
||||
return_error? [optimal_poly, min(poly*(I*poly)-2*val)] :
|
||||
optimal_poly;
|
||||
|
||||
|
||||
|
||||
// Function: align_polygon()
|
||||
// Usage:
|
||||
|
@ -1802,11 +1811,11 @@ function reindex_polygon(reference, poly, return_error=false) =
|
|||
// so if run time is a problem, use a smaller sampling of angles. Returns the rotated and reindexed
|
||||
// polygon.
|
||||
// Arguments:
|
||||
// reference = reference polygon
|
||||
// reference = reference polygon
|
||||
// poly = polygon to rotate into alignment with the reference
|
||||
// angles = list or range of angles to test
|
||||
// cp = centerpoint for rotations
|
||||
// Example(2D): The original hexagon in yellow is not well aligned with the pentagon. Turning it so the faces line up gives an optimal alignment, shown in red.
|
||||
// Example(2D): The original hexagon in yellow is not well aligned with the pentagon. Turning it so the faces line up gives an optimal alignment, shown in red.
|
||||
// $fn=32;
|
||||
// pentagon = subdivide_path(pentagon(side=2),60);
|
||||
// hexagon = subdivide_path(hexagon(side=2.7),60);
|
||||
|
@ -1860,7 +1869,7 @@ function centroid(poly) =
|
|||
] )
|
||||
)
|
||||
val[1]/val[0]/3;
|
||||
|
||||
|
||||
|
||||
// Function: point_in_polygon()
|
||||
// Usage:
|
||||
|
@ -1888,30 +1897,30 @@ function point_in_polygon(point, poly, eps=EPSILON, nonzero=true) =
|
|||
assert( is_finite(eps) && eps>=0, "Invalid tolerance." )
|
||||
// Does the point lie on any edges? If so return 0.
|
||||
let(
|
||||
on_brd = [for(i=[0:1:len(poly)-1])
|
||||
let( seg = select(poly,i,i+1) )
|
||||
if( !approx(seg[0],seg[1],eps=EPSILON) )
|
||||
on_brd = [for(i=[0:1:len(poly)-1])
|
||||
let( seg = select(poly,i,i+1) )
|
||||
if( !approx(seg[0],seg[1],eps=EPSILON) )
|
||||
point_on_segment2d(point, seg, eps=eps)? 1:0 ]
|
||||
)
|
||||
sum(on_brd) > 0
|
||||
? 0
|
||||
? 0
|
||||
: nonzero
|
||||
? // Compute winding number and return 1 for interior, -1 for exterior
|
||||
let(
|
||||
windchk = [for(i=[0:1:len(poly)-1])
|
||||
let(seg=select(poly,i,i+1))
|
||||
if(!approx(seg[0],seg[1],eps=eps))
|
||||
windchk = [for(i=[0:1:len(poly)-1])
|
||||
let(seg=select(poly,i,i+1))
|
||||
if(!approx(seg[0],seg[1],eps=eps))
|
||||
_point_above_below_segment(point, seg)
|
||||
]
|
||||
)
|
||||
sum(windchk) != 0 ? 1 : -1
|
||||
: // or compute the crossings with the ray [point, point+[1,0]]
|
||||
let(
|
||||
let(
|
||||
n = len(poly),
|
||||
cross =
|
||||
cross =
|
||||
[for(i=[0:n-1])
|
||||
let(
|
||||
p0 = poly[i]-point,
|
||||
let(
|
||||
p0 = poly[i]-point,
|
||||
p1 = poly[(i+1)%n]-point
|
||||
)
|
||||
if( ( (p1.y>eps && p0.y<=0) || (p1.y<=0 && p0.y>eps) )
|
||||
|
@ -1970,7 +1979,7 @@ function reverse_polygon(poly) =
|
|||
// n = polygon_normal(poly);
|
||||
// Description:
|
||||
// Given a 3D planar polygon, returns a unit-length normal vector for the
|
||||
// clockwise orientation of the polygon.
|
||||
// clockwise orientation of the polygon.
|
||||
function polygon_normal(poly) =
|
||||
assert(is_path(poly,dim=3), "Invalid 3D polygon." )
|
||||
let(
|
||||
|
@ -2082,9 +2091,8 @@ function _split_polygon_at_z(poly, z) =
|
|||
// polys = A list of 3D polygons to split.
|
||||
// xs = A list of scalar X values to split at.
|
||||
function split_polygons_at_each_x(polys, xs, _i=0) =
|
||||
assert( is_consistent(polys) && is_path(poly[0],dim=3) ,
|
||||
"The input list should contains only 3D polygons." )
|
||||
assert( is_finite(xs), "The split value list should contain only numbers." )
|
||||
assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.")
|
||||
assert( is_vector(xs), "The split value list should contain only numbers." )
|
||||
_i>=len(xs)? polys :
|
||||
split_polygons_at_each_x(
|
||||
[
|
||||
|
@ -2092,7 +2100,7 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
|
|||
each _split_polygon_at_x(poly, xs[_i])
|
||||
], xs, _i=_i+1
|
||||
);
|
||||
|
||||
|
||||
|
||||
// Function: split_polygons_at_each_y()
|
||||
// Usage:
|
||||
|
@ -2103,9 +2111,8 @@ function split_polygons_at_each_x(polys, xs, _i=0) =
|
|||
// polys = A list of 3D polygons to split.
|
||||
// ys = A list of scalar Y values to split at.
|
||||
function split_polygons_at_each_y(polys, ys, _i=0) =
|
||||
// assert( is_consistent(polys) && is_path(polys[0],dim=3) , // not all polygons should have the same length!!!
|
||||
// "The input list should contains only 3D polygons." )
|
||||
assert( is_finite(ys) || is_vector(ys), "The split value list should contain only numbers." ) //***
|
||||
assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.")
|
||||
assert( is_vector(ys), "The split value list should contain only numbers." )
|
||||
_i>=len(ys)? polys :
|
||||
split_polygons_at_each_y(
|
||||
[
|
||||
|
@ -2124,9 +2131,8 @@ function split_polygons_at_each_y(polys, ys, _i=0) =
|
|||
// polys = A list of 3D polygons to split.
|
||||
// zs = A list of scalar Z values to split at.
|
||||
function split_polygons_at_each_z(polys, zs, _i=0) =
|
||||
assert( is_consistent(polys) && is_path(poly[0],dim=3) ,
|
||||
"The input list should contains only 3D polygons." )
|
||||
assert( is_finite(zs), "The split value list should contain only numbers." )
|
||||
assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.")
|
||||
assert( is_vector(zs), "The split value list should contain only numbers." )
|
||||
_i>=len(zs)? polys :
|
||||
split_polygons_at_each_z(
|
||||
[
|
||||
|
|
|
@ -349,11 +349,16 @@ function _rounding_offsets(edgespec,z_dir=1) =
|
|||
chamf_angle = struct_val(edgespec, "angle"),
|
||||
cheight = struct_val(edgespec, "chamfer_height"),
|
||||
cwidth = struct_val(edgespec, "chamfer_width"),
|
||||
chamf_width = first_defined([cut/cos(chamf_angle), cwidth, cheight*tan(chamf_angle)]),
|
||||
chamf_height = first_defined([cut/sin(chamf_angle),cheight, cwidth/tan(chamf_angle)]),
|
||||
chamf_width = first_defined([!all_defined([cut,chamf_angle]) ? undef : cut/cos(chamf_angle),
|
||||
cwidth,
|
||||
!all_defined([cheight,chamf_angle]) ? undef : cheight*tan(chamf_angle)]),
|
||||
chamf_height = first_defined([
|
||||
!all_defined([cut,chamf_angle]) ? undef : cut/sin(chamf_angle),
|
||||
cheight,
|
||||
!all_defined([cwidth, chamf_angle]) ? undef : cwidth/tan(chamf_angle)]),
|
||||
joint = first_defined([
|
||||
struct_val(edgespec,"joint"),
|
||||
16*cut/sqrt(2)/(1+4*k)
|
||||
all_defined([cut,k]) ? 16*cut/sqrt(2)/(1+4*k) : undef
|
||||
]),
|
||||
points = struct_val(edgespec, "points"),
|
||||
argsOK = in_list(edgetype,["circle","teardrop"])? is_def(radius) :
|
||||
|
@ -365,7 +370,7 @@ function _rounding_offsets(edgespec,z_dir=1) =
|
|||
assert(argsOK,str("Invalid specification with type ",edgetype))
|
||||
let(
|
||||
offsets =
|
||||
edgetype == "profile"? scale([-1,z_dir], slice(points,1,-1)) :
|
||||
edgetype == "profile"? scale([-1,z_dir], p=slice(points,1,-1)) :
|
||||
edgetype == "chamfer"? chamf_width==0 && chamf_height==0? [] : [[-chamf_width,z_dir*abs(chamf_height)]] :
|
||||
edgetype == "teardrop"? (
|
||||
radius==0? [] : concat(
|
||||
|
@ -380,6 +385,7 @@ function _rounding_offsets(edgespec,z_dir=1) =
|
|||
1, -1
|
||||
)
|
||||
)
|
||||
|
||||
quant(extra > 0? concat(offsets, [select(offsets,-1)+[0,z_dir*extra]]) : offsets, 1/1024);
|
||||
|
||||
|
||||
|
@ -915,7 +921,7 @@ function offset_sweep(
|
|||
&& in_list(struct_val(bottom, "offset"),["round","delta"])
|
||||
)
|
||||
assert(offsetsok,"Offsets must be one of \"round\" or \"delta\"")
|
||||
let(
|
||||
let(
|
||||
offsets_bot = _rounding_offsets(bottom, -1),
|
||||
offsets_top = _rounding_offsets(top, 1),
|
||||
dummy = offset == "chamfer" && (len(offsets_bot)>5 || len(offsets_top)>5)
|
||||
|
|
|
@ -19,7 +19,7 @@ done
|
|||
if [[ "$FILES" != "" ]]; then
|
||||
PREVIEW_LIBS="$FILES"
|
||||
else
|
||||
PREVIEW_LIBS="affine arrays attachments beziers bottlecaps common constants coords cubetruss debug distributors edges geometry hingesnaps hull involute_gears joiners knurling linear_bearings masks math metric_screws mutators nema_steppers partitions paths phillips_drive polyhedra primitives quaternions queues regions rounding screws shapes shapes2d skin sliders stacks strings structs threading torx_drive transforms triangulation vectors version vnf walls wiring"
|
||||
PREVIEW_LIBS=$(git ls-files | grep '\.scad$' | grep -v / | grep -v -f .nodocsfiles)
|
||||
fi
|
||||
|
||||
dir="$(basename $PWD)"
|
||||
|
|
|
@ -18,8 +18,9 @@ for testscript in $INFILES ; do
|
|||
testfile="tests/test_$repname"
|
||||
if [ -f "$testfile" ] ; then
|
||||
${OPENSCAD} -o out.echo --hardwarnings --check-parameters true --check-parameter-ranges true $testfile 2>&1
|
||||
retcode=$?
|
||||
res=$(cat out.echo)
|
||||
if [ "$res" = "" ] ; then
|
||||
if [ $retcode -eq 0 ] && [ "$res" = "" ] ; then
|
||||
echo "$repname: PASS"
|
||||
else
|
||||
echo "$repname: FAIL!"
|
||||
|
|
|
@ -795,13 +795,13 @@ module xcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h
|
|||
// ycyl(l=35, d=20);
|
||||
// ycyl(l=35, d1=30, d2=10);
|
||||
// }
|
||||
module ycyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, anchor=CENTER)
|
||||
module ycyl(l, r, d, r1, r2, d1, d2, h, anchor=CENTER)
|
||||
{
|
||||
r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
|
||||
r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
|
||||
l = first_defined([l, h, 1]);
|
||||
attachable(anchor,0,UP, r1=r1, r2=r2, l=l, axis=BACK) {
|
||||
cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=BACK, anchor=CENTER);
|
||||
cyl(l=l, h=h, r1=r1, r2=r2, orient=BACK, anchor=CENTER);
|
||||
children();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -350,7 +350,7 @@ module stroke(
|
|||
// N = Number of vertices to form the arc curve from.
|
||||
// r = Radius of the arc.
|
||||
// d = Diameter of the arc.
|
||||
// angle = If a scalar, specifies the end angle in degrees. If a vector of two scalars, specifies start and end angles.
|
||||
// angle = If a scalar, specifies the end angle in degrees (relative to start parameter). If a vector of two scalars, specifies start and end angles.
|
||||
// cp = Centerpoint of arc.
|
||||
// points = Points on the arc.
|
||||
// long = if given with cp and points takes the long arc instead of the default short arc. Default: false
|
||||
|
@ -360,6 +360,7 @@ module stroke(
|
|||
// thickness = If given with `width`, arc starts and ends on X axis, to make a circle segment.
|
||||
// start = Start angle of arc.
|
||||
// wedge = If true, include centerpoint `cp` in output to form pie slice shape.
|
||||
// endpoint = If false exclude the last point (function only). Default: true
|
||||
// Examples(2D):
|
||||
// arc(N=4, r=30, angle=30, wedge=true);
|
||||
// arc(r=30, angle=30, wedge=true);
|
||||
|
@ -378,7 +379,10 @@ module stroke(
|
|||
// Example(FlatSpin):
|
||||
// path = arc(points=[[0,30,0],[0,0,30],[30,0,0]]);
|
||||
// trace_path(path, showpts=true, color="cyan");
|
||||
function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false) =
|
||||
function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false, endpoint=true) =
|
||||
assert(is_bool(endpoint))
|
||||
!endpoint ? assert(!wedge, "endpoint cannot be false if wedge is true")
|
||||
slice(arc(N,r,angle,d,cp,points,width,thickness,start,wedge,long,cw,ccw,true),0,-2) :
|
||||
// First try for 2D arc specified by width and thickness
|
||||
is_def(width) && is_def(thickness)? (
|
||||
assert(!any_defined([r,cp,points]) && !any([cw,ccw,long]),"Conflicting or invalid parameters to arc")
|
||||
|
@ -472,7 +476,7 @@ function _normal_segment(p1,p2) =
|
|||
|
||||
// Function: turtle()
|
||||
// Usage:
|
||||
// turtle(commands, [state], [full_state], [repeat])
|
||||
// turtle(commands, [state], [full_state], [repeat], [endpoint])
|
||||
// Description:
|
||||
// Use a sequence of turtle graphics commands to generate a path. The parameter `commands` is a list of
|
||||
// turtle commands and optional parameters for each command. The turtle state has a position, movement direction,
|
||||
|
|
|
@ -27,6 +27,21 @@ module test_select() {
|
|||
}
|
||||
test_select();
|
||||
|
||||
module test_last() {
|
||||
list = [1,2,3,4];
|
||||
assert(last(list)==4);
|
||||
assert(last([])==undef);
|
||||
}
|
||||
test_last();
|
||||
|
||||
module test_delete_last() {
|
||||
list = [1,2,3,4];
|
||||
assert(delete_last(list) == [1,2,3]);
|
||||
assert(delete_last([1]) == []);
|
||||
assert(delete_last([]) == []);
|
||||
}
|
||||
test_delete_last();
|
||||
|
||||
|
||||
module test_slice() {
|
||||
assert(slice([3,4,5,6,7,8,9], 3, 5) == [6,7]);
|
||||
|
|
|
@ -40,6 +40,7 @@ test_turtle();
|
|||
|
||||
module test_arc() {
|
||||
assert_approx(arc(N=8, d=100, angle=135, cp=[10,10]), [[60,10],[57.1941665154,26.5139530978],[49.0915741234,41.1744900929],[36.6016038258,52.3362099614],[21.1260466978,58.7463956091],[4.40177619483,59.6856104947],[-11.6941869559,55.0484433951],[-25.3553390593,45.3553390593]]);
|
||||
assert_approx(arc(N=8, d=100, angle=135, cp=[10,10],endpoint=false), [[60,10],[57.1941665154,26.5139530978],[49.0915741234,41.1744900929],[36.6016038258,52.3362099614],[21.1260466978,58.7463956091],[4.40177619483,59.6856104947],[-11.6941869559,55.0484433951]]);
|
||||
assert_approx(arc(N=8, d=100, angle=[45,225], cp=[10,10]), [[45.3553390593,45.3553390593],[26.5139530978,57.1941665154],[4.40177619483,59.6856104947],[-16.6016038258,52.3362099614],[-32.3362099614,36.6016038258],[-39.6856104947,15.5982238052],[-37.1941665154,-6.51395309776],[-25.3553390593,-25.3553390593]]);
|
||||
assert_approx(arc(N=8, d=100, start=45, angle=135, cp=[10,10]), [[45.3553390593,45.3553390593],[31.6941869559,55.0484433951],[15.5982238052,59.6856104947],[-1.12604669782,58.7463956091],[-16.6016038258,52.3362099614],[-29.0915741234,41.1744900929],[-37.1941665154,26.5139530978],[-40,10]]);
|
||||
assert_approx(arc(N=8, d=100, start=45, angle=-90, cp=[10,10]), [[45.3553390593,45.3553390593],[52.3362099614,36.6016038258],[57.1941665154,26.5139530978],[59.6856104947,15.5982238052],[59.6856104947,4.40177619483],[57.1941665154,-6.51395309776],[52.3362099614,-16.6016038258],[45.3553390593,-25.3553390593]]);
|
||||
|
|
|
@ -116,10 +116,13 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// "arcrot" |x | radius, rotation | Draw an arc turning by the specified absolute rotation with given radius
|
||||
// "arctodir" |x | radius, vector | Draw an arc turning to point in the (absolute) direction of given vector
|
||||
// "arcsteps" |x | count | Specifies the number of segments to use for drawing arcs. If you set it to zero then the standard `$fn`, `$fa` and `$fs` variables define the number of segments.
|
||||
//
|
||||
// Compound commands are lists that group multiple commands to be applied simultaneously during a turtle movement. Example: `["move", 5, "shrink", 2]`. The subcommands that may appear are listed below. Each command command
|
||||
// must begin with either "move" or "arc". The order of subcommands is not important. Left/right turning is applied before up/down. You cannot combine "rot" or "todir" with any other turning commands.
|
||||
//
|
||||
// .
|
||||
// Compound commands are lists that group multiple commands to be applied simultaneously during a
|
||||
// turtle movement. Example: `["move", 5, "shrink", 2]`. The subcommands that may appear are
|
||||
// listed below. Each compound command must begin with either "move" or "arc". The order of
|
||||
// subcommands is not important. Left/right turning is applied before up/down. You cannot combine
|
||||
// "rot" or "todir" with any other turning commands.
|
||||
// .
|
||||
// Subcommands | Arguments | What it does
|
||||
// ------------ | ------------------ | -------------------------------
|
||||
// "move" | dist | Compound command is a forward movement operation
|
||||
|
@ -146,40 +149,39 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// "down", "left" or "right" alone then you can give any angle, but if you combine "up"/"down" with "left"/"right" then the specified
|
||||
// angles must be smaller than 180 degrees. (This is because the algorithm decodes the rotation into an angle smaller than 180, so
|
||||
// the results are very strange if larger angles are permitted.)
|
||||
// .
|
||||
// Arguments:
|
||||
// commands = List of turtle3d commands
|
||||
// state = Starting turtle direction or full turtle state (from a previous call). Default: RIGHT
|
||||
// transforms = If true teturn list of transformations instead of points. Default: false
|
||||
// full_state = If true return full turtle state for continuing the path in subsequent turtle calls. Default: false
|
||||
// repeat = Number of times to repeat the command list. Default: 1
|
||||
// Example: Angled rectangle
|
||||
// Example(3D): Angled rectangle
|
||||
// path = turtle3d(["up",25,"move","left","move",3,"left","move"]);
|
||||
// stroke(path,closed=true, width=.2);
|
||||
// Example: Path with rounded corners. Note first and last point of the path are duplicates.
|
||||
// Example(3D): Path with rounded corners. Note first and last point of the path are duplicates.
|
||||
// r = 0.25;
|
||||
// path = turtle3d(["up",25,"move","arcleft",r,"move",3,"arcleft",r,"move","arcleft",r,"move",3,"arcleft",r]);
|
||||
// stroke(path,closed=true, width=.2);
|
||||
// Example: Non-coplanar figure
|
||||
// Example(3D): Non-coplanar figure
|
||||
// path = turtle3d(["up",25,"move","left","move",3,"up","left",0,"move"]);
|
||||
// stroke(path,closed=true, width=.2);
|
||||
// Example: Square spiral. Note that the core twists because the "up" and "left" turns are relative to the previous turns.
|
||||
// Example(3D): Square spiral. Note that the core twists because the "up" and "left" turns are relative to the previous turns.
|
||||
// include<BOSL2/skin.scad>
|
||||
// path = turtle3d(["move",10,"left","up",15],repeat=50);
|
||||
// path_sweep(circle(d=1, $fn=12), path);
|
||||
// Example: Square spiral, second try. Use roll to create the spiral instead of turning up. It still twists because the left turns are inclined.
|
||||
// Example(3D): Square spiral, second try. Use roll to create the spiral instead of turning up. It still twists because the left turns are inclined.
|
||||
// include<BOSL2/skin.scad>
|
||||
// path = turtle3d(["move",10,"left","roll",10],repeat=50);
|
||||
// path_sweep(circle(d=1, $fn=12), path);
|
||||
// Example: Square spiral, third try. One way to avoid the core twisting in the spiral is to use absolute turns. Note that the vertical rise is controlled by the starting upward angle of the turtle, which is preserved as we rotate around the z axis.
|
||||
// Example(3D): Square spiral, third try. One way to avoid the core twisting in the spiral is to use absolute turns. Note that the vertical rise is controlled by the starting upward angle of the turtle, which is preserved as we rotate around the z axis.
|
||||
// include<BOSL2/skin.scad>
|
||||
// path = turtle3d(["up", 5, "repeat", 12, ["move",10,"zrot"]]);
|
||||
// path_sweep(circle(d=1, $fn=12), path);
|
||||
// Example: Square spiral, rounded corners. Careful use of rotations can work for sweep, but it may be better to round the corners. Here we return a list of transforms and use sweep instead of path_sweep:
|
||||
// Example(3D): Square spiral, rounded corners. Careful use of rotations can work for sweep, but it may be better to round the corners. Here we return a list of transforms and use sweep instead of path_sweep:
|
||||
// include<BOSL2/skin.scad>
|
||||
// path = turtle3d(["up", 5, "repeat", 12, ["move",10,"arczrot",4]],transforms=true);
|
||||
// sweep(circle(d=1, $fn=12), path);
|
||||
// Example: Mixing relative and absolute commands
|
||||
// Example(3D): Mixing relative and absolute commands
|
||||
// include<BOSL2/skin.scad>
|
||||
// path = turtle3d(["repeat", 4, ["move",80,"arczrot",40],
|
||||
// "arcyrot",40,-90,
|
||||
|
@ -196,7 +198,7 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// state=[1,0,.2],transforms=true);
|
||||
// ushape = rot(90,p=[[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]);
|
||||
// sweep(ushape, path);
|
||||
// Example: Generic helix, constructed by a sequence of movements and then rotations
|
||||
// Example(3D): Generic helix, constructed by a sequence of movements and then rotations
|
||||
// include<BOSL2/skin.scad>
|
||||
// radius=14; // Helix radius
|
||||
// pitch=20; // Distance from one turn to the next
|
||||
|
@ -215,7 +217,7 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// ],
|
||||
// ], transforms=true);
|
||||
// sweep(subdivide_path(square([5,1]),20), helix);
|
||||
// Example: Helix generated by a single command. Note this only works for x, y, or z aligned helixes because the generic rot cannot handle multi-turn angles.
|
||||
// Example(3D): Helix generated by a single command. Note this only works for x, y, or z aligned helixes because the generic rot cannot handle multi-turn angles.
|
||||
// include<BOSL2/skin.scad>
|
||||
// pitch=20; // Distance from one turn to the next
|
||||
// radius=14; // Helix radius
|
||||
|
@ -231,11 +233,11 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// ]
|
||||
// ], transforms=true);
|
||||
// sweep(subdivide_path(square([5,1]),80), helix);
|
||||
// Example: Expanding helix
|
||||
// Example(3D): Expanding helix
|
||||
// include<BOSL2/skin.scad>
|
||||
// path = turtle3d(["length",.2,"angle",360/20,"up",5,"repeat",50,["move","zrot","addlength",0.05]]);
|
||||
// path_sweep(circle(d=1, $fn=12), path);
|
||||
// Example: Adding some twist to the model
|
||||
// Example(3D): Adding some twist to the model
|
||||
// include<BOSL2/skin.scad>
|
||||
// r = 2.5;
|
||||
// trans = turtle3d(["move",10,
|
||||
|
@ -248,7 +250,7 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// "arcleft",r],
|
||||
// state=yrot(25,p=RIGHT),transforms=true);
|
||||
// sweep(supershape(m1=4,n1=4,n2=16,n3=1.5,a=.9,b=9,step=5),trans);
|
||||
// Example: Twist does not change the turtle orientation, but roll does. The only change from the previous example is twist was changed to roll.
|
||||
// Example(3D): Twist does not change the turtle orientation, but roll does. The only change from the previous example is twist was changed to roll.
|
||||
// include<BOSL2/skin.scad>
|
||||
// r = 2;
|
||||
// trans = turtle3d(["move",10,
|
||||
|
@ -261,7 +263,7 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// "arcleft",r],
|
||||
// state=yrot(25,p=RIGHT),transforms=true);
|
||||
// sweep(supershape(m1=4,n1=4,n2=16,n3=1.5,a=.9,b=9,step=5),trans);
|
||||
// Example: Use of shrink and grow
|
||||
// Example(3D): Use of shrink and grow
|
||||
// include<BOSL2/skin.scad>
|
||||
// $fn=32;
|
||||
// T = turtle3d([
|
||||
|
@ -276,7 +278,7 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// "untily", -1,
|
||||
// ],state=RIGHT, transforms=true);
|
||||
// sweep(square(2,center=true),T);
|
||||
// Example: After several moves you may not understand the turtle orientation. An absolute reorientation with "arctodir" is helpful to head in a known direction
|
||||
// Example(3D): After several moves you may not understand the turtle orientation. An absolute reorientation with "arctodir" is helpful to head in a known direction
|
||||
// include<BOSL2/skin.scad>
|
||||
// trans = turtle3d([
|
||||
// "move",5,
|
||||
|
@ -293,7 +295,7 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// "untilz",0
|
||||
// ],transforms=true);
|
||||
// sweep(square(1,center=true),trans);
|
||||
// Example: The "grow" and "shrink" commands can take a vector giving x and y scaling
|
||||
// Example(3D): The "grow" and "shrink" commands can take a vector giving x and y scaling
|
||||
// include<BOSL2/skin.scad>
|
||||
// tr = turtle3d([
|
||||
// "move", 1.5,
|
||||
|
@ -301,7 +303,7 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// ["move", 5, "grow", [2,0.5],"steps", 10]
|
||||
// ], transforms=true);
|
||||
// sweep(circle($fn=32,r=1), tr);
|
||||
// Example: With "twist" added the anisotropic "grow" interacts with "twist", producing a complex form
|
||||
// Example(3D): With "twist" added the anisotropic "grow" interacts with "twist", producing a complex form
|
||||
// include<BOSL2/skin.scad>
|
||||
// tr = turtle3d([
|
||||
// "move", 1.5,
|
||||
|
@ -309,7 +311,7 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// ["move", 5, "grow", [0.5,2],"steps", 20, "twist",90]
|
||||
// ], transforms=true);
|
||||
// sweep(circle($fn=64,r=1), tr);
|
||||
// Example: Making a tube with "reverse". Note that the move direction is the same even though the direction is reversed.
|
||||
// Example(3D): Making a tube with "reverse". Note that the move direction is the same even though the direction is reversed.
|
||||
// include<BOSL2/skin.scad>
|
||||
// tr = turtle3d([ "move", 4,
|
||||
// ["move",0, "grow", .8, "reverse"],
|
||||
|
@ -317,7 +319,7 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// ], transforms=true);
|
||||
// back_half(s=10)
|
||||
// sweep(circle(r=1,$fn=16), tr, closed=true);
|
||||
// Example: To close the tube at one end we set closed to false in sweep.
|
||||
// Example(3D): To close the tube at one end we set closed to false in sweep.
|
||||
// include<BOSL2/skin.scad>
|
||||
// tr = turtle3d([ "move", 4,
|
||||
// ["move",0, "grow", .8, "reverse"],
|
||||
|
@ -325,7 +327,7 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// ], transforms=true);
|
||||
// back_half(s=10)
|
||||
// sweep(circle(r=1,$fn=16), tr, closed=false);
|
||||
// Example: Cookie cutter using "reverse"
|
||||
// Example(3D): Cookie cutter using "reverse"
|
||||
// include<BOSL2/skin.scad>
|
||||
// cutter = turtle3d( [
|
||||
// ["move", 10, "shrink", 1.3, ],
|
||||
|
@ -334,7 +336,7 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// ], transforms=true,state=UP);
|
||||
// cookie_shape = star(5, r=10, ir=5);
|
||||
// sweep(cookie_shape, cutter, closed=true);
|
||||
// Example: angled shopvac adapter. Shopvac tubing wedges together because the tubes are slightly tapered. We can make this part without using any difference() operations by using "reverse" to trace out the interior portion of the part. Note that it's "arcright" even when reversed.
|
||||
// Example(3D): angled shopvac adapter. Shopvac tubing wedges together because the tubes are slightly tapered. We can make this part without using any difference() operations by using "reverse" to trace out the interior portion of the part. Note that it's "arcright" even when reversed.
|
||||
// include<BOSL2/skin.scad>
|
||||
// inch = 25.4;
|
||||
// insert_ID = 2.3*inch; // Size of shopvac tube at larger end of taper
|
||||
|
@ -360,9 +362,9 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// ["move", seg1_len, "grow", seg1_bot_ID/seg2_bot_ID]
|
||||
// ],
|
||||
// state=UP, transforms=true);
|
||||
// zrot(90)back_half() // Remove this to get a usable part
|
||||
// back_half() // Remove this to get a usable part
|
||||
// sweep(circle(d=seg1_bot_OD, $fn=128), trans, closed=true);
|
||||
// Example: Closed spiral
|
||||
// Example(3D): Closed spiral
|
||||
// include<BOSL2/skin.scad>
|
||||
// steps = 500;
|
||||
// spiral = turtle3d([
|
||||
|
@ -382,11 +384,11 @@ function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
|
|||
// "grow",1.5],
|
||||
// ], transforms=true);
|
||||
// sweep(fwd(25,p=circle(r=2,$fn=24)), spiral, caps=false);
|
||||
// Example: Mobius strip (square)
|
||||
// Example(3D): Mobius strip (square)
|
||||
// include<BOSL2/skin.scad>
|
||||
// mobius = turtle3d([["arc", 20, "zrot", 360,"steps",100,"twist",180]], transforms=true);
|
||||
// sweep(subdivide_path(square(8,center=true),16), mobius, closed=false);
|
||||
// Example: Torus knot
|
||||
// Example(3D): Torus knot
|
||||
// include<BOSL2/skin.scad>
|
||||
// p = 3; // (number of turns)*gcd(p,q)
|
||||
// q = 10; // (number of dives)*gcd(p,q)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
BOSL_VERSION = [2,0,476];
|
||||
BOSL_VERSION = [2,0,480];
|
||||
|
||||
|
||||
// Section: BOSL Library Version Functions
|
||||
|
|
14
vnf.scad
14
vnf.scad
|
@ -672,8 +672,11 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
|
|||
],
|
||||
nonplanars = unique([
|
||||
for (face = faces) let(
|
||||
faceverts = [for (k=face) varr[k]]
|
||||
) if (!coplanar(faceverts)) [
|
||||
faceverts = [for (k=face) varr[k]],
|
||||
area = polygon_area(faceverts)
|
||||
)
|
||||
if (is_num(area) && abs(area) > EPSILON)
|
||||
if (!coplanar(faceverts)) [
|
||||
"ERROR",
|
||||
"NONPLANAR",
|
||||
"Face vertices are not coplanar",
|
||||
|
@ -712,9 +715,12 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
|
|||
if (v!=edge[0] && v!=edge[1]) let(
|
||||
a = varr[edge[0]],
|
||||
b = varr[v],
|
||||
c = varr[edge[1]],
|
||||
c = varr[edge[1]]
|
||||
)
|
||||
if (a != b && b != c && a != c) let(
|
||||
pt = segment_closest_point([a,c],b)
|
||||
) if (pt == b) [
|
||||
)
|
||||
if (pt == b) [
|
||||
"ERROR",
|
||||
"T_JUNCTION",
|
||||
"Vertex is mid-edge on another Face",
|
||||
|
|
Loading…
Reference in a new issue