Merge pull request #460 from revarbat/revarbat_dev

Revarbat dev
This commit is contained in:
Revar Desmera 2021-03-06 02:55:04 -08:00 committed by GitHub
commit ae8d638732
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 544 additions and 181 deletions

View file

@ -19,6 +19,172 @@
// Spline Steps = The number of straight-line segments to split a bezier segment into, to approximate the bezier curve. The more spline steps, the closer the approximation will be to the curve, but the slower it will be to generate. Usually defaults to 16.
// Section: Bezier Path Construction
// Function: bez_begin()
// Topics: Bezier Paths
// See Also: bez_tang(), bez_joint(), bez_end()
// Usage:
// pts = bez_begin(pt,a,r,<p=>);
// pts = bez_begin(pt,VECTOR,<r>,<p=>);
// Description:
// This is used to create the first endpoint and control point of a cubic bezier path.
// Arguments:
// pt = The starting endpoint for the bezier path.
// a = If given a scalar, specifies the theta (XY plane) angle in degrees from X+. If given a vector, specifies the direction and possibly distance of the first control point.
// r = Specifies the distance of the control point from the endpoint `pt`.
// p = If given, specifies the number of degrees away from the Z+ axis.
// Example(2D): 2D Bezier Path by Angle
// bezpath = flatten([
// bez_begin([-50, 0], 45,20),
// bez_tang ([ 0, 0],-135,20),
// bez_joint([ 20,-25], 135, 90, 10, 15),
// bez_end ([ 50, 0], -90,20),
// ]);
// trace_bezier(bezpath);
// Example(2D): 2D Bezier Path by Vector
// bezpath = flatten([
// bez_begin([-50,0],[0,-20]),
// bez_tang ([-10,0],[0,-20]),
// bez_joint([ 20,-25], [-10,10], [0,15]),
// bez_end ([ 50,0],[0, 20]),
// ]);
// trace_bezier(bezpath);
// Example(2D): 2D Bezier Path by Vector and Distance
// bezpath = flatten([
// bez_begin([-30,0],FWD, 30),
// bez_tang ([ 0,0],FWD, 30),
// bez_joint([ 20,-25], 135, 90, 10, 15),
// bez_end ([ 30,0],BACK,30),
// ]);
// trace_bezier(bezpath);
// Example(3D,FlatSpin,VPD=200): 3D Bezier Path by Angle
// bezpath = flatten([
// bez_begin([-30,0,0],90,20,p=135),
// bez_tang ([ 0,0,0],-90,20,p=135),
// bez_joint([20,-25,0], 135, 90, 15, 10, p1=135, p2=45),
// bez_end ([ 30,0,0],-90,20,p=45),
// ]);
// trace_bezier(bezpath);
// Example(3D,FlatSpin,VPD=225): 3D Bezier Path by Vector
// bezpath = flatten([
// bez_begin([-30,0,0],[0,-20, 20]),
// bez_tang ([ 0,0,0],[0,-20,-20]),
// bez_joint([20,-25,0],[0,10,-10],[0,15,15]),
// bez_end ([ 30,0,0],[0,-20,-20]),
// ]);
// trace_bezier(bezpath);
// Example(3D,FlatSpin,VPD=225): 3D Bezier Path by Vector and Distance
// bezpath = flatten([
// bez_begin([-30,0,0],FWD, 20),
// bez_tang ([ 0,0,0],DOWN,20),
// bez_joint([20,-25,0],LEFT,DOWN,r1=20,r2=15),
// bez_end ([ 30,0,0],DOWN,20),
// ]);
// trace_bezier(bezpath);
function bez_begin(pt,a,r,p) =
assert(is_finite(r) || is_vector(a))
assert(len(pt)==3 || is_undef(p))
is_vector(a)? [pt, pt+(is_undef(r)? a : r*unit(a))] :
is_finite(a)? [pt, pt+spherical_to_xyz(r,a,default(p,90))] :
assert(false, "Bad arguments.");
// Function: bez_tang()
// Topics: Bezier Paths
// See Also: bez_begin(), bez_joint(), bez_end()
// Usage:
// pts = bez_tang(pt,a,r1,r2,<p=>);
// pts = bez_tang(pt,VECTOR,<r1>,<r2>,<p=>);
// Description:
// This creates a smooth joint in a cubic bezier path. It creates three points, being the
// approaching control point, the fixed bezier control point, and the departing control
// point. The two control points will be collinear with the fixed point, making for a
// smooth bezier curve at the fixed point. See {{bez_begin()}} for examples.
// Arguments:
// pt = The fixed point for the bezier path.
// a = If given a scalar, specifies the theta (XY plane) angle in degrees from X+. If given a vector, specifies the direction and possibly distance of the departing control point.
// r1 = Specifies the distance of the approching control point from the fixed point. Overrides the distance component of the vector if `a` contains a vector.
// r2 = Specifies the distance of the departing control point from the fixed point. Overrides the distance component of the vector if `a` contains a vector. If `r1` is given and `r2` is not, uses the value of `r1` for `r2`.
// p = If given, specifies the number of degrees away from the Z+ axis.
function bez_tang(pt,a,r1,r2,p) =
assert(is_finite(r1) || is_vector(a))
assert(len(pt)==3 || is_undef(p))
let(
r1 = is_num(r1)? r1 : norm(a),
r2 = default(r2,r1),
p = default(p, 90)
)
is_vector(a)? [pt-r1*unit(a), pt, pt+r2*unit(a)] :
is_finite(a)? [
pt-spherical_to_xyz(r1,a,p),
pt,
pt+spherical_to_xyz(r2,a,p)
] :
assert(false, "Bad arguments.");
// Function: bez_joint()
// Topics: Bezier Paths
// See Also: bez_begin(), bez_tang(), bez_end()
// Usage:
// pts = bez_joint(pt,a1,a2,r1,r2,<p1=>,<p2=>);
// pts = bez_joint(pt,VEC1,VEC2,<r1=>,<r2=>,<p1=>,<p2=>);
// Description:
// This creates a disjoint corner joint in a cubic bezier path. It creates three points, being
// the aproaching control point, the fixed bezier control point, and the departing control point.
// The two control points can be directed in different arbitrary directions from the fixed bezier
// point. See {{bez_begin()}} for examples.
// Arguments:
// pt = The fixed point for the bezier path.
// a1 = If given a scalar, specifies the theta (XY plane) angle in degrees from X+. If given a vector, specifies the direction and possibly distance of the approaching control point.
// a2 = If given a scalar, specifies the theta (XY plane) angle in degrees from X+. If given a vector, specifies the direction and possibly distance of the departing control point.
// r1 = Specifies the distance of the approching control point from the fixed point. Overrides the distance component of the vector if `a1` contains a vector.
// r2 = Specifies the distance of the departing control point from the fixed point. Overrides the distance component of the vector if `a2` contains a vector.
// p1 = If given, specifies the number of degrees away from the Z+ axis of the approaching control point.
// p2 = If given, specifies the number of degrees away from the Z+ axis of the departing control point.
function bez_joint(pt,a1,a2,r1,r2,p1,p2) =
assert(is_finite(r1) || is_vector(a1))
assert(is_finite(r2) || is_vector(a2))
assert(len(pt)==3 || (is_undef(p1) && is_undef(p2)))
let(
r1 = is_num(r1)? r1 : norm(a1),
r2 = is_num(r2)? r2 : norm(a2),
p1 = default(p1, 90),
p2 = default(p2, 90)
) [
if (is_vector(a1)) (pt+r1*unit(a1))
else if (is_finite(a1)) (pt+spherical_to_xyz(r1,a1,p1))
else assert(false, "Bad Arguments"),
pt,
if (is_vector(a2)) (pt+r2*unit(a2))
else if (is_finite(a2)) (pt+spherical_to_xyz(r2,a2,p2))
else assert(false, "Bad Arguments")
];
// Function: bez_end()
// Topics: Bezier Paths
// See Also: bez_tang(), bez_joint(), bez_end()
// Usage:
// pts = bez_end(pt,a,r,<p=>);
// pts = bez_end(pt,VECTOR,<r>,<p=>);
// Description:
// This is used to create the approaching control point, and the endpoint of a cubic bezier path.
// See {{bez_begin()}} for examples.
// Arguments:
// pt = The starting endpoint for the bezier path.
// a = If given a scalar, specifies the theta (XY plane) angle in degrees from X+. If given a vector, specifies the direction and possibly distance of the first control point.
// r = Specifies the distance of the control point from the endpoint `pt`.
// p = If given, specifies the number of degrees away from the Z+ axis.
function bez_end(pt,a,r,p) =
assert(is_finite(r) || is_vector(a))
assert(len(pt)==3 || is_undef(p))
is_vector(a)? [pt+(is_undef(r)? a : r*unit(a)), pt] :
is_finite(a)? [pt+spherical_to_xyz(r,a,default(p,90)), pt] :
assert(false, "Bad arguments.");
// Section: Segment Functions
// Function: bezier_points()

