Fix Examples: that should be Example:

Add closed option to path_merge_collinear
Add nonzero to decompose_path
offset() bugfix for paths whose endpoints are equal
vnf doc tweaks
This commit is contained in:
Adrian Mariano 2021-09-20 18:34:22 -04:00
parent b96fbca930
commit ef9f54c369
9 changed files with 188 additions and 166 deletions

View file

@ -50,7 +50,7 @@ function ident(n) = [
// Arguments:
// x = The value to test for being an affine matrix.
// dim = The number of dimensions the given affine is required to be for. Generally 2 for 2D or 3 for 3D. If given as a list of integers, allows any of the given dimensions. Default: `[2,3]`
// Examples:
// Example:
// bool = is_affine(affine2d_scale([2,3])); // Returns true
// bool = is_affine(affine3d_scale([2,3,4])); // Returns true
// bool = is_affine(affine3d_scale([2,3,4]),2); // Returns false
@ -74,7 +74,7 @@ function is_affine(x,dim=[2,3]) =
// for a simple scaling of z. Note that an input which is only a zscale returns false.
// Arguments:
// t = The transformation matrix to check.
// Examples:
// Example:
// b = is_2d_transform(zrot(45)); // Returns: true
// b = is_2d_transform(yrot(45)); // Returns: false
// b = is_2d_transform(xrot(45)); // Returns: false

View file

@ -141,12 +141,10 @@ function reduce(func, list, init=0) =
// list = The input list.
// init = The starting value for the accumulator. Default: 0
// See Also: map(), filter(), reduce(), while(), for_n()
// Examples: Reimplement cumsum()
// echo(accumulate(function (a,b) a+b, [3,4,5],0));
// // ECHO: [3,7,12]
// Examples: Reimplement cumprod()
// echo(accumulate(f_mul(),[3,4,5],1));
// // ECHO: [3,12,60,360]
// Example: Reimplement cumsum()
// echo(accumulate(function (a,b) a+b, [3,4,5],0)); // ECHO: [3,7,12]
// Example: Reimplement cumprod()
// echo(accumulate(f_mul(),[3,4,5],1)); // ECHO: [3,12,60,360]
function accumulate(func, list, init=0) =
assert(is_function(func))
assert(is_list(list))
@ -313,7 +311,7 @@ function binsearch(key, list, idx, cmp=f_cmp()) =
// Arguments:
// x = The value to get the simple hash value of.
// See Also: hashmap()
// Examples:
// Example:
// x = simple_hash("Foobar");
// x = simple_hash([[10,20],[-5,3]]);
function simple_hash(x) =

View file

@ -37,7 +37,7 @@
// Arguments:
// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
// mod = The metric module/modulus of the gear.
// Examples:
// Example:
// circp = circular_pitch(pitch=5);
// circp = circular_pitch(mod=2);
function circular_pitch(pitch=5, mod) =
@ -54,7 +54,7 @@ function circular_pitch(pitch=5, mod) =
// Arguments:
// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
// mod = The metric module/modulus of the gear.
// Examples:
// Example:
// dp = diametral_pitch(pitch=5);
// dp = diametral_pitch(mod=2);
function diametral_pitch(pitch=5, mod) =
@ -96,7 +96,7 @@ function module_value(pitch=5) = pitch / PI;
// Arguments:
// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
// mod = The metric module/modulus of the gear.
// Examples:
// Example:
// ad = adendum(pitch=5);
// ad = adendum(mod=2);
// Example(2D):
@ -123,7 +123,7 @@ function adendum(pitch=5, mod) =
// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
// clearance = If given, sets the clearance between meshing teeth.
// mod = The metric module/modulus of the gear.
// Examples:
// Example:
// ddn = dedendum(pitch=5);
// ddn = dedendum(mod=2);
// Example(2D):
@ -152,7 +152,7 @@ function dedendum(pitch=5, clearance, mod) =
// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
// teeth = The number of teeth on the gear.
// mod = The metric module/modulus of the gear.
// Examples:
// Example:
// pr = pitch_radius(pitch=5, teeth=11);
// pr = pitch_radius(mod=2, teeth=20);
// Example(2D):
@ -177,7 +177,7 @@ function pitch_radius(pitch=5, teeth=11, mod) =
// clearance = If given, sets the clearance between meshing teeth.
// interior = If true, calculate for an interior gear.
// mod = The metric module/modulus of the gear.
// Examples:
// Example:
// or = outer_radius(pitch=5, teeth=20);
// or = outer_radius(mod=2, teeth=16);
// Example(2D):
@ -203,7 +203,7 @@ function outer_radius(pitch=5, teeth=11, clearance, interior=false, mod) =
// clearance = If given, sets the clearance between meshing teeth.
// interior = If true, calculate for an interior gear.
// mod = The metric module/modulus of the gear.
// Examples:
// Example:
// rr = root_radius(pitch=5, teeth=11);
// rr = root_radius(mod=2, teeth=16);
// Example(2D):
@ -228,7 +228,7 @@ function root_radius(pitch=5, teeth=11, clearance, interior=false, mod) =
// teeth = The number of teeth on the gear.
// pressure_angle = Pressure angle in degrees. Controls how straight or bulged the tooth sides are.
// mod = The metric module/modulus of the gear.
// Examples:
// Example:
// br = base_radius(pitch=5, teeth=20, pressure_angle=20);
// br = base_radius(mod=2, teeth=18, pressure_angle=20);
// Example(2D):
@ -253,7 +253,7 @@ function base_radius(pitch=5, teeth=11, pressure_angle=28, mod) =
// teeth = Number of teeth that this gear has.
// mate_teeth = Number of teeth that the matching gear has.
// drive_angle = Angle between the drive shafts of each gear. Default: 90º.
// Examples:
// Example:
// ang = bevel_pitch_angle(teeth=18, mate_teeth=30);
// Example(2D):
// t1 = 13; t2 = 19; pitch=5;
@ -287,7 +287,7 @@ function bevel_pitch_angle(teeth, mate_teeth, drive_angle=90) =
// crowning = The amount to oversize the virtual hobbing cutter used to make the teeth, to add a slight crowning to the teeth to make them fir the work easier. Default: 1
// clearance = Clearance gap at the bottom of the inter-tooth valleys.
// mod = The metric module/modulus of the gear.
// Examples:
// Example:
// thick = worm_gear_thickness(pitch=5, teeth=36, worm_diam=30);
// thick = worm_gear_thickness(mod=2, teeth=28, worm_diam=25);
// Example(2D):

View file

@ -1049,7 +1049,7 @@ module rainbow(list, stride=1)
{
ll = len(list);
huestep = 360 / ll;
hues = [for (i=[0:1:ll-1]) posmod(i*huestep+i*360/stride,360)];
hues = shuffle([for (i=[0:1:ll-1]) posmod(i*huestep+i*360/stride,360)]);
for($idx=idx(list)) {
$item = list[$idx];
HSV(h=hues[$idx]) children();

View file

@ -109,17 +109,18 @@ function _path_select(path, s1, u1, s2, u2, closed=false) =
// path_merge_collinear(path, [eps])
// Arguments:
// path = A list of path points of any dimension.
// closed = treat as closed polygon. Default: false
// eps = Largest positional variance allowed. Default: `EPSILON` (1-e9)
function path_merge_collinear(path, eps=EPSILON) =
function path_merge_collinear(path, closed=false, 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 (!is_collinear(path[i-1], path[i], path[i+1], eps=eps)) i,
len(path)-1
for (i=[1:1:len(path)-(closed?1:2)])
if (!is_collinear(path[i-1], path[i], select(path,i+1), eps=eps)) i,
if (!closed) len(path)-1
]
) [for (i=indices) path[i]];
@ -156,6 +157,27 @@ function path_segment_lengths(path, closed=false) =
];
// Function: path_length_fractions()
// Usage:
// fracs = path_length_fractions(path, [closed]);
// Description:
// Returns the distance fraction of each point in the path along the path, so the first
// point is zero and the final point is 1. If the path is closed the length of the output
// will have one extra point because of the final connecting segment that connects the last
// point of the path to the first point.
function path_length_fractions(path, closed=false) =
assert(is_path(path))
assert(is_bool(closed))
let(
lengths = [
0,
for (i=[0:1:len(path)-(closed?1:2)])
norm(select(path,i+1)-path[i])
],
partial_len = cumsum(lengths),
total_len = last(partial_len)
) partial_len / total_len;
// Function: path_closest_point()
// Usage:
@ -181,6 +203,8 @@ function path_closest_point(path, pt) =
) [min_seg, pts[min_seg]];
// Section: Geometric Properties of Paths
// Function: path_tangents()
// Usage:
// tangs = path_tangents(path, [closed], [uniform]);
@ -574,7 +598,7 @@ function split_path_at_self_crossings(path, closed=true, eps=EPSILON) =
];
function _tag_self_crossing_subpaths(path, closed=true, eps=EPSILON) =
function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) =
let(
subpaths = split_path_at_self_crossings(
path, closed=closed, eps=eps
@ -586,8 +610,8 @@ function _tag_self_crossing_subpaths(path, closed=true, eps=EPSILON) =
n = line_normal(seg) / 2048,
p1 = mp + n,
p2 = mp - n,
p1in = point_in_polygon(p1, path) >= 0,
p2in = point_in_polygon(p2, path) >= 0,
p1in = point_in_polygon(p1, path, nonzero=nonzero) >= 0,
p2in = point_in_polygon(p2, path, nonzero=nonzero) >= 0,
tag = (p1in && p2in)? "I" : "O"
) [tag, subpath]
];
@ -595,7 +619,7 @@ function _tag_self_crossing_subpaths(path, closed=true, eps=EPSILON) =
// Function: decompose_path()
// Usage:
// splitpaths = decompose_path(path, [closed], [eps]);
// splitpaths = decompose_path(path, [nonzero], [closed], [eps]);
// Description:
// Given a possibly self-crossing path, decompose it into non-crossing paths that are on the perimeter
// of the areas bounded by that path.
@ -609,10 +633,10 @@ function _tag_self_crossing_subpaths(path, closed=true, eps=EPSILON) =
// ];
// splitpaths = decompose_path(path, closed=true);
// rainbow(splitpaths) stroke($item, closed=true, width=3);
function decompose_path(path, closed=true, eps=EPSILON) =
function decompose_path(path, nonzero, closed=true, eps=EPSILON) =
let(
path = cleanup_path(path, eps=eps),
tagged = _tag_self_crossing_subpaths(path, closed=closed, eps=eps),
tagged = _tag_self_crossing_subpaths(path, nonzero=nonzero, closed=closed, eps=eps),
kept = [for (sub = tagged) if(sub[0] == "O") sub[1]],
outregion = _assemble_path_fragments(kept, eps=eps)
) outregion;
@ -867,6 +891,7 @@ function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) =
// Function: path_cut()
// Topics: Paths
// See Also: split_path_at_self_crossings()
// Usage:
// path_list = path_cut(path, cutdist, [closed=]);
// Description:
@ -956,6 +981,8 @@ function _sum_preserving_round(data, index=0) =
);
// Section: Changing sampling of paths
// Function: subdivide_path()
// Usage:
// newpath = subdivide_path(path, [N|refine], method);
@ -1052,27 +1079,6 @@ function subdivide_path(path, N, refine, closed=true, exact=true, method="length
);
// Function: path_length_fractions()
// Usage:
// fracs = path_length_fractions(path, [closed]);
// Description:
// Returns the distance fraction of each point in the path along the path, so the first
// point is zero and the final point is 1. If the path is closed the length of the output
// will have one extra point because of the final connecting segment that connects the last
// point of the path to the first point.
function path_length_fractions(path, closed=false) =
assert(is_path(path))
assert(is_bool(closed))
let(
lengths = [
0,
for (i=[0:1:len(path)-(closed?1:2)])
norm(select(path,i+1)-path[i])
],
partial_len = cumsum(lengths),
total_len = last(partial_len)
) partial_len / total_len;
// Function: resample_path()
// Usage:

