diff --git a/affine.scad b/affine.scad index b1a328a..07c662c 100644 --- a/affine.scad +++ b/affine.scad @@ -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 diff --git a/fnliterals.scad b/fnliterals.scad index f5f5951..c0cde8b 100644 --- a/fnliterals.scad +++ b/fnliterals.scad @@ -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) = diff --git a/gears.scad b/gears.scad index f44acd7..58a9122 100644 --- a/gears.scad +++ b/gears.scad @@ -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): diff --git a/mutators.scad b/mutators.scad index 0f51e73..fc5c026 100644 --- a/mutators.scad +++ b/mutators.scad @@ -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(); diff --git a/paths.scad b/paths.scad index 83b301b..dfce2c3 100644 --- a/paths.scad +++ b/paths.scad @@ -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: diff --git a/regions.scad b/regions.scad index c7d771d..e839fe0 100644 --- a/regions.scad +++ b/regions.scad @@ -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), diff --git a/shapes3d.scad b/shapes3d.scad index 1a135f4..ab01138 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -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 ////////////////////////////////////////////////////////////////////// @@ -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() diff --git a/skin.scad b/skin.scad index 4640086..5deceaa 100644 --- a/skin.scad +++ b/skin.scad @@ -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") diff --git a/vnf.scad b/vnf.scad index 0cfe001..3ea55d3 100644 --- a/vnf.scad +++ b/vnf.scad @@ -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);