View file

@ -9,9 +9,12 @@
// Section: Coordinate Manipulation
// Function: point2d()
// Usage:
// pt = point2d(p, <fill>);
// Topics: Coordinates, Points
// See Also: path2d(), point3d(), path3d()
// Description:
// Returns a 2D vector/point from a 2D or 3D vector.
// If given a 3D point, removes the Z coordinate.
// Returns a 2D vector/point from a 2D or 3D vector. If given a 3D point, removes the Z coordinate.
// Arguments:
// p = The coordinates to force into a 2D vector/point.
// fill = Value to fill missing values in vector with.
@ -19,11 +22,14 @@ function point2d(p, fill=0) = [for (i=[0:1]) (p[i]==undef)? fill : p[i]];
// Function: path2d()
// Usage:
// pts = path2d(points);
// Topics: Coordinates, Points, Paths
// See Also: point2d(), point3d(), path3d()
// Description:
// Returns a list of 2D vectors/points from a list of 2D, 3D or higher
// dimensional vectors/points. Removes the extra coordinates from
// higher dimensional points. The input must be a path, where
// every vector has the same length.
// Returns a list of 2D vectors/points from a list of 2D, 3D or higher dimensional vectors/points.
// Removes the extra coordinates from higher dimensional points. The input must be a path, where
// every vector has the same length.
// Arguments:
// points = A list of 2D or 3D points/vectors.
function path2d(points) =
@ -34,6 +40,10 @@ function path2d(points) =
// Function: point3d()
// Usage:
// pt = point3d(p, <fill>);
// Topics: Coordinates, Points
// See Also: path2d(), point2d(), path3d()
// Description:
// Returns a 3D vector/point from a 2D or 3D vector.
// Arguments:
@ -43,6 +53,10 @@ function point3d(p, fill=0) = [for (i=[0:2]) (p[i]==undef)? fill : p[i]];
// Function: path3d()
// Usage:
// pts = path3d(points, <fill>);
// Topics: Coordinates, Points, Paths
// See Also: point2d(), path2d(), point3d()
// Description:
// Returns a list of 3D vectors/points from a list of 2D or higher dimensional vectors/points
// by removing extra coordinates or adding the z coordinate.
@ -63,6 +77,10 @@ function path3d(points, fill=0) =
// Function: point4d()
// Usage:
// pt = point4d(p, <fill>);
// Topics: Coordinates, Points
// See Also: point2d(), path2d(), point3d(), path3d(), path4d()
// Description:
// Returns a 4D vector/point from a 2D or 3D vector.
// Arguments:
@ -72,12 +90,15 @@ function point4d(p, fill=0) = [for (i=[0:3]) (p[i]==undef)? fill : p[i]];
// Function: path4d()
// Usage:
// pt = path4d(points, <fill>);
// Topics: Coordinates, Points, Paths
// See Also: point2d(), path2d(), point3d(), path3d(), point4d()
// Description:
// Returns a list of 4D vectors/points from a list of 2D or 3D vectors/points.
// Arguments:
// points = A list of 2D or 3D points/vectors.
// fill = Value to fill missing values in vectors with.
function path4d(points, fill=0) =
assert(is_num(fill) || is_vector(fill))
assert(is_path(points, dim=undef, fast=true), "Input to path4d is not a path")
@ -102,8 +123,10 @@ function path4d(points, fill=0) =
// Function: polar_to_xy()
// Usage:
// polar_to_xy(r, theta);
// polar_to_xy([r, theta]);
// pt = polar_to_xy(r, theta);
// pt = polar_to_xy([r, theta]);
// Topics: Coordinates, Points, Paths
// See Also: xy_to_polar(), xyz_to_cylindrical(), cylindrical_to_xyz(), xyz_to_spherical(), spherical_to_xyz()
// Description:
// Convert polar coordinates to 2D cartesian coordinates.
// Returns [X,Y] cartesian coordinates.
@ -114,6 +137,13 @@ function path4d(points, fill=0) =
// xy = polar_to_xy(20,45); // Returns: ~[14.1421365, 14.1421365]
// xy = polar_to_xy(40,30); // Returns: ~[34.6410162, 15]
// xy = polar_to_xy([40,30]); // Returns: ~[34.6410162, 15]
// Example(2D):
// r=40; ang=30; $fn=36;
// pt = polar_to_xy(r,ang);
// stroke(circle(r=r), closed=true, width=0.5);
// color("black") stroke([[r,0], [0,0], pt], width=0.5);
// color("black") stroke(arc(r=15, angle=ang), width=0.5);
// color("red") move(pt) circle(d=3);
function polar_to_xy(r,theta=undef) = let(
rad = theta==undef? r[0] : r,
t = theta==undef? r[1] : theta
@ -122,8 +152,10 @@ function polar_to_xy(r,theta=undef) = let(
// Function: xy_to_polar()
// Usage:
// xy_to_polar(x,y);
// xy_to_polar([X,Y]);
// r_theta = xy_to_polar(x,y);
// r_theta = xy_to_polar([X,Y]);
// Topics: Coordinates, Points, Paths
// See Also: polar_to_xy(), xyz_to_cylindrical(), cylindrical_to_xyz(), xyz_to_spherical(), spherical_to_xyz()
// Description:
// Convert 2D cartesian coordinates to polar coordinates.
// Returns [radius, theta] where theta is the angle counter-clockwise of X+.
@ -133,6 +165,13 @@ function polar_to_xy(r,theta=undef) = let(
// Examples:
// plr = xy_to_polar(20,30);
// plr = xy_to_polar([40,60]);
// Example(2D):
// pt = [-20,30]; $fn = 36;
// rt = xy_to_polar(pt);
// r = rt[0]; ang = rt[1];
// stroke(circle(r=r), closed=true, width=0.5);
// zrot(ang) stroke([[0,0],[r,0]],width=0.5);
// color("red") move(pt) circle(d=3);
function xy_to_polar(x,y=undef) = let(
xx = y==undef? x[0] : x,
yy = y==undef? x[1] : y
@ -141,11 +180,13 @@ function xy_to_polar(x,y=undef) = let(
// Function: project_plane()
// Usage: With the plane defined by 3 Points
// xyz = project_plane(point, a, b, c);
// pt = project_plane(point, a, b, c);
// Usage: With the plane defined by Pointlist
// xyz = project_plane(point, POINTLIST);
// pt = project_plane(point, POINTLIST);
// Usage: With the plane defined by Plane Definition [A,B,C,D] Where Ax+By+Cz=D
// xyz = project_plane(point, PLANE);
// pt = project_plane(point, PLANE);
// Topics: Coordinates, Points, Paths
// See Also: lift_plane()
// Description:
// Converts the given 3D points from global coordinates to the 2D planar coordinates of the closest
// points on the plane. This coordinate system can be useful in taking a set of nearly coplanar
@ -165,6 +206,20 @@ function xy_to_polar(x,y=undef) = let(
// a=[0,0,0]; b=[10,-10,0]; c=[10,0,10];
// xy = project_plane(pt, a, b, c);
// xy2 = project_plane(pt, [a,b,c]);
// Example(3D):
// points = move([10,20,30], p=yrot(25, p=path3d(circle(d=100, $fn=36))));
// plane = plane_from_normal([1,0,1]);
// proj = project_plane(points,plane);
// n = plane_normal(plane);
// cp = centroid(proj);
// color("red") move_copies(points) sphere(d=2,$fn=12);
// color("blue") rot(from=UP,to=n,cp=cp) move_copies(proj) sphere(d=2,$fn=12);
// move(cp) {
// rot(from=UP,to=n) {
// anchor_arrow(30);
// %cube([120,150,0.1],center=true);
// }
// }
function project_plane(point, a, b, c) =
is_undef(b) && is_undef(c) && is_list(a)? let(
mat = is_vector(a,4)? plane_transform(a) :
@ -195,6 +250,8 @@ function project_plane(point, a, b, c) =
// xyz = lift_plane(point, POINTLIST);
// Usage: With Plane Definition [A,B,C,D] Where Ax+By+Cz=D
// xyz = lift_plane(point, PLANE);
// Topics: Coordinates, Points, Paths
// See Also: project_plane()
// Description:
// Converts the given 2D point from planar coordinates to the global 3D coordinates of the point on the plane.
// Can be called one of three ways:
@ -232,8 +289,10 @@ function lift_plane(point, a, b, c) =
// Function: cylindrical_to_xyz()
// Usage:
// cylindrical_to_xyz(r, theta, z)
// cylindrical_to_xyz([r, theta, z])
// pt = cylindrical_to_xyz(r, theta, z);
// pt = cylindrical_to_xyz([r, theta, z]);
// Topics: Coordinates, Points, Paths
// See Also: xyz_to_cylindrical(), xyz_to_spherical(), spherical_to_xyz()
// Description:
// Convert cylindrical coordinates to 3D cartesian coordinates. Returns [X,Y,Z] cartesian coordinates.
// Arguments:
@ -252,12 +311,13 @@ function cylindrical_to_xyz(r,theta=undef,z=undef) = let(
// Function: xyz_to_cylindrical()
// Usage:
// xyz_to_cylindrical(x,y,z)
// xyz_to_cylindrical([X,Y,Z])
// rtz = xyz_to_cylindrical(x,y,z);
// rtz = xyz_to_cylindrical([X,Y,Z]);
// Topics: Coordinates, Points, Paths
// See Also: cylindrical_to_xyz(), xyz_to_spherical(), spherical_to_xyz()
// Description:
// Convert 3D cartesian coordinates to cylindrical coordinates.
// Returns [radius,theta,Z]. Theta is the angle counter-clockwise
// of X+ on the XY plane. Z is height above the XY plane.
// Convert 3D cartesian coordinates to cylindrical coordinates. Returns [radius,theta,Z].
// Theta is the angle counter-clockwise of X+ on the XY plane. Z is height above the XY plane.
// Arguments:
// x = X coordinate.
// y = Y coordinate.
@ -272,11 +332,12 @@ function xyz_to_cylindrical(x,y=undef,z=undef) = let(
// Function: spherical_to_xyz()
// Usage:
// spherical_to_xyz(r, theta, phi);
// spherical_to_xyz([r, theta, phi]);
// pt = spherical_to_xyz(r, theta, phi);
// pt = spherical_to_xyz([r, theta, phi]);
// Description:
// Convert spherical coordinates to 3D cartesian coordinates.
// Returns [X,Y,Z] cartesian coordinates.
// Convert spherical coordinates to 3D cartesian coordinates. Returns [X,Y,Z] cartesian coordinates.
// Topics: Coordinates, Points, Paths
// See Also: cylindrical_to_xyz(), xyz_to_spherical(), xyz_to_cylindrical()
// Arguments:
// r = distance from origin.
// theta = angle in degrees, counter-clockwise of X+ on the XY plane.
@ -293,12 +354,13 @@ function spherical_to_xyz(r,theta=undef,phi=undef) = let(
// Function: xyz_to_spherical()
// Usage:
// xyz_to_spherical(x,y,z)
// xyz_to_spherical([X,Y,Z])
// r_theta_phi = xyz_to_spherical(x,y,z)
// r_theta_phi = xyz_to_spherical([X,Y,Z])
// Topics: Coordinates, Points, Paths
// See Also: cylindrical_to_xyz(), spherical_to_xyz(), xyz_to_cylindrical()
// Description:
// Convert 3D cartesian coordinates to spherical coordinates.
// Returns [r,theta,phi], where phi is the angle from the Z+ pole,
// and theta is degrees counter-clockwise of X+ on the XY plane.
// Convert 3D cartesian coordinates to spherical coordinates. Returns [r,theta,phi], where phi is
// the angle from the Z+ pole, and theta is degrees counter-clockwise of X+ on the XY plane.
// Arguments:
// x = X coordinate.
// y = Y coordinate.
@ -313,8 +375,10 @@ function xyz_to_spherical(x,y=undef,z=undef) = let(
// Function: altaz_to_xyz()
// Usage:
// altaz_to_xyz(alt, az, r);
// altaz_to_xyz([alt, az, r]);
// pt = altaz_to_xyz(alt, az, r);
// pt = altaz_to_xyz([alt, az, r]);
// Topics: Coordinates, Points, Paths
// See Also: cylindrical_to_xyz(), xyz_to_spherical(), spherical_to_xyz(), xyz_to_cylindrical(), xyz_to_altaz()
// Description:
// Convert altitude/azimuth/range coordinates to 3D cartesian coordinates.
// Returns [X,Y,Z] cartesian coordinates.
@ -334,8 +398,10 @@ function altaz_to_xyz(alt,az=undef,r=undef) = let(
// Function: xyz_to_altaz()
// Usage:
// xyz_to_altaz(x,y,z);
// xyz_to_altaz([X,Y,Z]);
// alt_az_r = xyz_to_altaz(x,y,z);
// alt_az_r = xyz_to_altaz([X,Y,Z]);
// Topics: Coordinates, Points, Paths
// See Also: cylindrical_to_xyz(), xyz_to_spherical(), spherical_to_xyz(), xyz_to_cylindrical(), altaz_to_xyz()
// Description:
// Convert 3D cartesian coordinates to altitude/azimuth/range coordinates.
// Returns [altitude,azimuth,range], where altitude is angle above the

View file

@ -999,10 +999,11 @@ function plane_transform(plane) =
// Function: projection_on_plane()
// Usage:
// projection_on_plane(points);
// pts = projection_on_plane(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
// projection of the points on the plane.
// 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.
// In other words, for every point given, returns the closest point to it on the plane.
// Arguments:
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
// points = List of points to project
@ -1061,24 +1062,6 @@ function distance_from_plane(plane, point) =
point3d(plane)* point - plane[3];
// Function: closest_point_on_plane()
// Usage:
// pt = closest_point_on_plane(plane, point);
// Description:
// Takes a point, and a plane [A,B,C,D] where the equation of that plane is `Ax+By+Cz=D`.
// Returns the coordinates of the closest point on that plane to the given `point`.
// Arguments:
// plane = The [A,B,C,D] coefficients for the equation of the plane.
// point = The 3D point to find the closest point to.
function closest_point_on_plane(plane, point) =
assert( _valid_plane(plane), "Invalid input plane." )
assert( is_vector(point,3), "Invalid point." )
let( plane = normalize_plane(plane),
n = point3d(plane),
d = n*point - plane[3] // distance from plane
)
point - n*d;
// Returns [POINT, U] if line intersects plane at one point.
// Returns [LINE, undef] if the line is on the plane.

View file

@ -114,15 +114,15 @@ function path_subselect(path, s1, u1, s2, u2, closed=false) =
function simplify_path(path, eps=EPSILON) =
assert( is_path(path), "Invalid path." )
assert( is_undef(eps) || (is_finite(eps) && (eps>=0) ), "Invalid tolerance." )
len(path)<=2 ? path
: let(
indices = [ 0,
for (i=[1:1:len(path)-2])
if (!collinear(path[i-1],path[i],path[i+1], eps=eps)) i,
len(path)-1
]
)
[for (i = indices) path[i] ];
len(path)<=2 ? path :
let(
indices = [
0,
for (i=[1:1:len(path)-2])
if (!collinear(path[i-1], path[i], path[i+1], eps=eps)) i,
len(path)-1
]
) [for (i=indices) path[i]];
// Function: simplify_path_indexed()
@ -137,19 +137,21 @@ function simplify_path(path, eps=EPSILON) =
// indices = A list of indices into `points` that forms a path.
// eps = Largest angle variance allowed. Default: EPSILON (1-e9) degrees.
function simplify_path_indexed(points, indices, eps=EPSILON) =
len(indices)<=2? indices
: let(
indices = concat( indices[0],
[for (i=[1:1:len(indices)-2])
let(
i1 = indices[i-1],
i2 = indices[i],
i3 = indices[i+1]
)
if (!collinear(points[i1],points[i2],points[i3], eps=eps)) indices[i]],
indices[len(indices)-1] )
)
indices;
len(indices)<=2? indices :
let(
indices = concat(
indices[0],
[
for (i=[1:1:len(indices)-2]) let(
i1 = indices[i-1],
i2 = indices[i],
i3 = indices[i+1]
) if (!collinear(points[i1], points[i2], points[i3], eps=eps))
indices[i]
],
indices[len(indices)-1]
)
) indices;
// Function: path_length()
@ -178,9 +180,9 @@ function path_length(path,closed=false) =
// closed = true if the path is closed. Default: false
function path_segment_lengths(path, closed=false) =
[
for (i=[0:1:len(path)-2]) norm(path[i+1]-path[i]),
if (closed) norm(path[0]-path[len(path)-1])
];
for (i=[0:1:len(path)-2]) norm(path[i+1]-path[i]),
if (closed) norm(path[0]-select(path,-1))
];
// Function: path_pos_from_start()
@ -1193,7 +1195,7 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=fals
);
distOK = is_def(n) || (min(distances)>=0 && max(distances)<=length);
assert(distOK,"Cannot fit all of the copies");
cutlist = path_cut(path, distances, closed, direction=true);
cutlist = path_cut_points(path, distances, closed, direction=true);
planar = len(path[0])==2;
if (true) for(i=[0:1:len(cutlist)-1]) {
$pos = cutlist[i][0];
@ -1216,17 +1218,17 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=fals
}
// Function: path_cut()
// Function: path_cut_points()
//
// Usage:
// path_cut(path, dists, [closed], [direction])
// cuts = path_cut_points(path, dists, <closed=>, <direction=>);
//
// Description:
// Cuts a path at a list of distances from the first point in the path. Returns a list of the cut
// points and indices of the next point in the path after that point. So for example, a return
// value entry of [[2,3], 5] means that the cut point was [2,3] and the next point on the path after
// this point is path[5]. If the path is too short then path_cut fails with an error. If you set
// `direction` to true then `path_cut` will also return the tangent vector to the path and a normal
// this point is path[5]. If the path is too short then path_cut_points returns undef. If you set
// `direction` to true then `path_cut_points` will also return the tangent vector to the path and a normal
// vector to the path. It tries to find a normal vector that is coplanar to the path near the cut
// point. If this fails it will return a normal vector parallel to the xy plane. The output with
// direction vectors will be `[point, next_index, tangent, normal]`.
@ -1240,22 +1242,23 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=fals
// Arguments:
// path = path to cut
// dists = distances where the path should be cut (a list) or a scalar single distance
// ---
// closed = set to true if the curve is closed. Default: false
// direction = set to true to return direction vectors. Default: false
//
// Example(NORENDER):
// square=[[0,0],[1,0],[1,1],[0,1]];
// path_cut(square, [.5,1.5,2.5]); // Returns [[[0.5, 0], 1], [[1, 0.5], 2], [[0.5, 1], 3]]
// path_cut(square, [0,1,2,3]); // Returns [[[0, 0], 1], [[1, 0], 2], [[1, 1], 3], [[0, 1], 4]]
// path_cut(square, [0,0.8,1.6,2.4,3.2], closed=true); // Returns [[[0, 0], 1], [[0.8, 0], 1], [[1, 0.6], 2], [[0.6, 1], 3], [[0, 0.8], 4]]
// path_cut(square, [0,0.8,1.6,2.4,3.2]); // Returns [[[0, 0], 1], [[0.8, 0], 1], [[1, 0.6], 2], [[0.6, 1], 3], undef]
function path_cut(path, dists, closed=false, direction=false) =
// path_cut_points(square, [.5,1.5,2.5]); // Returns [[[0.5, 0], 1], [[1, 0.5], 2], [[0.5, 1], 3]]
// path_cut_points(square, [0,1,2,3]); // Returns [[[0, 0], 1], [[1, 0], 2], [[1, 1], 3], [[0, 1], 4]]
// path_cut_points(square, [0,0.8,1.6,2.4,3.2], closed=true); // Returns [[[0, 0], 1], [[0.8, 0], 1], [[1, 0.6], 2], [[0.6, 1], 3], [[0, 0.8], 4]]
// path_cut_points(square, [0,0.8,1.6,2.4,3.2]); // Returns [[[0, 0], 1], [[0.8, 0], 1], [[1, 0.6], 2], [[0.6, 1], 3], undef]
function path_cut_points(path, dists, closed=false, direction=false) =
let(long_enough = len(path) >= (closed ? 3 : 2))
assert(long_enough,len(path)<2 ? "Two points needed to define a path" : "Closed path must include three points")
is_num(dists) ? path_cut(path, [dists],closed, direction)[0] :
is_num(dists) ? path_cut_points(path, [dists],closed, direction)[0] :
assert(is_vector(dists))
assert(list_increasing(dists), "Cut distances must be an increasing list")
let(cuts = _path_cut(path,dists,closed))
let(cuts = _path_cut_points(path,dists,closed))
!direction
? cuts
: let(
@ -1265,7 +1268,7 @@ function path_cut(path, dists, closed=false, direction=false) =
hstack(cuts, array_group(dir,1), array_group(normals,1));
// Main recursive path cut function
function _path_cut(path, dists, closed=false, pind=0, dtotal=0, dind=0, result=[]) =
function _path_cut_points(path, dists, closed=false, pind=0, dtotal=0, dind=0, result=[]) =
dind == len(dists) ? result :
let(
lastpt = len(result)==0? [] : select(result,-1)[0], // location of last cut point
@ -1274,7 +1277,7 @@ function _path_cut(path, dists, closed=false, pind=0, dtotal=0, dind=0, result=[
? [lerp(lastpt,select(path,pind),(dists[dind]-dtotal)/dpartial),pind]
: _path_cut_single(path, dists[dind]-dtotal-dpartial, closed, pind)
)
_path_cut(path, dists, closed, nextpoint[1], dists[dind],dind+1, concat(result, [nextpoint]));
_path_cut_points(path, dists, closed, nextpoint[1], dists[dind],dind+1, concat(result, [nextpoint]));
// Search for a single cut point in the path
@ -1332,8 +1335,10 @@ function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) =
// Function: path_cut_segs()
// Topics: Paths
// See Also: path_cut_points()
// Usage:
// path_list = path_cut_segs(path, cutdist, <closed>);
// path_list = path_cut_segs(path, cutdist, <closed=>);
// Description:
// Given a list of distances in `cutdist`, cut the path into
// subpaths at those lengths, returning a list of paths.
@ -1342,9 +1347,13 @@ function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) =
// in ascending order. If you repeat a distance you will get an
// empty list in that position in the output.
// Arguments:
// path = path to cut
// cutdist = distance or list of distances where path is cut
// closed = set to true for a closed path. Default: false
// path = The original path to split.
// cutdist = Distance or list of distances where path is cut
// closed = If true, treat the path as a closed polygon.
// Example(2D):
// path = circle(d=100);
// segs = path_cut_segs(path, [50, 200], closed=true);
// rainbow(segs) stroke($item);
function path_cut_segs(path,cutdist,closed) =
is_num(cutdist) ? path_cut_segs(path,[cutdist],closed) :
assert(is_vector(cutdist))
@ -1533,7 +1542,7 @@ function resample_path(path, N, spacing, closed=false) =
N = is_def(N) ? N : round(length/spacing) + (closed?0:1),
spacing = length/(closed?N:N-1), // Note: worried about round-off error, so don't include
distlist = list_range(closed?N:N-1,step=spacing), // last point when closed=false
cuts = path_cut(path, distlist, closed=closed)
cuts = path_cut_points(path, distlist, closed=closed)
)
concat(subindex(cuts,0),closed?[]:[select(path,-1)]); // Then add last point here

View file

@ -651,8 +651,8 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
assert(d_first<path_length(revresult),str("Path ",i," is too short for specified cut distance ",d_first))
assert(d_next<path_length(nextpath), str("Path ",i+1," is too short for specified cut distance ",d_next))
let(
firstcut = path_cut(revresult, d_first, direction=true),
nextcut = path_cut(nextpath, d_next, direction=true)
firstcut = path_cut_points(revresult, d_first, direction=true),
nextcut = path_cut_points(nextpath, d_next, direction=true)
)
assert(!loop || nextcut[1] < len(revresult)-1-firstcut[1], "Path is too short to close the loop")
let(
@ -1598,8 +1598,8 @@ function _stroke_end(width,left, right, spec) =
90-vector_angle([newright[1],newright[0],newleft[0]])/2,
jointleft = 8*cutleft/cos(leftangle)/(1+4*bez_k),
jointright = 8*cutright/cos(rightangle)/(1+4*bez_k),
pathcutleft = path_cut(newleft,abs(jointleft)),
pathcutright = path_cut(newright,abs(jointright)),
pathcutleft = path_cut_points(newleft,abs(jointleft)),
pathcutright = path_cut_points(newright,abs(jointright)),
leftdelete = intright? pathcutleft[1] : pathcutleft[1] + pathclip[1] -1,
rightdelete = intright? pathcutright[1] + pathclip[1] -1 : pathcutright[1],
leftcorner = line_intersection([pathcutleft[0], newleft[pathcutleft[1]]], [newright[0],newleft[0]]),

View file

@ -16,37 +16,54 @@
// Description:
// Draws a 2D or 3D path with a given line width. Endcaps can be specified for each end individually.
// Figure(Med,NoAxes,VPR=[0,0,0],VPD=250): Endcap Types
// endcaps = [
// ["butt", "square", "round", "chisel", "tail", "tail2"],
// ["line", "cross", "dot", "diamond", "x", "arrow", "arrow2"]
// cap_pairs = [
// ["butt", "chisel" ],
// ["round", "square" ],
// ["line", "cross" ],
// ["x", "diamond"],
// ["dot", "block" ],
// ["tail", "arrow" ],
// ["tail2", "arrow2" ]
// ];
// for (x=idx(endcaps), y=idx(endcaps[x])) {
// cap = endcaps[x][y];
// right(x*60-60+5) fwd(y*10-30) {
// right(28) color("black") text(text=cap, size=5, halign="left", valign="center");
// stroke([[0,0], [20,0]], width=3, endcap_width=3, endcap1=false, endcap2=cap);
// color("black") stroke([[0,0], [20,0]], width=0.25, endcaps=false);
// for (i = idx(cap_pairs)) {
// fwd((i-len(cap_pairs)/2+0.5)*13) {
// stroke([[-20,0], [20,0]], width=3, endcap1=cap_pairs[i][0], endcap2=cap_pairs[i][1]);
// color("black") {
// stroke([[-20,0], [20,0]], width=0.25, endcaps=false);
// left(28) text(text=cap_pairs[i][0], size=5, halign="right", valign="center");
// right(28) text(text=cap_pairs[i][1], size=5, halign="left", valign="center");
// }
// }
// }
// Arguments:
// path = The 2D path to draw along.
// path = The path to draw along.
// width = The width of the line to draw. If given as a list of widths, (one for each path point), draws the line with varying thickness to each point.
// closed = If true, draw an additional line from the end of the path to the start.
// plots = Specifies the plot point shape for every point of the line. If a 2D path is given, use that to draw custom plot points.
// joints = Specifies the joint shape for each joint of the line. If a 2D path is given, use that to draw custom joints.
// endcaps = Specifies the endcap type for both ends of the line. If a 2D path is given, use that to draw custom endcaps.
// endcap1 = Specifies the endcap type for the start of the line. If a 2D path is given, use that to draw a custom endcap.
// endcap2 = Specifies the endcap type for the end of the line. If a 2D path is given, use that to draw a custom endcap.
// endcap_width = Some endcap types are wider than the line. This specifies the size of endcaps, in multiples of the line width. Default: 3.5
// endcap_width1 = This specifies the size of starting endcap, in multiples of the line width. Default: 3.5
// endcap_width2 = This specifies the size of ending endcap, in multiples of the line width. Default: 3.5
// endcap_length = Length of endcaps, in multiples of the line width. Default: `endcap_width*0.5`
// endcap_length1 = Length of starting endcap, in multiples of the line width. Default: `endcap_width1*0.5`
// endcap_length2 = Length of ending endcap, in multiples of the line width. Default: `endcap_width2*0.5`
// endcap_extent = Extents length of endcaps, in multiples of the line width. Default: `endcap_width*0.5`
// endcap_extent1 = Extents length of starting endcap, in multiples of the line width. Default: `endcap_width1*0.5`
// endcap_extent2 = Extents length of ending endcap, in multiples of the line width. Default: `endcap_width2*0.5`
// endcap_angle = Extra axial rotation given to flat endcaps for 3D paths, in degrees. If not given, the endcaps are fully spun. Default: `undef` (Fully spun cap)
// endcap_angle1 = Extra axial rotation given to a flat starting endcap for 3D paths, in degrees. If not given, the endcap is fully spun. Default: `undef` (Fully spun cap)
// endcap_angle2 = Extra axial rotation given to a flat ending endcap for 3D paths, in degrees. If not given, the endcap is fully spun. Default: `undef` (Fully spun cap)
// plot_width = Some plot point shapes are wider than the line. This specifies the width of the shape, in multiples of the line width.
// joint_width = Some joint shapes are wider than the line. This specifies the width of the shape, in multiples of the line width.
// endcap_width = Some endcap types are wider than the line. This specifies the size of endcaps, in multiples of the line width.
// endcap_width1 = This specifies the size of starting endcap, in multiples of the line width.
// endcap_width2 = This specifies the size of ending endcap, in multiples of the line width.
// plot_length = Length of plot point shape, in multiples of the line width.
// joint_length = Length of joint shape, in multiples of the line width.
// endcap_length = Length of endcaps, in multiples of the line width.
// endcap_length1 = Length of starting endcap, in multiples of the line width.
// endcap_length2 = Length of ending endcap, in multiples of the line width.
// plot_extent = Extents length of plot point shape, in multiples of the line width.
// joint_extent = Extents length of joint shape, in multiples of the line width.
// endcap_extent = Extents length of endcaps, in multiples of the line width.
// endcap_extent1 = Extents length of starting endcap, in multiples of the line width.
// endcap_extent2 = Extents length of ending endcap, in multiples of the line width.
// plot_angle = Extra rotation given to plot point shapes, in degrees. If not given, the shapes are fully spun.
// joint_angle = Extra rotation given to joint shapes, in degrees. If not given, the shapes are fully spun.
// endcap_angle = Extra rotation given to endcaps, in degrees. If not given, the endcaps are fully spun.
// endcap_angle1 = Extra rotation given to a starting endcap, in degrees. If not given, the endcap is fully spun.
// endcap_angle2 = Extra rotation given to a ending endcap, in degrees. If not given, the endcap is fully spun.
// trim = Trim the the start and end line segments by this much, to keep them from interfering with custom endcaps.
// trim1 = Trim the the starting line segment by this much, to keep it from interfering with a custom endcap.
// trim2 = Trim the the ending line segment by this much, to keep it from interfering with a custom endcap.
@ -67,6 +84,12 @@
// Example(2D): Mixed Endcaps
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
// stroke(path, width=10, endcap1="tail2", endcap2="arrow2");
// Example(2D): Plotting Points
// path = [for (a=[0:30:360]) [a-180, 60*sin(a)]];
// stroke(path, width=3, joints="diamond", endcaps="arrow2", plot_angle=0, plot_width=5);
// Example(2D): Joints and Endcaps
// path = [for (a=[0:30:360]) [a-180, 60*sin(a)]];
// stroke(path, width=3, joints="dot", endcaps="arrow2", joint_angle=0);
// Example(2D): Custom Endcap Shapes
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
// arrow = [[0,0], [2,-3], [0.5,-2.3], [2,-4], [0.5,-3.5], [-0.5,-3.5], [-2,-4], [-0.5,-2.3], [-2,-3]];
@ -84,32 +107,56 @@
// Example: 3D Path with Mixed Endcaps
// path = rot([15,30,0], p=path3d(pentagon(d=50)));
// stroke(path, width=2, endcap1="arrow2", endcap2="tail", endcap_angle2=0, $fn=18);
// Example: 3D Path with Joints and Endcaps
// path = [for (i=[0:10:360]) [(i-180)/2,20*cos(3*i),20*sin(3*i)]];
// stroke(path, width=2, joints="dot", endcap1="round", endcap2="arrow2", joint_width=2.0, endcap_width2=3, $fn=18);
module stroke(
path, width=1, closed=false,
endcaps, endcap1, endcap2,
endcaps, endcap1, endcap2, joints, plots,
endcap_width, endcap_width1, endcap_width2, joint_width, plot_width,
endcap_length, endcap_length1, endcap_length2, joint_length, plot_length,
endcap_extent, endcap_extent1, endcap_extent2, joint_extent, plot_extent,
endcap_angle, endcap_angle1, endcap_angle2, joint_angle, plot_angle,
trim, trim1, trim2,
endcap_width, endcap_width1, endcap_width2,
endcap_length, endcap_length1, endcap_length2,
endcap_extent, endcap_extent1, endcap_extent2,
endcap_angle, endcap_angle1, endcap_angle2,
convexity=10, hull=true
) {
function _endcap_shape(cap,linewidth,w,l,l2) = (
let(sq2=sqrt(2), l3=l-l2)
(cap=="round" || cap==true)? circle(d=1, $fn=max(8, segs(w/2))) :
cap=="chisel"? [[-0.5,0], [0,0.5], [0.5,0], [0,-0.5]] :
cap=="square"? [[-0.5,-0.5], [-0.5,0.5], [0.5,0.5], [0.5,-0.5]] :
cap=="diamond"? [[0,w/2], [w/2,0], [0,-w/2], [-w/2,0]] :
cap=="dot"? circle(d=3, $fn=max(12, segs(w*3/2))) :
cap=="x"? [for (a=[0:90:270]) each rot(a,p=[[w+sq2/2,w-sq2/2]/2, [w-sq2/2,w+sq2/2]/2, [0,sq2/2]]) ] :
cap=="cross"? [for (a=[0:90:270]) each rot(a,p=[[1,w]/2, [-1,w]/2, [-1,1]/2]) ] :
cap=="line"? [[w/2,0.5], [w/2,-0.5], [-w/2,-0.5], [-w/2,0.5]] :
cap=="arrow"? [[0,0], [w/2,-l2], [w/2,-l2-l], [0,-l], [-w/2,-l2-l], [-w/2,-l2]] :
cap=="arrow2"? [[0,0], [w/2,-l2-l], [0,-l], [-w/2,-l2-l]] :
cap=="tail"? [[0,0], [w/2,l2], [w/2,l2-l], [0,-l], [-w/2,l2-l], [-w/2,l2]] :
cap=="tail2"? [[w/2,0], [w/2,-l], [0,-l-l2], [-w/2,-l], [-w/2,0]] :
function _shape_defaults(cap) =
cap==undef? [1.00, 0.00, 0.00] :
cap==false? [1.00, 0.00, 0.00] :
cap==true? [1.00, 1.00, 0.00] :
cap=="butt"? [1.00, 0.00, 0.00] :
cap=="round"? [1.00, 1.00, 0.00] :
cap=="chisel"? [1.00, 1.00, 0.00] :
cap=="square"? [1.00, 1.00, 0.00] :
cap=="block"? [3.00, 1.00, 0.00] :
cap=="diamond"? [3.50, 1.00, 0.00] :
cap=="dot"? [3.00, 1.00, 0.00] :
cap=="x"? [3.50, 0.40, 0.00] :
cap=="cross"? [4.50, 0.22, 0.00] :
cap=="line"? [4.50, 0.22, 0.00] :
cap=="arrow"? [3.50, 0.40, 0.50] :
cap=="arrow2"? [3.50, 1.00, 0.14] :
cap=="tail"? [3.50, 0.47, 0.50] :
cap=="tail2"? [3.50, 0.28, 0.50] :
is_path(cap)? [0.00, 0.00, 0.00] :
assert(false, str("Invalid cap or joint: ",cap));
function _shape_path(cap,linewidth,w,l,l2) = (
(cap=="butt" || cap==false || cap==undef)? [] :
(cap=="round" || cap==true)? scale([w,l], p=circle(d=1, $fn=max(8, segs(w/2)))) :
cap=="chisel"? scale([w,l], p=circle(d=1,$fn=4)) :
cap=="diamond"? circle(d=w,$fn=4) :
cap=="square"? scale([w,l], p=square(1,center=true)) :
cap=="block"? scale([w,l], p=square(1,center=true)) :
cap=="dot"? circle(d=w, $fn=max(12, segs(w*3/2))) :
cap=="x"? [for (a=[0:90:270]) each rot(a,p=[[w+l/2,w-l/2]/2, [w-l/2,w+l/2]/2, [0,l/2]]) ] :
cap=="cross"? [for (a=[0:90:270]) each rot(a,p=[[l,w]/2, [-l,w]/2, [-l,l]/2]) ] :
cap=="line"? scale([w,l], p=square(1,center=true)) :
cap=="arrow"? [[0,0], [w/2,-l2], [w/2,-l2-l], [0,-l], [-w/2,-l2-l], [-w/2,-l2]] :
cap=="arrow2"? [[0,0], [w/2,-l2-l], [0,-l], [-w/2,-l2-l]] :
cap=="tail"? [[0,0], [w/2,l2], [w/2,l2-l], [0,-l], [-w/2,l2-l], [-w/2,l2]] :
cap=="tail2"? [[w/2,0], [w/2,-l], [0,-l-l2], [-w/2,-l], [-w/2,0]] :
is_path(cap)? cap :
is_undef(cap)? [] :
assert(false, str("Invalid endcap: ",cap))
) * linewidth;
@ -123,33 +170,47 @@ module stroke(
assert(is_num(width) || (is_vector(width) && len(width)==len(path)));
width = is_num(width)? [for (x=path) width] : width;
endcap1 = first_defined([endcap1, endcaps, "round"]);
endcap2 = first_defined([endcap2, endcaps, "round"]);
endcap1 = first_defined([endcap1, endcaps, if(!closed) plots, "round"]);
endcap2 = first_defined([endcap2, endcaps, plots, "round"]);
joints = first_defined([joints, plots, "round"]);
assert(is_bool(endcap1) || is_string(endcap1) || is_path(endcap1));
assert(is_bool(endcap2) || is_string(endcap2) || is_path(endcap2));
assert(is_bool(joints) || is_string(joints) || is_path(joints));
endcap_width1 = first_defined([endcap_width1, endcap_width, 3.5]);
endcap_width2 = first_defined([endcap_width2, endcap_width, 3.5]);
endcap1_dflts = _shape_defaults(endcap1);
endcap2_dflts = _shape_defaults(endcap2);
joint_dflts = _shape_defaults(joints);
endcap_width1 = first_defined([endcap_width1, endcap_width, plot_width, endcap1_dflts[0]]);
endcap_width2 = first_defined([endcap_width2, endcap_width, plot_width, endcap2_dflts[0]]);
joint_width = first_defined([joint_width, plot_width, joint_dflts[0]]);
assert(is_num(endcap_width1));
assert(is_num(endcap_width2));
assert(is_num(joint_width));
endcap_length1 = first_defined([endcap_length1, endcap_length, endcap_width1*0.5]);
endcap_length2 = first_defined([endcap_length2, endcap_length, endcap_width2*0.5]);
endcap_length1 = first_defined([endcap_length1, endcap_length, plot_length, endcap1_dflts[1]*endcap_width1]);
endcap_length2 = first_defined([endcap_length2, endcap_length, plot_length, endcap2_dflts[1]*endcap_width2]);
joint_length = first_defined([joint_length, plot_length, joint_dflts[1]*joint_width]);
assert(is_num(endcap_length1));
assert(is_num(endcap_length2));
assert(is_num(joint_length));
endcap_extent1 = first_defined([endcap_extent1, endcap_extent, endcap_width1*0.5]);
endcap_extent2 = first_defined([endcap_extent2, endcap_extent, endcap_width2*0.5]);
endcap_extent1 = first_defined([endcap_extent1, endcap_extent, plot_extent, endcap1_dflts[2]*endcap_width1]);
endcap_extent2 = first_defined([endcap_extent2, endcap_extent, plot_extent, endcap2_dflts[2]*endcap_width2]);
joint_extent = first_defined([joint_extent, plot_extent, joint_dflts[2]*joint_width]);
assert(is_num(endcap_extent1));
assert(is_num(endcap_extent2));
assert(is_num(joint_extent));
endcap_angle1 = first_defined([endcap_angle1, endcap_angle]);
endcap_angle2 = first_defined([endcap_angle2, endcap_angle]);
endcap_angle1 = first_defined([endcap_angle1, endcap_angle, plot_angle]);
endcap_angle2 = first_defined([endcap_angle2, endcap_angle, plot_angle]);
joint_angle = first_defined([joint_angle, plot_angle]);
assert(is_undef(endcap_angle1)||is_num(endcap_angle1));
assert(is_undef(endcap_angle2)||is_num(endcap_angle2));
assert(is_undef(joint_angle)||is_num(joint_angle));
endcap_shape1 = _endcap_shape(endcap1, select(width,0), endcap_width1, endcap_length1, endcap_extent1);
endcap_shape2 = _endcap_shape(endcap2, select(width,-1), endcap_width2, endcap_length2, endcap_extent2);
endcap_shape1 = _shape_path(endcap1, select(width,0), endcap_width1, endcap_length1, endcap_extent1);
endcap_shape2 = _shape_path(endcap2, select(width,-1), endcap_width2, endcap_length2, endcap_extent2);
trim1 = select(width,0) * first_defined([
trim1, trim,
@ -201,17 +262,29 @@ module stroke(
// Joints
for (i = [1:1:len(path2)-2]) {
$fn = quantup(segs(widths[i]/2),4);
if (hull) {
hull() {
translate(path2[i]) {
translate(path2[i]) {
if (joints != undef) {
joint_shape = _shape_path(
joints, width[i],
joint_width,
joint_length,
joint_extent
);
v1 = unit(path2[i] - path2[i-1]);
v2 = unit(path2[i+1] - path2[i]);
vec = unit((v1+v2)/2);
mat = is_undef(joint_angle)
? rot(from=BACK,to=v1)
: zrot(joint_angle);
multmatrix(mat) polygon(joint_shape);
} else if (hull) {
hull() {
rot(from=BACK, to=path2[i]-path2[i-1])
circle(d=widths[i]);
rot(from=BACK, to=path2[i+1]-path2[i])
circle(d=widths[i]);
}
}
} else {
translate(path2[i]) {
} else {
rot(from=BACK, to=path2[i]-path2[i-1])
circle(d=widths[i]);
rot(from=BACK, to=path2[i+1]-path2[i])
@ -222,17 +295,16 @@ module stroke(
// Endcap1
translate(path[0]) {
start_vec = select(path,0) - select(path,1);
rot(from=BACK, to=start_vec) {
polygon(endcap_shape1);
}
mat = is_undef(endcap_angle1)? rot(from=BACK,to=start_vec) :
zrot(endcap_angle1);
multmatrix(mat) polygon(endcap_shape1);
}
// Endcap2
translate(select(path,-1)) {
rot(from=BACK, to=end_vec) {
polygon(endcap_shape2);
}
mat = is_undef(endcap_angle2)? rot(from=BACK,to=end_vec) :
zrot(endcap_angle2);
multmatrix(mat) polygon(endcap_shape2);
}
} else {
quatsums = Q_Cumulative([
@ -266,7 +338,30 @@ module stroke(
for (i = [1:1:len(path2)-2]) {
$fn = sides[i];
translate(path2[i]) {
if (hull) {
if (joints != undef) {
joint_shape = _shape_path(
joints, width[i],
joint_width,
joint_length,
joint_extent
);
multmatrix(rotmats[i] * xrot(180)) {
$fn = sides[i];
if (is_undef(joint_angle)) {
rotate_extrude(convexity=convexity) {
right_half(planar=true) {
polygon(joint_shape);
}
}
} else {
rotate([90,0,joint_angle]) {
linear_extrude(height=max(widths[i],0.001), center=true, convexity=convexity) {
polygon(joint_shape);
}
}
}
}
} else if (hull) {
hull(){
multmatrix(rotmats[i]) {
sphere(d=widths[i],style="aligned");
@ -330,6 +425,60 @@ module stroke(
}
// Function&Module: dashed_stroke()
// Usage: As a Module
// dashed_stroke(path, dashpat, <closed=>);
// Usage: As a Function
// dashes = dashed_stroke(path, dashpat, width=, <closed=>);
// Topics: Paths, Drawing Tools
// See Also: stroke(), path_cut_segs()
// Description:
// Given a path and a dash pattern, creates a dashed line that follows that
// path with the given dash pattern.
// - When called as a function, returns a list of dash sub-paths.
// - When called as a module, draws all those subpaths using `stroke()`.
// Arguments:
// path = The path to subdivide into dashes.
// dashpat = A list of alternating dash lengths and space lengths for the dash pattern. This will be scaled by the width of the line.
// ---
// width = The width of the dashed line to draw. Module only. Default: 1
// closed = If true, treat path as a closed polygon. Default: false
// Example(2D): Open Path
// path = [for (a=[-180:10:180]) [a/3,20*sin(a)]];
// dashed_stroke(path, [3,2], width=1);
// Example(2D): Closed Polygon
// path = circle(d=100,$fn=72);
// dashpat = [10,2,3,2,3,2];
// dashed_stroke(path, dashpat, width=1, closed=true);
// Example(FlatSpin,VPD=250): 3D Dashed Path
// path = [for (a=[-180:5:180]) [a/3, 20*cos(3*a), 20*sin(3*a)]];
// dashed_stroke(path, [3,2], width=1);
function dashed_stroke(path, dashpat=[3,3], closed=false) =
let(
path = closed? close_path(path) : path,
dashpat = len(dashpat)%2==0? dashpat : concat(dashpat,[0]),
plen = path_length(path),
dlen = sum(dashpat),
doff = cumsum(dashpat),
reps = floor(plen / dlen),
step = plen / reps,
cuts = [
for (i=[0:1:reps-1], off=doff)
let (st=i*step, x=st+off)
if (x>0 && x<plen) x
],
dashes = path_cut_segs(path, cuts, closed=false),
evens = [for (i=idx(dashes)) if (i%2==0) dashes[i]]
) evens;
module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) {
segs = dashed_stroke(path, dashpat=dashpat*width, closed=closed);
for (seg = segs)
stroke(seg, width=width, endcaps=false);
}
// Function&Module: arc()
// Usage: 2D arc from 0º to `angle` degrees.
// arc(N, r|d=, angle);

View file

@ -47,7 +47,6 @@ test_projection_on_plane();
test_plane_point_nearest_origin();
test_distance_from_plane();
test_closest_point_on_plane();
test__general_plane_line_intersection();
test_plane_line_angle();
test_normalize_plane();
@ -111,15 +110,6 @@ function info_str(list,i=0,string=chr(10)) =
: info_str(list,i+1,str(string,str(list[i][0],_valstr(list[i][1]),chr(10))));
module test_closest_point_on_plane(){
plane = rands(-5,5,4,seed=175)+[10,0,0,0];
point = rands(-1,1,3,seed=477);
point2 = closest_point_on_plane(plane,point);
assert_approx(norm(point-point2), abs(distance_from_plane(plane,point)));
}
*test_closest_point_on_plane();
module test_normalize_plane(){
plane = rands(-5,5,4,seed=333)+[10,0,0,0];
plane2 = normalize_plane(plane);