View file

@ -489,7 +489,7 @@ function _shift_segment(segment, d) =
// Extend to segments to their intersection point. First check if the segments already have a point in common,
// which can happen if two colinear segments are input to the path variant of `offset()`
function _segment_extension(s1,s2) =
norm(s1[1]-s2[0])<1e-6 ? s1[1] : line_intersection(s1,s2);
norm(s1[1]-s2[0])<1e-6 ? s1[1] : line_intersection(s1,s2,LINE,LINE);
function _makefaces(direction, startind, good, pointcount, closed) =
@ -745,7 +745,11 @@ function offset(
quality = max(0,round(quality)),
flip_dir = closed && !is_polygon_clockwise(path)? -1 : 1,
d = flip_dir * (is_def(r) ? r : delta),
shiftsegs = [for(i=[0:len(path)-1]) _shift_segment(select(path,i,i+1), d)],
// shiftsegs = [for(i=[0:len(path)-1]) _shift_segment(select(path,i,i+1), d)],
shiftsegs = [for(i=[0:len(path)-2]) _shift_segment([path[i],path[i+1]], d),
if (closed) _shift_segment([last(path),path[0]],d)
else [path[0],path[1]] // dummy segment, not used
],
// good segments are ones where no point on the segment is less than distance d from any point on the path
good = check_valid ? _good_segments(path, abs(d), shiftsegs, closed, quality) : repeat(true,len(shiftsegs)),
goodsegs = bselect(shiftsegs, good),

View file

@ -1,6 +1,10 @@
//////////////////////////////////////////////////////////////////////
// LibFile: shapes3d.scad
// Common useful shapes and structured objects.
// Some standard modules for making 3d shapes with attachment support, and function forms
// that produce a VNF. Also included are shortcuts cylinders in each orientation and extended versions of
// the standard modules that provide roundovers and chamfers. The sphereoid() module provides
// several different ways to make a sphere, and the text modules let you write text on a path
// so you can place it on a curved object.
// Includes:
// include <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
@ -1396,6 +1400,61 @@ module tube(
// Module: pie_slice()
//
// Description:
// Creates a pie slice shape.
//
// Usage: Typical
// pie_slice(l|h, r, ang, [center]);
// pie_slice(l|h, d=, ang=, ...);
// pie_slice(l|h, r1=|d1=, r2=|d2=, ang=, ...);
// Usage: Attaching Children
// pie_slice(l|h, r, ang, ...) [attachments];
//
// Arguments:
// h / l = height of pie slice.
// r = radius of pie slice.
// ang = pie slice angle in degrees.
// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=UP`.
// ---
// r1 = bottom radius of pie slice.
// r2 = top radius of pie slice.
// d = diameter of pie slice.
// d1 = bottom diameter of pie slice.
// d2 = top diameter of pie slice.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
//
// Example: Cylindrical Pie Slice
// pie_slice(ang=45, l=20, r=30);
// Example: Conical Pie Slice
// pie_slice(ang=60, l=20, d1=50, d2=70);
module pie_slice(
h, r, ang=30, center,
r1, r2, d, d1, d2, l,
anchor, spin=0, orient=UP
) {
l = first_defined([l, h, 1]);
r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10);
r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10);
maxd = max(r1,r2)+0.1;
anchor = get_anchor(anchor, center, BOT, BOT);
attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
difference() {
cyl(r1=r1, r2=r2, h=l);
if (ang<180) rotate(ang) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
difference() {
fwd(maxd/2) cube([2*maxd, maxd, l+0.2], center=true);
if (ang>180) rotate(ang-180) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
}
}
children();
}
}
// Section: Other Round Objects
@ -2204,59 +2263,6 @@ module nil() union(){}
module noop(spin=0, orient=UP) attachable(CENTER,spin,orient, d=0.01) {nil(); children();}
// Module: pie_slice()
//
// Description:
// Creates a pie slice shape.
//
// Usage: Typical
// pie_slice(l|h, r, ang, [center]);
// pie_slice(l|h, d=, ang=, ...);
// pie_slice(l|h, r1=|d1=, r2=|d2=, ang=, ...);
// Usage: Attaching Children
// pie_slice(l|h, r, ang, ...) [attachments];
//
// Arguments:
// h / l = height of pie slice.
// r = radius of pie slice.
// ang = pie slice angle in degrees.
// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=UP`.
// ---
// r1 = bottom radius of pie slice.
// r2 = top radius of pie slice.
// d = diameter of pie slice.
// d1 = bottom diameter of pie slice.
// d2 = top diameter of pie slice.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
//
// Example: Cylindrical Pie Slice
// pie_slice(ang=45, l=20, r=30);
// Example: Conical Pie Slice
// pie_slice(ang=60, l=20, d1=50, d2=70);
module pie_slice(
h, r, ang=30, center,
r1, r2, d, d1, d2, l,
anchor, spin=0, orient=UP
) {
l = first_defined([l, h, 1]);
r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10);
r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10);
maxd = max(r1,r2)+0.1;
anchor = get_anchor(anchor, center, BOT, BOT);
attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
difference() {
cyl(r1=r1, r2=r2, h=l);
if (ang<180) rotate(ang) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
difference() {
fwd(maxd/2) cube([2*maxd, maxd, l+0.2], center=true);
if (ang>180) rotate(ang-180) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
}
}
children();
}
}
// Module: interior_fillet()

