mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 00:09:41 +00:00
Added force_list, path_to_bezier, smooth_path, associate_vertices,
improved skin and sweep error handling. Allow path_sweep to take a 2d path.
This commit is contained in:
parent
469b4cb525
commit
51af394c24
6 changed files with 140 additions and 15 deletions
|
@ -851,6 +851,15 @@ function enumerate(l,idx=undef) =
|
|||
[for (i=[0:1:len(l)-1]) concat([i], [for (j=idx) l[i][j]])];
|
||||
|
||||
|
||||
// Function: force_list()
|
||||
// Usage:
|
||||
// list = force_list(value)
|
||||
// Description:
|
||||
// If value is a list returns value, otherwise returns [value]. Makes it easy to
|
||||
// treat a scalar input consistently as a singleton list along with list inputs.
|
||||
function force_list(value) = is_list(value) ? value : [value];
|
||||
|
||||
|
||||
// Function: pair()
|
||||
// Usage:
|
||||
// pair(v)
|
||||
|
|
27
beziers.scad
27
beziers.scad
|
@ -9,8 +9,8 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
include <BOSL2/vnf.scad>
|
||||
|
||||
include <vnf.scad>
|
||||
include <skin.scad>
|
||||
|
||||
// Section: Terminology
|
||||
// **Polyline**: A series of points joined by straight line segements.
|
||||
|
@ -318,6 +318,29 @@ function bezier_polyline(bezier, splinesteps=16, N=3) = let(
|
|||
);
|
||||
|
||||
|
||||
// Function: path_to_bezier()
|
||||
// Usage:
|
||||
// path_to_bezier(path,[tangent],[closed]);
|
||||
// Description:
|
||||
// Given an input path and optional path of tangent vectors, computes a cubic (degree 3) bezier path that passes
|
||||
// through every point on the input path and matches the tangent vectors. If you do not supply
|
||||
// the tangent it will be computed using path_tangents. If the path is closed specify this
|
||||
// by setting closed=true.
|
||||
// Arguments:
|
||||
// path = path of points to define the bezier
|
||||
// tangents = optional list of tangent vectors at every point
|
||||
// closed = set to true for a closed path. Default: false
|
||||
function path_to_bezier(path, tangent, closed=false) =
|
||||
assert(is_path(path,dim=undef),"Input path is not a valid path")
|
||||
assert(is_undef(tangent) || is_path(tanget,dim=len(path[0])),"Tangent must be a path of the same dimension as the input path")
|
||||
let(
|
||||
tangent = is_def(tangent)? tangent : path_tangents(path, closed=closed),
|
||||
lastpt = len(path) - (closed?0:1)
|
||||
)
|
||||
[for(i=[0:lastpt-1]) each [path[i], path[i]+tangent[i], select(path,i+1)-select(tangent,i+1)],
|
||||
select(path,lastpt)];
|
||||
|
||||
|
||||
|
||||
// Function: fillet_path()
|
||||
// Usage:
|
||||
|
|
|
@ -412,7 +412,7 @@ function _lcmlist(a) =
|
|||
function lcm(a,b=[]) =
|
||||
!is_list(a) && !is_list(b) ? _lcm(a,b) :
|
||||
let(
|
||||
arglist = concat((is_list(a)?a:[a]), (is_list(b)?b:[b]))
|
||||
arglist = concat(force_list(a),force_list(b))
|
||||
)
|
||||
assert(len(arglist)>0,"invalid call to lcm with empty list(s)")
|
||||
_lcmlist(arglist);
|
||||
|
|
|
@ -46,7 +46,7 @@ function is_path(list, dim=[2,3], fast=false) =
|
|||
fast? is_list(list) && is_vector(list[0],fast=true) :
|
||||
is_list(list) && is_list(list[0]) && len(list)>1 &&
|
||||
let( d = len(list[0]) )
|
||||
(is_undef(dim) || in_list(d, is_list(dim)?dim:[dim]) ) &&
|
||||
(is_undef(dim) || in_list(d, force_list(dim))) &&
|
||||
is_list_of(list, replist(0,d));
|
||||
|
||||
|
||||
|
@ -287,7 +287,7 @@ function path_closest_point(path, pt) =
|
|||
// The returns vectors will be normalized to length 1.
|
||||
function path_tangents(path, closed=false) =
|
||||
assert(is_path(path))
|
||||
[for(t=deriv(path)) unit(t)];
|
||||
[for(t=deriv(path,closed=closed)) unit(t)];
|
||||
|
||||
|
||||
// Function: path_normals()
|
||||
|
@ -862,6 +862,7 @@ module path_extrude(path, convexity=10, clipsize=100) {
|
|||
// polyline = [for (a=[0:30:210]) 10*[cos(a), sin(a), sin(a)]];
|
||||
// trace_polyline(polyline, showpts=true, size=0.5, color="lightgreen");
|
||||
module trace_polyline(pline, closed=false, showpts=false, N=1, size=1, color="yellow") {
|
||||
assert(is_path(pline),"Input pline is not a path");
|
||||
sides = segs(size/2);
|
||||
pline = closed? close_path(pline) : pline;
|
||||
if (showpts) {
|
||||
|
|
|
@ -407,6 +407,33 @@ function _rounding_offsets(edgespec,z_dir=1) =
|
|||
|
||||
|
||||
|
||||
// Function: smooth_path()
|
||||
// Usage:
|
||||
// smooth_path(path, [tangent], [splinesteps], [closed]
|
||||
// Description:
|
||||
// Smooths the input path using a cubic spline. Every segment of the path will be replaced by a cubic curve
|
||||
// with `splinesteps` points. The cubic interpolation will pass through every input point on the path
|
||||
// and will match the tangents at every point. If you do not specify tangents they will be computed using
|
||||
// path_tangents(). See also path_to_bezier().
|
||||
// Arguments:
|
||||
// path = path to smooth
|
||||
// tangents = tangent vectors of the path
|
||||
// splinesteps = number of points to insert between the path points. Default: 10
|
||||
// closed = set to true for a closed path. Default: false
|
||||
// Example(2D): Original path in green, smoothed path in yellow:
|
||||
// color("green")stroke(square(4), width=0.1);
|
||||
// stroke(smooth_path(square(4)), width=0.1);
|
||||
// Example(2D): Closing the path changes the end tangents
|
||||
// polygon(smooth_path(square(4), closed=true));
|
||||
// Example(FlatSpin): Works on 3d paths as well
|
||||
// path = [[0,0,0],[3,3,2],[6,0,1],[9,9,0]];
|
||||
// trace_polyline(smooth_path(path),size=.3);
|
||||
function smooth_path(path, tangent, splinesteps=10, closed=false) =
|
||||
let(
|
||||
bez = path_to_bezier(path, tangent=tangent, closed=closed)
|
||||
)
|
||||
bezier_polyline(bez,splinesteps=splinesteps);
|
||||
|
||||
|
||||
// Module: offset_sweep()
|
||||
//
|
||||
|
|
85
skin.scad
85
skin.scad
|
@ -324,6 +324,7 @@ module skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=
|
|||
|
||||
|
||||
function skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z) =
|
||||
assert(is_def(slices),"The slices argument must be specified.")
|
||||
assert(is_list(profiles) && len(profiles)>1, "Must provide at least two profiles")
|
||||
let( bad = [for(i=idx(profiles)) if (!(is_path(profiles[i]) && len(profiles[i])>2)) i])
|
||||
assert(len(bad)==0, str("Profiles ",bad," are not a paths or have length less than 3"))
|
||||
|
@ -715,6 +716,71 @@ function _find_one_tangent(curve, edge, curve_offset=[0,0,0], closed=true) =
|
|||
zero_cross[min_index(d)];
|
||||
|
||||
|
||||
// Function: associate_vertices()
|
||||
// Usage:
|
||||
// associate_vertices(polygons, split)
|
||||
// Description:
|
||||
// Takes as input a list of polygons and duplicates specified vertices in each polygon in the list through the series so
|
||||
// that the input can be passed to `skin()`. This allows you to decide how the vertices are linked up rather than accepting
|
||||
// the automatically computed minimal distance linkage. However, the number of vertices in the polygons must not decrease in the list.
|
||||
// The output is a list of polygons that all have the same number of vertices with some duplicates. You specify the vertix splitting
|
||||
// using the `split` which is a list where each entry corresponds to a polygon: split[i] is a value or list specfying which vertices in polygon i to split.
|
||||
// Give the empty list if you don't want a split for a particular polygon. If you list a vertex once then it will be split and mapped to
|
||||
// two vertices in the next polygon. If you list it N times then N copies will be created to map to N+1 vertices in the next polygon.
|
||||
// You must ensure that each mapping produces the correct number of vertices to exactly map onto every vertex of the next polygon.
|
||||
// Note that if you split (only) vertex i of a polygon that means it will map to vertices i and i+1 of the next polygon. Vertex 0 will always
|
||||
// map to vertex 0 and the last vertices will always map to each other, so if you want something different than that you'll need to reindex
|
||||
// your polygons.
|
||||
// Arguments:
|
||||
// polygons = list of polygons to split
|
||||
// split = list of lists of split vertices
|
||||
// Example(FlatSpin): If you skin together a square and hexagon using the optimal distance method you get two triangular faces on opposite sides:
|
||||
// sq = regular_ngon(4,side=2);
|
||||
// hex = apply(rot(15),hexagon(side=2));
|
||||
// skin([sq,hex], slices=10, refine=10, method="distance", z=[0,4]);
|
||||
// Example(FlatSpin): Using associate_vertices you can change the location of the triangular faces. Here they are connect to two adjacent vertices of the square:
|
||||
// sq = regular_ngon(4,side=2);
|
||||
// hex = apply(rot(15),hexagon(side=2));
|
||||
// skin(associate_vertices([sq,hex],[[1,2]]), slices=10, refine=10, sampling="segment", z=[0,4]);
|
||||
// Example(FlatSpin): Here the two triangular faces connect to a single vertex on the square. Note that we had to rotate the hexagon to line them up because the vertices match counting forward, so in this case vertex 0 of the square matches to vertices 0, 1, and 2 of the hexagon.
|
||||
// sq = regular_ngon(4,side=2);
|
||||
// hex = apply(rot(60),hexagon(side=2));
|
||||
// skin(associate_vertices([sq,hex],[[0,0]]), slices=10, refine=10, sampling="segment", z=[0,4]);
|
||||
// Example: This example shows several polygons, with only a single vertex split at each step:
|
||||
// sq = regular_ngon(4,side=2);
|
||||
// pent = pentagon(side=2);
|
||||
// hex = hexagon(side=2);
|
||||
// sep = regular_ngon(7,side=2);
|
||||
// skin(associate_vertices([sq,pent,hex,sep], [1,3,4]) ,slices=10, refine=10, method="distance", z=[0,2,4,6]);
|
||||
// Example: The polygons cannot shrink, so if you want to have decreasing polygons you'll need to concatenate multiple results. Note that it is perfectly ok to duplicate a profile as shown here, where the pentagon is duplicated:
|
||||
// sq = regular_ngon(4,side=2);
|
||||
// pent = pentagon(side=2);
|
||||
// grow = associate_vertices([sq,pent],[1]);
|
||||
// shrink = associate_vertices([sq,pent],[2]);
|
||||
// skin(concat(grow, reverse(shrink)), slices=10, refine=10, method="distance", z=[0,2,2,4]);
|
||||
function associate_vertices(polygons, split, curpoly=0) =
|
||||
curpoly==len(polygons)-1 ? polygons :
|
||||
let(
|
||||
polylen = len(polygons[curpoly]),
|
||||
cursplit = force_list(split[curpoly]),
|
||||
fdsa= echo(cursplit=cursplit)
|
||||
)
|
||||
assert(len(split)==len(polygons)-1,str(split,"Split list length mismatch: it has length ", len(split)," but must have length ",len(polygons)-1))
|
||||
assert(polylen<=len(polygons[curpoly+1]),str("Polygon ",curpoly," has more vertices than the next one."))
|
||||
assert(len(cursplit)+polylen == len(polygons[curpoly+1]),
|
||||
str("Polygon ", curpoly, " has ", polylen, " vertices. Next polygon has ", len(polygons[curpoly+1]),
|
||||
" vertices. Split list has length ", len(cursplit), " but must have length ", len(polygons[curpoly+1])-polylen))
|
||||
assert(max(cursplit)<polylen && min(curpoly)>=0,
|
||||
str("Split ",cursplit," at polygon ",curpoly," has invalid vertices. Must be in [0:",polylen-1,"]"))
|
||||
len(cursplit)==0 ? associate_vertices(polygons,split,curpoly+1) :
|
||||
let(
|
||||
splitindex = sort(concat(list_range(polylen), cursplit)),
|
||||
newpoly = [for(i=[0:len(polygons)-1]) i<=curpoly ? select(polygons[i],splitindex) : polygons[i]]
|
||||
)
|
||||
associate_vertices(newpoly, split, curpoly+1);
|
||||
|
||||
|
||||
|
||||
// Function&Module: sweep()
|
||||
// Usage: sweep(shape, transformations, [closed], [caps])
|
||||
// Description:
|
||||
|
@ -759,18 +825,16 @@ function _find_one_tangent(curve, edge, curve_offset=[0,0,0], closed=true) =
|
|||
// sweep(shape, concat(outside,inside));
|
||||
|
||||
function sweep(shape, transformations, closed=false, caps) =
|
||||
assert(is_list_of(transformations, ident(4)), "Input transformations must be a list of numeric 4x4 matrices in sweep")
|
||||
assert(is_path(shape,2), "Input shape must be a 2d path")
|
||||
let(
|
||||
tdim = array_dim(transformations),
|
||||
shapedim = array_dim(shape),
|
||||
caps = is_def(caps) ? caps :
|
||||
closed ? false : true,
|
||||
capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])),
|
||||
fullcaps = is_bool(caps) ? [caps,caps] : caps
|
||||
)
|
||||
assert(len(tdim)==3 && tdim[1]==4 && tdim[2]==4, "transformations must be a list of 4x4 matrices in sweep")
|
||||
assert(tdim[0]>1, "transformation must be length 2 or more")
|
||||
assert(len(shapedim)==2 && shapedim[0]>2, "shape must be a path of at least 3 points")
|
||||
assert(shapedim[1]==2, "shape must be a path in 2-dimensions")
|
||||
assert(len(transformations), "transformation must be length 2 or more")
|
||||
assert(len(shape)>=3, "shape must be a path of at least 3 points")
|
||||
assert(capsOK, "caps must be boolean or a list of two booleans")
|
||||
assert(!closed || !caps, "Cannot make closed shape with caps")
|
||||
_skin_core([for(i=[0:len(transformations)-(closed?0:1)]) apply(transformations[i%len(transformations)],path3d(shape))],caps=fullcaps);
|
||||
|
@ -783,7 +847,7 @@ module sweep(shape, transformations, closed=false, caps, convexity=10) {
|
|||
// Function&Module: path_sweep()
|
||||
// Usage: path_sweep(shape, path, [method], [normal], [closed], [twist], [twist_by_length], [symmetry], [last_normal], [tangent], [relaxed], [caps], [convexity], [transforms])
|
||||
// Description:
|
||||
// Takes as input a 2d shape (specified as a point list) and a 3d path and constructs a polyhedron by sweeping the shape along the path.
|
||||
// Takes as input a 2d shape (specified as a point list) and a 2d or 3d path and constructs a polyhedron by sweeping the shape along the path.
|
||||
// When run as a module returns the polyhedron geometry. When run as a function returns a VNF by default or if you set `transforms=true` then
|
||||
// it returns a list of transformations suitable as input to `sweep`.
|
||||
//
|
||||
|
@ -858,7 +922,7 @@ module sweep(shape, transformations, closed=false, caps, convexity=10) {
|
|||
// Example: Sweep along a clockwise elliptical arc, using "natural" method, which lines up the X axis of the shape with the direction of curvature. This means the X axis will point inward, so a counterclockwise arc gives:
|
||||
// ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]];
|
||||
// elliptic_arc = xscale(2, p=arc($fn=64,angle=[0,180], r=30)); // Counter-clockwise
|
||||
// path_sweep(ushape, path3d(elliptic_arc), method="natural");
|
||||
// path_sweep(ushape, elliptic_arc, method="natural");
|
||||
// Example: Sweep along a clockwise elliptical arc, using "natural" method. If the curve is clockwise than the shape flips upside-down to align the X axis.
|
||||
// ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]];
|
||||
// elliptic_arc = xscale(2, p=arc($fn=64,angle=[180,0], r=30)); // Clockwise
|
||||
|
@ -1069,9 +1133,10 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
assert(!closed || twist % (360/symmetry)==0, str("For a closed sweep, twist must be a multiple of 360/symmetry = ",360/symmetry))
|
||||
assert(closed || symmetry==1, "symmetry must be 1 when closed is false")
|
||||
assert(is_integer(symmetry) && symmetry>0, "symmetry must be a positive integer")
|
||||
assert(is_path(shape) && len(shape[0])==2, "shape must be a 2d path")
|
||||
assert(is_path(path) && len(path[0])==3, "path must be a 3d path")
|
||||
assert(is_path(shape,2), "shape must be a 2d path")
|
||||
assert(is_path(path), "input path is not a path")
|
||||
let(
|
||||
path = path3d(path),
|
||||
caps = is_def(caps) ? caps :
|
||||
closed ? false : true,
|
||||
capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])),
|
||||
|
|
Loading…
Reference in a new issue