mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
Added dashed_stroke(). path_cut() -> path_cut_points().
This commit is contained in:
parent
1ee6e3a2e7
commit
3682f7b27c
5 changed files with 405 additions and 167 deletions
134
coords.scad
134
coords.scad
|
@ -9,9 +9,12 @@
|
||||||
// Section: Coordinate Manipulation
|
// Section: Coordinate Manipulation
|
||||||
|
|
||||||
// Function: point2d()
|
// Function: point2d()
|
||||||
|
// Usage:
|
||||||
|
// pt = point2d(p, <fill>);
|
||||||
|
// Topics: Coordinates, Points
|
||||||
|
// See Also: path2d(), point3d(), path3d()
|
||||||
// Description:
|
// Description:
|
||||||
// Returns a 2D vector/point from a 2D or 3D vector.
|
// Returns a 2D vector/point from a 2D or 3D vector. If given a 3D point, removes the Z coordinate.
|
||||||
// If given a 3D point, removes the Z coordinate.
|
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// p = The coordinates to force into a 2D vector/point.
|
// p = The coordinates to force into a 2D vector/point.
|
||||||
// fill = Value to fill missing values in vector with.
|
// 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()
|
// Function: path2d()
|
||||||
|
// Usage:
|
||||||
|
// pts = path2d(points);
|
||||||
|
// Topics: Coordinates, Points, Paths
|
||||||
|
// See Also: point2d(), point3d(), path3d()
|
||||||
// Description:
|
// Description:
|
||||||
// Returns a list of 2D vectors/points from a list of 2D, 3D or higher
|
// Returns a list of 2D vectors/points from a list of 2D, 3D or higher dimensional vectors/points.
|
||||||
// dimensional vectors/points. Removes the extra coordinates from
|
// Removes the extra coordinates from higher dimensional points. The input must be a path, where
|
||||||
// higher dimensional points. The input must be a path, where
|
// every vector has the same length.
|
||||||
// every vector has the same length.
|
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// points = A list of 2D or 3D points/vectors.
|
// points = A list of 2D or 3D points/vectors.
|
||||||
function path2d(points) =
|
function path2d(points) =
|
||||||
|
@ -34,6 +40,10 @@ function path2d(points) =
|
||||||
|
|
||||||
|
|
||||||
// Function: point3d()
|
// Function: point3d()
|
||||||
|
// Usage:
|
||||||
|
// pt = point3d(p, <fill>);
|
||||||
|
// Topics: Coordinates, Points
|
||||||
|
// See Also: path2d(), point2d(), path3d()
|
||||||
// Description:
|
// Description:
|
||||||
// Returns a 3D vector/point from a 2D or 3D vector.
|
// Returns a 3D vector/point from a 2D or 3D vector.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
|
@ -43,6 +53,10 @@ function point3d(p, fill=0) = [for (i=[0:2]) (p[i]==undef)? fill : p[i]];
|
||||||
|
|
||||||
|
|
||||||
// Function: path3d()
|
// Function: path3d()
|
||||||
|
// Usage:
|
||||||
|
// pts = path3d(points, <fill>);
|
||||||
|
// Topics: Coordinates, Points, Paths
|
||||||
|
// See Also: point2d(), path2d(), point3d()
|
||||||
// Description:
|
// Description:
|
||||||
// Returns a list of 3D vectors/points from a list of 2D or higher dimensional vectors/points
|
// 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.
|
// by removing extra coordinates or adding the z coordinate.
|
||||||
|
@ -63,6 +77,10 @@ function path3d(points, fill=0) =
|
||||||
|
|
||||||
|
|
||||||
// Function: point4d()
|
// Function: point4d()
|
||||||
|
// Usage:
|
||||||
|
// pt = point4d(p, <fill>);
|
||||||
|
// Topics: Coordinates, Points
|
||||||
|
// See Also: point2d(), path2d(), point3d(), path3d(), path4d()
|
||||||
// Description:
|
// Description:
|
||||||
// Returns a 4D vector/point from a 2D or 3D vector.
|
// Returns a 4D vector/point from a 2D or 3D vector.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
|
@ -72,12 +90,15 @@ function point4d(p, fill=0) = [for (i=[0:3]) (p[i]==undef)? fill : p[i]];
|
||||||
|
|
||||||
|
|
||||||
// Function: path4d()
|
// Function: path4d()
|
||||||
|
// Usage:
|
||||||
|
// pt = path4d(points, <fill>);
|
||||||
|
// Topics: Coordinates, Points, Paths
|
||||||
|
// See Also: point2d(), path2d(), point3d(), path3d(), point4d()
|
||||||
// Description:
|
// Description:
|
||||||
// Returns a list of 4D vectors/points from a list of 2D or 3D vectors/points.
|
// Returns a list of 4D vectors/points from a list of 2D or 3D vectors/points.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// points = A list of 2D or 3D points/vectors.
|
// points = A list of 2D or 3D points/vectors.
|
||||||
// fill = Value to fill missing values in vectors with.
|
// fill = Value to fill missing values in vectors with.
|
||||||
|
|
||||||
function path4d(points, fill=0) =
|
function path4d(points, fill=0) =
|
||||||
assert(is_num(fill) || is_vector(fill))
|
assert(is_num(fill) || is_vector(fill))
|
||||||
assert(is_path(points, dim=undef, fast=true), "Input to path4d is not a path")
|
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()
|
// Function: polar_to_xy()
|
||||||
// Usage:
|
// Usage:
|
||||||
// polar_to_xy(r, theta);
|
// pt = polar_to_xy(r, theta);
|
||||||
// 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:
|
// Description:
|
||||||
// Convert polar coordinates to 2D cartesian coordinates.
|
// Convert polar coordinates to 2D cartesian coordinates.
|
||||||
// Returns [X,Y] 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(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]
|
||||||
// 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(
|
function polar_to_xy(r,theta=undef) = let(
|
||||||
rad = theta==undef? r[0] : r,
|
rad = theta==undef? r[0] : r,
|
||||||
t = theta==undef? r[1] : theta
|
t = theta==undef? r[1] : theta
|
||||||
|
@ -122,8 +152,10 @@ function polar_to_xy(r,theta=undef) = let(
|
||||||
|
|
||||||
// Function: xy_to_polar()
|
// Function: xy_to_polar()
|
||||||
// Usage:
|
// Usage:
|
||||||
// xy_to_polar(x,y);
|
// r_theta = xy_to_polar(x,y);
|
||||||
// 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:
|
// Description:
|
||||||
// Convert 2D cartesian coordinates to polar coordinates.
|
// Convert 2D cartesian coordinates to polar coordinates.
|
||||||
// Returns [radius, theta] where theta is the angle counter-clockwise of X+.
|
// 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:
|
// Examples:
|
||||||
// plr = xy_to_polar(20,30);
|
// plr = xy_to_polar(20,30);
|
||||||
// plr = xy_to_polar([40,60]);
|
// 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(
|
function xy_to_polar(x,y=undef) = let(
|
||||||
xx = y==undef? x[0] : x,
|
xx = y==undef? x[0] : x,
|
||||||
yy = y==undef? x[1] : y
|
yy = y==undef? x[1] : y
|
||||||
|
@ -141,11 +180,13 @@ function xy_to_polar(x,y=undef) = let(
|
||||||
|
|
||||||
// Function: project_plane()
|
// Function: project_plane()
|
||||||
// Usage: With the plane defined by 3 Points
|
// 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
|
// 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
|
// 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:
|
// Description:
|
||||||
// Converts the given 3D points from global coordinates to the 2D planar coordinates of the closest
|
// 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
|
// 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];
|
// a=[0,0,0]; b=[10,-10,0]; c=[10,0,10];
|
||||||
// xy = project_plane(pt, a, b, c);
|
// xy = project_plane(pt, a, b, c);
|
||||||
// xy2 = 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) =
|
function project_plane(point, a, b, c) =
|
||||||
is_undef(b) && is_undef(c) && is_list(a)? let(
|
is_undef(b) && is_undef(c) && is_list(a)? let(
|
||||||
mat = is_vector(a,4)? plane_transform(a) :
|
mat = is_vector(a,4)? plane_transform(a) :
|
||||||
|
@ -195,6 +250,8 @@ function project_plane(point, a, b, c) =
|
||||||
// xyz = lift_plane(point, POINTLIST);
|
// xyz = lift_plane(point, POINTLIST);
|
||||||
// Usage: With Plane Definition [A,B,C,D] Where Ax+By+Cz=D
|
// Usage: With Plane Definition [A,B,C,D] Where Ax+By+Cz=D
|
||||||
// xyz = lift_plane(point, PLANE);
|
// xyz = lift_plane(point, PLANE);
|
||||||
|
// Topics: Coordinates, Points, Paths
|
||||||
|
// See Also: project_plane()
|
||||||
// Description:
|
// Description:
|
||||||
// Converts the given 2D point from planar coordinates to the global 3D coordinates of the point on the plane.
|
// 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:
|
// Can be called one of three ways:
|
||||||
|
@ -232,8 +289,10 @@ function lift_plane(point, a, b, c) =
|
||||||
|
|
||||||
// Function: cylindrical_to_xyz()
|
// Function: cylindrical_to_xyz()
|
||||||
// Usage:
|
// Usage:
|
||||||
// cylindrical_to_xyz(r, theta, z)
|
// pt = cylindrical_to_xyz(r, theta, z);
|
||||||
// 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:
|
// Description:
|
||||||
// Convert cylindrical coordinates to 3D cartesian coordinates. Returns [X,Y,Z] cartesian coordinates.
|
// Convert cylindrical coordinates to 3D cartesian coordinates. Returns [X,Y,Z] cartesian coordinates.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
|
@ -252,12 +311,13 @@ function cylindrical_to_xyz(r,theta=undef,z=undef) = let(
|
||||||
|
|
||||||
// Function: xyz_to_cylindrical()
|
// Function: xyz_to_cylindrical()
|
||||||
// Usage:
|
// Usage:
|
||||||
// xyz_to_cylindrical(x,y,z)
|
// rtz = xyz_to_cylindrical(x,y,z);
|
||||||
// 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:
|
// Description:
|
||||||
// Convert 3D cartesian coordinates to cylindrical coordinates.
|
// Convert 3D cartesian coordinates to cylindrical coordinates. Returns [radius,theta,Z].
|
||||||
// Returns [radius,theta,Z]. Theta is the angle counter-clockwise
|
// Theta is the angle counter-clockwise of X+ on the XY plane. Z is height above the XY plane.
|
||||||
// of X+ on the XY plane. Z is height above the XY plane.
|
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// x = X coordinate.
|
// x = X coordinate.
|
||||||
// y = Y coordinate.
|
// y = Y coordinate.
|
||||||
|
@ -272,11 +332,12 @@ function xyz_to_cylindrical(x,y=undef,z=undef) = let(
|
||||||
|
|
||||||
// Function: spherical_to_xyz()
|
// Function: spherical_to_xyz()
|
||||||
// Usage:
|
// Usage:
|
||||||
// spherical_to_xyz(r, theta, phi);
|
// pt = spherical_to_xyz(r, theta, phi);
|
||||||
// spherical_to_xyz([r, theta, phi]);
|
// pt = spherical_to_xyz([r, theta, phi]);
|
||||||
// Description:
|
// Description:
|
||||||
// Convert spherical coordinates to 3D cartesian coordinates.
|
// Convert spherical coordinates to 3D cartesian coordinates. Returns [X,Y,Z] cartesian coordinates.
|
||||||
// Returns [X,Y,Z] cartesian coordinates.
|
// Topics: Coordinates, Points, Paths
|
||||||
|
// See Also: cylindrical_to_xyz(), xyz_to_spherical(), xyz_to_cylindrical()
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// r = distance from origin.
|
// r = distance from origin.
|
||||||
// theta = angle in degrees, counter-clockwise of X+ on the XY plane.
|
// 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()
|
// Function: xyz_to_spherical()
|
||||||
// Usage:
|
// Usage:
|
||||||
// xyz_to_spherical(x,y,z)
|
// r_theta_phi = xyz_to_spherical(x,y,z)
|
||||||
// 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:
|
// Description:
|
||||||
// Convert 3D cartesian coordinates to spherical coordinates.
|
// Convert 3D cartesian coordinates to spherical coordinates. Returns [r,theta,phi], where phi is
|
||||||
// Returns [r,theta,phi], where phi is the angle from the Z+ pole,
|
// the angle from the Z+ pole, and theta is degrees counter-clockwise of X+ on the XY plane.
|
||||||
// and theta is degrees counter-clockwise of X+ on the XY plane.
|
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// x = X coordinate.
|
// x = X coordinate.
|
||||||
// y = Y coordinate.
|
// y = Y coordinate.
|
||||||
|
@ -313,8 +375,10 @@ function xyz_to_spherical(x,y=undef,z=undef) = let(
|
||||||
|
|
||||||
// Function: altaz_to_xyz()
|
// Function: altaz_to_xyz()
|
||||||
// Usage:
|
// Usage:
|
||||||
// altaz_to_xyz(alt, az, r);
|
// pt = altaz_to_xyz(alt, az, r);
|
||||||
// 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:
|
// Description:
|
||||||
// Convert altitude/azimuth/range coordinates to 3D cartesian coordinates.
|
// Convert altitude/azimuth/range coordinates to 3D cartesian coordinates.
|
||||||
// Returns [X,Y,Z] 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()
|
// Function: xyz_to_altaz()
|
||||||
// Usage:
|
// Usage:
|
||||||
// xyz_to_altaz(x,y,z);
|
// alt_az_r = xyz_to_altaz(x,y,z);
|
||||||
// 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:
|
// Description:
|
||||||
// Convert 3D cartesian coordinates to altitude/azimuth/range coordinates.
|
// Convert 3D cartesian coordinates to altitude/azimuth/range coordinates.
|
||||||
// Returns [altitude,azimuth,range], where altitude is angle above the
|
// Returns [altitude,azimuth,range], where altitude is angle above the
|
||||||
|
|
|
@ -999,10 +999,11 @@ function plane_transform(plane) =
|
||||||
|
|
||||||
// Function: projection_on_plane()
|
// Function: projection_on_plane()
|
||||||
// Usage:
|
// Usage:
|
||||||
// projection_on_plane(points);
|
// pts = projection_on_plane(plane, points);
|
||||||
// Description:
|
// 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
|
||||||
// projection of the points on the plane.
|
// 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:
|
// Arguments:
|
||||||
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
|
// 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
|
// points = List of points to project
|
||||||
|
@ -1061,24 +1062,6 @@ function distance_from_plane(plane, point) =
|
||||||
point3d(plane)* point - plane[3];
|
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 [POINT, U] if line intersects plane at one point.
|
||||||
// Returns [LINE, undef] if the line is on the plane.
|
// Returns [LINE, undef] if the line is on the plane.
|
||||||
|
|
120
paths.scad
120
paths.scad
|
@ -114,15 +114,15 @@ function path_subselect(path, s1, u1, s2, u2, closed=false) =
|
||||||
function simplify_path(path, eps=EPSILON) =
|
function simplify_path(path, eps=EPSILON) =
|
||||||
assert( is_path(path), "Invalid path." )
|
assert( is_path(path), "Invalid path." )
|
||||||
assert( is_undef(eps) || (is_finite(eps) && (eps>=0) ), "Invalid tolerance." )
|
assert( is_undef(eps) || (is_finite(eps) && (eps>=0) ), "Invalid tolerance." )
|
||||||
len(path)<=2 ? path
|
len(path)<=2 ? path :
|
||||||
: let(
|
let(
|
||||||
indices = [ 0,
|
indices = [
|
||||||
for (i=[1:1:len(path)-2])
|
0,
|
||||||
if (!collinear(path[i-1],path[i],path[i+1], eps=eps)) i,
|
for (i=[1:1:len(path)-2])
|
||||||
len(path)-1
|
if (!collinear(path[i-1], path[i], path[i+1], eps=eps)) i,
|
||||||
]
|
len(path)-1
|
||||||
)
|
]
|
||||||
[for (i = indices) path[i] ];
|
) [for (i=indices) path[i]];
|
||||||
|
|
||||||
|
|
||||||
// Function: simplify_path_indexed()
|
// 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.
|
// indices = A list of indices into `points` that forms a path.
|
||||||
// eps = Largest angle variance allowed. Default: EPSILON (1-e9) degrees.
|
// eps = Largest angle variance allowed. Default: EPSILON (1-e9) degrees.
|
||||||
function simplify_path_indexed(points, indices, eps=EPSILON) =
|
function simplify_path_indexed(points, indices, eps=EPSILON) =
|
||||||
len(indices)<=2? indices
|
len(indices)<=2? indices :
|
||||||
: let(
|
let(
|
||||||
indices = concat( indices[0],
|
indices = concat(
|
||||||
[for (i=[1:1:len(indices)-2])
|
indices[0],
|
||||||
let(
|
[
|
||||||
i1 = indices[i-1],
|
for (i=[1:1:len(indices)-2]) let(
|
||||||
i2 = indices[i],
|
i1 = indices[i-1],
|
||||||
i3 = indices[i+1]
|
i2 = indices[i],
|
||||||
)
|
i3 = indices[i+1]
|
||||||
if (!collinear(points[i1],points[i2],points[i3], eps=eps)) indices[i]],
|
) if (!collinear(points[i1], points[i2], points[i3], eps=eps))
|
||||||
indices[len(indices)-1] )
|
indices[i]
|
||||||
)
|
],
|
||||||
indices;
|
indices[len(indices)-1]
|
||||||
|
)
|
||||||
|
) indices;
|
||||||
|
|
||||||
|
|
||||||
// Function: path_length()
|
// Function: path_length()
|
||||||
|
@ -178,9 +180,9 @@ function path_length(path,closed=false) =
|
||||||
// closed = true if the path is closed. Default: false
|
// closed = true if the path is closed. Default: false
|
||||||
function path_segment_lengths(path, closed=false) =
|
function path_segment_lengths(path, closed=false) =
|
||||||
[
|
[
|
||||||
for (i=[0:1:len(path)-2]) norm(path[i+1]-path[i]),
|
for (i=[0:1:len(path)-2]) norm(path[i+1]-path[i]),
|
||||||
if (closed) norm(path[0]-path[len(path)-1])
|
if (closed) norm(path[0]-select(path,-1))
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
// Function: path_pos_from_start()
|
// 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);
|
distOK = is_def(n) || (min(distances)>=0 && max(distances)<=length);
|
||||||
assert(distOK,"Cannot fit all of the copies");
|
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;
|
planar = len(path[0])==2;
|
||||||
if (true) for(i=[0:1:len(cutlist)-1]) {
|
if (true) for(i=[0:1:len(cutlist)-1]) {
|
||||||
$pos = cutlist[i][0];
|
$pos = cutlist[i][0];
|
||||||
|
@ -1215,18 +1217,56 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=fals
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function: path_cut_segs()
|
||||||
|
// Usage:
|
||||||
|
// segs = path_cut_segs(path, cutlens, <closed=>);
|
||||||
|
// Topics: Paths
|
||||||
|
// See Also: path_cut_points()
|
||||||
|
// Description:
|
||||||
|
// Given a path and a list of path lengths at which to cut, returns a list of
|
||||||
|
// sub-paths cut from the original path at those path lengths.
|
||||||
|
// Arguments:
|
||||||
|
// path = The original path to split.
|
||||||
|
// cutlens = The list of path lengths to cut at.
|
||||||
|
// 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,cutlens,closed=false) =
|
||||||
|
let(
|
||||||
|
path = closed? close_path(path) : path,
|
||||||
|
cutlist = path_cut_points(path, cutlens),
|
||||||
|
cuts = len(cutlist)
|
||||||
|
) [
|
||||||
|
concat(
|
||||||
|
select(path,0,cutlist[0][1]-1),
|
||||||
|
[cutlist[0][0]]
|
||||||
|
),
|
||||||
|
for(i=[0:cuts-2]) concat(
|
||||||
|
[cutlist[i][0]],
|
||||||
|
slice(path, cutlist[i][1], cutlist[i+1][1]),
|
||||||
|
[cutlist[i+1][0]]
|
||||||
|
),
|
||||||
|
concat(
|
||||||
|
[cutlist[cuts-1][0]],
|
||||||
|
select(path, cutlist[cuts-1][1], -1)
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
// Function: path_cut()
|
|
||||||
|
|
||||||
|
// Function: path_cut_points()
|
||||||
//
|
//
|
||||||
// Usage:
|
// Usage:
|
||||||
// path_cut(path, dists, [closed], [direction])
|
// path_cut_points(path, dists, [closed], [direction])
|
||||||
//
|
//
|
||||||
// Description:
|
// Description:
|
||||||
// Cuts a path at a list of distances from the first point in the path. Returns a list of the cut
|
// 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
|
// 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
|
// 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 returns undef. If you set
|
// 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` will also return the tangent vector to the path and a normal
|
// `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
|
// 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
|
// 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]`.
|
// direction vectors will be `[point, next_index, tangent, normal]`.
|
||||||
|
@ -1239,15 +1279,15 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=fals
|
||||||
//
|
//
|
||||||
// Example(NORENDER):
|
// Example(NORENDER):
|
||||||
// square=[[0,0],[1,0],[1,1],[0,1]];
|
// 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_points(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_points(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_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(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]
|
// 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(path, dists, closed=false, direction=false) =
|
function path_cut_points(path, dists, closed=false, direction=false) =
|
||||||
let(long_enough = len(path) >= (closed ? 3 : 2))
|
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")
|
assert(long_enough,len(path)<2 ? "Two points needed to define a path" : "Closed path must include three points")
|
||||||
!is_list(dists)? path_cut(path, [dists],closed, direction)[0]
|
!is_list(dists)? path_cut_points(path, [dists],closed, direction)[0]
|
||||||
: let(cuts = _path_cut(path,dists,closed))
|
: let(cuts = _path_cut_points(path,dists,closed))
|
||||||
!direction
|
!direction
|
||||||
? cuts
|
? cuts
|
||||||
: let(
|
: let(
|
||||||
|
@ -1257,7 +1297,7 @@ function path_cut(path, dists, closed=false, direction=false) =
|
||||||
hstack(cuts, array_group(dir,1), array_group(normals,1));
|
hstack(cuts, array_group(dir,1), array_group(normals,1));
|
||||||
|
|
||||||
// Main recursive path cut function
|
// 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 :
|
dind == len(dists) ? result :
|
||||||
let(
|
let(
|
||||||
lastpt = len(result)>0? select(result,-1)[0] : [],
|
lastpt = len(result)>0? select(result,-1)[0] : [],
|
||||||
|
@ -1267,7 +1307,7 @@ function _path_cut(path, dists, closed=false, pind=0, dtotal=0, dind=0, result=[
|
||||||
_path_cut_single(path, dists[dind]-dtotal-dpartial, closed, pind)
|
_path_cut_single(path, dists[dind]-dtotal-dpartial, closed, pind)
|
||||||
) is_undef(nextpoint)?
|
) is_undef(nextpoint)?
|
||||||
concat(result, repeat(undef,len(dists)-dind)) :
|
concat(result, repeat(undef,len(dists)-dind)) :
|
||||||
_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
|
// Search for a single cut point in the path
|
||||||
function _path_cut_single(path, dist, closed=false, ind=0, eps=1e-7) =
|
function _path_cut_single(path, dist, closed=false, ind=0, eps=1e-7) =
|
||||||
|
@ -1479,7 +1519,7 @@ function resample_path(path, N, spacing, closed=false) =
|
||||||
N = is_def(N) ? N : round(length/spacing) + (closed?0:1),
|
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
|
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
|
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
|
concat(subindex(cuts,0),closed?[]:[select(path,-1)]); // Then add last point here
|
||||||
|
|
||||||
|
|
|
@ -640,8 +640,8 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
|
||||||
)
|
)
|
||||||
assert(d_first>=0 && d_next>=0, str("Joint value negative when adding path ",i+1))
|
assert(d_first>=0 && d_next>=0, str("Joint value negative when adding path ",i+1))
|
||||||
let(
|
let(
|
||||||
firstcut = path_cut(revresult, d_first, direction=true),
|
firstcut = path_cut_points(revresult, d_first, direction=true),
|
||||||
nextcut = path_cut(nextpath, d_next, direction=true)
|
nextcut = path_cut_points(nextpath, d_next, direction=true)
|
||||||
)
|
)
|
||||||
assert(is_def(firstcut),str("Path ",i," is too short for specified cut distance ",d_first))
|
assert(is_def(firstcut),str("Path ",i," is too short for specified cut distance ",d_first))
|
||||||
assert(is_def(nextcut),str("Path ",i+1," is too short for specified cut distance ",d_next))
|
assert(is_def(nextcut),str("Path ",i+1," is too short for specified cut distance ",d_next))
|
||||||
|
@ -1589,8 +1589,8 @@ function _stroke_end(width,left, right, spec) =
|
||||||
90-vector_angle([newright[1],newright[0],newleft[0]])/2,
|
90-vector_angle([newright[1],newright[0],newleft[0]])/2,
|
||||||
jointleft = 8*cutleft/cos(leftangle)/(1+4*bez_k),
|
jointleft = 8*cutleft/cos(leftangle)/(1+4*bez_k),
|
||||||
jointright = 8*cutright/cos(rightangle)/(1+4*bez_k),
|
jointright = 8*cutright/cos(rightangle)/(1+4*bez_k),
|
||||||
pathcutleft = path_cut(newleft,abs(jointleft)),
|
pathcutleft = path_cut_points(newleft,abs(jointleft)),
|
||||||
pathcutright = path_cut(newright,abs(jointright)),
|
pathcutright = path_cut_points(newright,abs(jointright)),
|
||||||
leftdelete = intright? pathcutleft[1] : pathcutleft[1] + pathclip[1] -1,
|
leftdelete = intright? pathcutleft[1] : pathcutleft[1] + pathclip[1] -1,
|
||||||
rightdelete = intright? pathcutright[1] + pathclip[1] -1 : pathcutright[1],
|
rightdelete = intright? pathcutright[1] + pathclip[1] -1 : pathcutright[1],
|
||||||
leftcorner = line_intersection([pathcutleft[0], newleft[pathcutleft[1]]], [newright[0],newleft[0]]),
|
leftcorner = line_intersection([pathcutleft[0], newleft[pathcutleft[1]]], [newright[0],newleft[0]]),
|
||||||
|
|
285
shapes2d.scad
285
shapes2d.scad
|
@ -16,37 +16,54 @@
|
||||||
// Description:
|
// Description:
|
||||||
// Draws a 2D or 3D path with a given line width. Endcaps can be specified for each end individually.
|
// 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
|
// Figure(Med,NoAxes,VPR=[0,0,0],VPD=250): Endcap Types
|
||||||
// endcaps = [
|
// cap_pairs = [
|
||||||
// ["butt", "square", "round", "chisel", "tail", "tail2"],
|
// ["butt", "chisel" ],
|
||||||
// ["line", "cross", "dot", "diamond", "x", "arrow", "arrow2"]
|
// ["round", "square" ],
|
||||||
|
// ["line", "cross" ],
|
||||||
|
// ["x", "diamond"],
|
||||||
|
// ["dot", "block" ],
|
||||||
|
// ["tail", "arrow" ],
|
||||||
|
// ["tail2", "arrow2" ]
|
||||||
// ];
|
// ];
|
||||||
// for (x=idx(endcaps), y=idx(endcaps[x])) {
|
// for (i = idx(cap_pairs)) {
|
||||||
// cap = endcaps[x][y];
|
// fwd((i-len(cap_pairs)/2+0.5)*13) {
|
||||||
// right(x*60-60+5) fwd(y*10-30) {
|
// stroke([[-20,0], [20,0]], width=3, endcap1=cap_pairs[i][0], endcap2=cap_pairs[i][1]);
|
||||||
// right(28) color("black") text(text=cap, size=5, halign="left", valign="center");
|
// color("black") {
|
||||||
// stroke([[0,0], [20,0]], width=3, endcap_width=3, endcap1=false, endcap2=cap);
|
// stroke([[-20,0], [20,0]], width=0.25, endcaps=false);
|
||||||
// color("black") stroke([[0,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:
|
// 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.
|
// 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.
|
// 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.
|
// 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.
|
// 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.
|
// 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
|
// plot_width = Some plot point shapes are wider than the line. This specifies the width of the shape, in multiples of the line width.
|
||||||
// endcap_width1 = This specifies the size of starting endcap, in multiples of the line width. Default: 3.5
|
// joint_width = Some joint shapes are wider than the line. This specifies the width of the shape, in multiples of the line width.
|
||||||
// endcap_width2 = This specifies the size of ending endcap, in multiples of the line width. Default: 3.5
|
// endcap_width = Some endcap types are wider than the line. This specifies the size of endcaps, in multiples of the line width.
|
||||||
// endcap_length = Length of endcaps, in multiples of the line width. Default: `endcap_width*0.5`
|
// endcap_width1 = This specifies the size of starting endcap, in multiples of the line width.
|
||||||
// endcap_length1 = Length of starting endcap, in multiples of the line width. Default: `endcap_width1*0.5`
|
// endcap_width2 = This specifies the size of ending endcap, in multiples of the line width.
|
||||||
// endcap_length2 = Length of ending endcap, in multiples of the line width. Default: `endcap_width2*0.5`
|
// plot_length = Length of plot point shape, in multiples of the line width.
|
||||||
// endcap_extent = Extents length of endcaps, in multiples of the line width. Default: `endcap_width*0.5`
|
// joint_length = Length of joint shape, in multiples of the line width.
|
||||||
// endcap_extent1 = Extents length of starting endcap, in multiples of the line width. Default: `endcap_width1*0.5`
|
// endcap_length = Length of endcaps, in multiples of the line width.
|
||||||
// endcap_extent2 = Extents length of ending endcap, in multiples of the line width. Default: `endcap_width2*0.5`
|
// endcap_length1 = Length of starting endcap, in multiples of the line width.
|
||||||
// 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_length2 = Length of ending endcap, in multiples of the line width.
|
||||||
// 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)
|
// plot_extent = Extents length of plot point shape, in multiples of the line width.
|
||||||
// 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)
|
// 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.
|
// 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.
|
// 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.
|
// 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
|
// Example(2D): Mixed Endcaps
|
||||||
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
||||||
// stroke(path, width=10, endcap1="tail2", endcap2="arrow2");
|
// 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
|
// Example(2D): Custom Endcap Shapes
|
||||||
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
// 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]];
|
// 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
|
// Example: 3D Path with Mixed Endcaps
|
||||||
// path = rot([15,30,0], p=path3d(pentagon(d=50)));
|
// path = rot([15,30,0], p=path3d(pentagon(d=50)));
|
||||||
// stroke(path, width=2, endcap1="arrow2", endcap2="tail", endcap_angle2=0, $fn=18);
|
// 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(
|
module stroke(
|
||||||
path, width=1, closed=false,
|
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,
|
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
|
convexity=10, hull=true
|
||||||
) {
|
) {
|
||||||
function _endcap_shape(cap,linewidth,w,l,l2) = (
|
function _shape_defaults(cap) =
|
||||||
let(sq2=sqrt(2), l3=l-l2)
|
cap==undef? [1.00, 0.00, 0.00] :
|
||||||
(cap=="round" || cap==true)? circle(d=1, $fn=max(8, segs(w/2))) :
|
cap==false? [1.00, 0.00, 0.00] :
|
||||||
cap=="chisel"? [[-0.5,0], [0,0.5], [0.5,0], [0,-0.5]] :
|
cap==true? [1.00, 1.00, 0.00] :
|
||||||
cap=="square"? [[-0.5,-0.5], [-0.5,0.5], [0.5,0.5], [0.5,-0.5]] :
|
cap=="butt"? [1.00, 0.00, 0.00] :
|
||||||
cap=="diamond"? [[0,w/2], [w/2,0], [0,-w/2], [-w/2,0]] :
|
cap=="round"? [1.00, 1.00, 0.00] :
|
||||||
cap=="dot"? circle(d=3, $fn=max(12, segs(w*3/2))) :
|
cap=="chisel"? [1.00, 1.00, 0.00] :
|
||||||
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=="square"? [1.00, 1.00, 0.00] :
|
||||||
cap=="cross"? [for (a=[0:90:270]) each rot(a,p=[[1,w]/2, [-1,w]/2, [-1,1]/2]) ] :
|
cap=="block"? [3.00, 1.00, 0.00] :
|
||||||
cap=="line"? [[w/2,0.5], [w/2,-0.5], [-w/2,-0.5], [-w/2,0.5]] :
|
cap=="diamond"? [3.50, 1.00, 0.00] :
|
||||||
cap=="arrow"? [[0,0], [w/2,-l2], [w/2,-l2-l], [0,-l], [-w/2,-l2-l], [-w/2,-l2]] :
|
cap=="dot"? [3.00, 1.00, 0.00] :
|
||||||
cap=="arrow2"? [[0,0], [w/2,-l2-l], [0,-l], [-w/2,-l2-l]] :
|
cap=="x"? [3.50, 0.40, 0.00] :
|
||||||
cap=="tail"? [[0,0], [w/2,l2], [w/2,l2-l], [0,-l], [-w/2,l2-l], [-w/2,l2]] :
|
cap=="cross"? [4.50, 0.22, 0.00] :
|
||||||
cap=="tail2"? [[w/2,0], [w/2,-l], [0,-l-l2], [-w/2,-l], [-w/2,0]] :
|
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_path(cap)? cap :
|
||||||
is_undef(cap)? [] :
|
|
||||||
assert(false, str("Invalid endcap: ",cap))
|
assert(false, str("Invalid endcap: ",cap))
|
||||||
) * linewidth;
|
) * linewidth;
|
||||||
|
|
||||||
|
@ -123,33 +170,47 @@ module stroke(
|
||||||
assert(is_num(width) || (is_vector(width) && len(width)==len(path)));
|
assert(is_num(width) || (is_vector(width) && len(width)==len(path)));
|
||||||
width = is_num(width)? [for (x=path) width] : width;
|
width = is_num(width)? [for (x=path) width] : width;
|
||||||
|
|
||||||
endcap1 = first_defined([endcap1, endcaps, "round"]);
|
endcap1 = first_defined([endcap1, endcaps, if(!closed) plots, "round"]);
|
||||||
endcap2 = first_defined([endcap2, endcaps, "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(endcap1) || is_string(endcap1) || is_path(endcap1));
|
||||||
assert(is_bool(endcap2) || is_string(endcap2) || is_path(endcap2));
|
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]);
|
endcap1_dflts = _shape_defaults(endcap1);
|
||||||
endcap_width2 = first_defined([endcap_width2, endcap_width, 3.5]);
|
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_width1));
|
||||||
assert(is_num(endcap_width2));
|
assert(is_num(endcap_width2));
|
||||||
|
assert(is_num(joint_width));
|
||||||
|
|
||||||
endcap_length1 = first_defined([endcap_length1, endcap_length, endcap_width1*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, endcap_width2*0.5]);
|
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_length1));
|
||||||
assert(is_num(endcap_length2));
|
assert(is_num(endcap_length2));
|
||||||
|
assert(is_num(joint_length));
|
||||||
|
|
||||||
endcap_extent1 = first_defined([endcap_extent1, endcap_extent, endcap_width1*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, endcap_width2*0.5]);
|
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_extent1));
|
||||||
assert(is_num(endcap_extent2));
|
assert(is_num(endcap_extent2));
|
||||||
|
assert(is_num(joint_extent));
|
||||||
|
|
||||||
endcap_angle1 = first_defined([endcap_angle1, endcap_angle]);
|
endcap_angle1 = first_defined([endcap_angle1, endcap_angle, plot_angle]);
|
||||||
endcap_angle2 = first_defined([endcap_angle2, endcap_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_angle1)||is_num(endcap_angle1));
|
||||||
assert(is_undef(endcap_angle2)||is_num(endcap_angle2));
|
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_shape1 = _shape_path(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_shape2 = _shape_path(endcap2, select(width,-1), endcap_width2, endcap_length2, endcap_extent2);
|
||||||
|
|
||||||
trim1 = select(width,0) * first_defined([
|
trim1 = select(width,0) * first_defined([
|
||||||
trim1, trim,
|
trim1, trim,
|
||||||
|
@ -201,17 +262,29 @@ module stroke(
|
||||||
// Joints
|
// Joints
|
||||||
for (i = [1:1:len(path2)-2]) {
|
for (i = [1:1:len(path2)-2]) {
|
||||||
$fn = quantup(segs(widths[i]/2),4);
|
$fn = quantup(segs(widths[i]/2),4);
|
||||||
if (hull) {
|
translate(path2[i]) {
|
||||||
hull() {
|
if (joints != undef) {
|
||||||
translate(path2[i]) {
|
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])
|
rot(from=BACK, to=path2[i]-path2[i-1])
|
||||||
circle(d=widths[i]);
|
circle(d=widths[i]);
|
||||||
rot(from=BACK, to=path2[i+1]-path2[i])
|
rot(from=BACK, to=path2[i+1]-path2[i])
|
||||||
circle(d=widths[i]);
|
circle(d=widths[i]);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
|
||||||
translate(path2[i]) {
|
|
||||||
rot(from=BACK, to=path2[i]-path2[i-1])
|
rot(from=BACK, to=path2[i]-path2[i-1])
|
||||||
circle(d=widths[i]);
|
circle(d=widths[i]);
|
||||||
rot(from=BACK, to=path2[i+1]-path2[i])
|
rot(from=BACK, to=path2[i+1]-path2[i])
|
||||||
|
@ -222,17 +295,16 @@ module stroke(
|
||||||
|
|
||||||
// Endcap1
|
// Endcap1
|
||||||
translate(path[0]) {
|
translate(path[0]) {
|
||||||
start_vec = select(path,0) - select(path,1);
|
mat = is_undef(endcap_angle1)? rot(from=BACK,to=start_vec) :
|
||||||
rot(from=BACK, to=start_vec) {
|
zrot(endcap_angle1);
|
||||||
polygon(endcap_shape1);
|
multmatrix(mat) polygon(endcap_shape1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Endcap2
|
// Endcap2
|
||||||
translate(select(path,-1)) {
|
translate(select(path,-1)) {
|
||||||
rot(from=BACK, to=end_vec) {
|
mat = is_undef(endcap_angle2)? rot(from=BACK,to=end_vec) :
|
||||||
polygon(endcap_shape2);
|
zrot(endcap_angle2);
|
||||||
}
|
multmatrix(mat) polygon(endcap_shape2);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quatsums = Q_Cumulative([
|
quatsums = Q_Cumulative([
|
||||||
|
@ -266,7 +338,30 @@ module stroke(
|
||||||
for (i = [1:1:len(path2)-2]) {
|
for (i = [1:1:len(path2)-2]) {
|
||||||
$fn = sides[i];
|
$fn = sides[i];
|
||||||
translate(path2[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(){
|
hull(){
|
||||||
multmatrix(rotmats[i]) {
|
multmatrix(rotmats[i]) {
|
||||||
sphere(d=widths[i],style="aligned");
|
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()
|
// Function&Module: arc()
|
||||||
// Usage: 2D arc from 0º to `angle` degrees.
|
// Usage: 2D arc from 0º to `angle` degrees.
|
||||||
// arc(N, r|d=, angle);
|
// arc(N, r|d=, angle);
|
||||||
|
|
Loading…
Reference in a new issue