View file

@ -832,8 +832,13 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
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
fullcaps = is_bool(caps) ? [caps,caps] : caps,
normalOK = is_undef(normal) || (method!="natural" && is_vector(normal,3))
|| (method=="manual" && same_shape(normal,path))
)
assert(normalOK, method=="natural" ? "Cannot specify normal with the \"natural\" method"
: method=="incremental" ? "Normal with \"incremental\" method must be a 3-vector"
: str("Incompatible normal given. Must be a 3-vector or a list of ",len(path)," 3-vectors"))
assert(capsOK, "caps must be boolean or a list of two booleans")
assert(!closed || !caps, "Cannot make closed shape with caps")
assert(is_undef(normal) || (is_vector(normal) && len(normal)==3) || (is_path(normal) && len(normal)==len(path) && len(normal[0])==3), "Invalid normal specified")

119
vnf.scad
View file

@ -9,9 +9,8 @@
//////////////////////////////////////////////////////////////////////
// Creating Polyhedrons with VNF Structures
// Section: Creating Polyhedrons with VNF Structures
// Section: VNF Testing and Access
// VNF stands for "Vertices'N'Faces". VNF structures are 2-item lists, `[VERTICES,FACES]` where the
// first item is a list of vertex points, and the second is a list of face indices into the vertex
// list. Each VNF is self contained, with face indices referring only to its own vertex list.
@ -21,8 +20,6 @@
EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// Section: Constructing VNFs
// Function: vnf_vertex_array()
// Usage:
// vnf = vnf_vertex_array(points, [caps], [cap1], [cap2], [style], [reverse], [col_wrap], [row_wrap], [vnf]);
@ -207,12 +204,12 @@ function vnf_vertex_array(
// points = List of point lists for each row
// row_wrap = If true then add faces connecting the first row and last row. These rows must differ by at most 2 in length.
// reverse = Set this to reverse the direction of the faces
// Examples: Each row has one more point than the preceeding one.
// Example: Each row has one more point than the preceeding one.
// pts = [for(y=[1:1:10]) [for(x=[0:y-1]) [x,y,y]]];
// vnf = vnf_tri_array(pts);
// vnf_wireframe(vnf,d=.1);
// color("red")move_copies(flatten(pts)) sphere(r=.15,$fn=9);
// Examples: Each row has one more point than the preceeding one.
// Example: Each row has one more point than the preceeding one.
// pts = [for(y=[0:2:10]) [for(x=[-y/2:y/2]) [x,y,y]]];
// vnf = vnf_tri_array(pts);
// vnf_wireframe(vnf,d=.1);
@ -277,58 +274,6 @@ function vnf_tri_array(points, row_wrap=false, reverse=false, vnf=EMPTY_VNF) =
vnf_merge(cleanup=true, [vnf, [flatten(points), faces]]);
// Function: vnf_add_face()
// Usage:
// vnf_add_face(vnf, pts);
// Description:
// Given a VNF structure and a list of face vertex points, adds the face to the VNF structure.
// Returns the modified VNF structure `[VERTICES, FACES]`. It is up to the caller to make
// sure that the points are in the correct order to make the face normal point outwards.
// Arguments:
// vnf = The VNF structure to add a face to.
// pts = The vertex points for the face.
function vnf_add_face(vnf=EMPTY_VNF, pts) =
assert(is_vnf(vnf))
assert(is_path(pts))
let(
res = set_union(vnf[0], pts, get_indices=true),
face = deduplicate(res[0], closed=true)
) [
res[1],
concat(vnf[1], len(face)>2? [face] : [])
];
// Function: vnf_add_faces()
// Usage:
// vnf_add_faces(vnf, faces);
// Description:
// Given a VNF structure and a list of faces, where each face is given as a list of vertex points,
// adds the faces to the VNF structure. Returns the modified VNF structure `[VERTICES, FACES]`.
// It is up to the caller to make sure that the points are in the correct order to make the face
// normals point outwards.
// Arguments:
// vnf = The VNF structure to add a face to.
// faces = The list of faces, where each face is given as a list of vertex points.
function vnf_add_faces(vnf=EMPTY_VNF, faces) =
assert(is_vnf(vnf))
assert(is_list(faces))
let(
res = set_union(vnf[0], flatten(faces), get_indices=true),
idxs = res[0],
nverts = res[1],
offs = cumsum([0, for (face=faces) len(face)]),
ifaces = [
for (i=idx(faces)) [
for (j=idx(faces[i]))
idxs[offs[i]+j]
]
]
) [
nverts,
concat(vnf[1],ifaces)
];
// Function: vnf_merge()
// Usage:
@ -381,6 +326,64 @@ function vnf_merge(vnfs, cleanup=false, eps=EPSILON) =
[nverts, nfaces];
// Function: vnf_add_face()
// Usage:
// vnf_add_face(vnf, pts);
// Description:
// Given a VNF structure and a list of face vertex points, adds the face to the VNF structure.
// Returns the modified VNF structure `[VERTICES, FACES]`. It is up to the caller to make
// sure that the points are in the correct order to make the face normal point outwards.
// Arguments:
// vnf = The VNF structure to add a face to.
// pts = The vertex points for the face.
function vnf_add_face(vnf=EMPTY_VNF, pts) =
assert(is_vnf(vnf))
assert(is_path(pts))
let(
res = set_union(vnf[0], pts, get_indices=true),
face = deduplicate(res[0], closed=true)
) [
res[1],
concat(vnf[1], len(face)>2? [face] : [])
];
// Function: vnf_add_faces()
// Usage:
// vnf_add_faces(vnf, faces);
// Description:
// Given a VNF structure and a list of faces, where each face is given as a list of vertex points,
// adds the faces to the VNF structure. Returns the modified VNF structure `[VERTICES, FACES]`.
// It is up to the caller to make sure that the points are in the correct order to make the face
// normals point outwards.
// Arguments:
// vnf = The VNF structure to add a face to.
// faces = The list of faces, where each face is given as a list of vertex points.
function vnf_add_faces(vnf=EMPTY_VNF, faces) =
assert(is_vnf(vnf))
assert(is_list(faces))
let(
res = set_union(vnf[0], flatten(faces), get_indices=true),
idxs = res[0],
nverts = res[1],
offs = cumsum([0, for (face=faces) len(face)]),
ifaces = [
for (i=idx(faces)) [
for (j=idx(faces[i]))
idxs[offs[i]+j]
]
]
) [
nverts,
concat(vnf[1],ifaces)
];
// Section: VNF Testing and Access
// Function: is_vnf()
// Usage:
// bool = is_vnf(x);