mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
Merge pull request #500 from adrianVmariano/master
Fix face orientation bugs
This commit is contained in:
commit
5ed3602391
9 changed files with 171 additions and 156 deletions
|
@ -425,11 +425,15 @@ function repeat(val, n, i=0) =
|
|||
// n = The length of the list of numbers to create.
|
||||
// s = The starting value of the list of numbers.
|
||||
// step = The amount to increment successive numbers in the list.
|
||||
// reverse = Reverse the list. Default: false.
|
||||
// Example:
|
||||
// nl1 = count(5); // Returns: [0,1,2,3,4]
|
||||
// nl2 = count(5,3); // Returns: [3,4,5,6,7]
|
||||
// nl3 = count(4,3,2); // Returns: [3,5,7,9]
|
||||
function count(n,s=0,step=1) = [for (i=[0:1:n-1]) s+i*step];
|
||||
// nl4 = count(5,reverse=true); // Returns: [4,3,2,1,0]
|
||||
// nl5 = count(5,3,reverse=true); // Returns: [7,6,5,4,3]
|
||||
function count(n,s=0,step=1,reverse=false) = reverse? [for (i=[n-1:-1:0]) s+i*step]
|
||||
: [for (i=[0:1:n-1]) s+i*step];
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1328,7 +1328,6 @@ function bezier_patch_degenerate(patch, splinesteps=16, reverse=false, return_ed
|
|||
let(
|
||||
row_degen = [for(row=patch) all_equal(row)],
|
||||
col_degen = [for(col=transpose(patch)) all_equal(col)],
|
||||
|
||||
top_degen = row_degen[0],
|
||||
bot_degen = last(row_degen),
|
||||
left_degen = col_degen[0],
|
||||
|
@ -1362,7 +1361,7 @@ function bezier_patch_degenerate(patch, splinesteps=16, reverse=false, return_ed
|
|||
for(j=[0:splinesteps-2]) bezier_points(subindex(bpatch,j+1), lerpn(0,1,rowcount[j])),
|
||||
[last(bpatch[0])]
|
||||
],
|
||||
vnf = vnf_tri_array(pts, reverse=reverse)
|
||||
vnf = vnf_tri_array(pts, reverse=!reverse)
|
||||
) [
|
||||
vnf,
|
||||
[
|
||||
|
@ -1390,7 +1389,7 @@ function bezier_patch_degenerate(patch, splinesteps=16, reverse=false, return_ed
|
|||
[bpatch[0][0]],
|
||||
for(j=[1:splinesteps]) bezier_points(subindex(bpatch,j), lerpn(0,1,rowmax[j]+1))
|
||||
],
|
||||
vnf = vnf_tri_array(pts, reverse=reverse)
|
||||
vnf = vnf_tri_array(pts, reverse=!reverse)
|
||||
) [
|
||||
vnf,
|
||||
[
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
// Section: Type handling helpers.
|
||||
|
||||
|
||||
|
@ -323,7 +324,8 @@ function first_defined(v,recursive=false,_i=0) =
|
|||
// Examples:
|
||||
// length = one_defined([length,L,l], ["length","L","l"]);
|
||||
// length = one_defined([length,L,l], "length,L,l", dflt=1);
|
||||
function one_defined(vals, names, dflt=_UNDEF) =
|
||||
|
||||
function one_defined(vals, names, dflt=_UNDEF) =
|
||||
let(
|
||||
checkargs = is_list(names)? assert(len(vals) == len(names)) :
|
||||
is_string(names)? let(
|
||||
|
@ -534,10 +536,6 @@ function get_radius(r1, r2, r, d1, d2, d, dflt) =
|
|||
// echo(f(undef, arg1="given1", undef));
|
||||
// // ["given1", undef, undef]
|
||||
|
||||
// a value that the user should never enter randomly;
|
||||
// result of `dd if=/dev/random bs=32 count=1 |base64` :
|
||||
_UNDEF="LRG+HX7dy89RyHvDlAKvb9Y04OTuaikpx205CTh8BSI";
|
||||
|
||||
/* Note: however tempting it might be, it is *not* possible to accept
|
||||
* named argument as a list [named1, named2, ...] (without default
|
||||
* values), because the values [named1, named2...] themselves might be
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
// include <BOSL2/std.scad>
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
// a value that the user should never enter randomly;
|
||||
// result of `dd if=/dev/random bs=32 count=1 |base64` :
|
||||
_UNDEF="LRG+HX7dy89RyHvDlAKvb9Y04OTuaikpx205CTh8BSI";
|
||||
|
||||
// Section: General Constants
|
||||
|
||||
|
|
|
@ -380,7 +380,7 @@ function path_normals(path, tangents, closed=false) =
|
|||
dim == 2 ? [tangents[i].y,-tangents[i].x]
|
||||
: let(v=cross(cross(pts[1]-pts[0], pts[2]-pts[0]),tangents[i]))
|
||||
assert(norm(v)>EPSILON, "3D path contains collinear points")
|
||||
v
|
||||
unit(v)
|
||||
];
|
||||
|
||||
|
||||
|
|
|
@ -1779,8 +1779,8 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
|
|||
// M = path3d(turtle(["left", 180, "length",3,"move", "left", "move", 3, "right", "move", "right", "move", 4, "right", "move", 3, "right", "move", 2]));
|
||||
// rounded_prism(M, apply(right(1)*scale(.75)*up(3),M), joint_top=0.5, joint_bot=0.2, joint_sides=[.2,1,1,0.5,1.5,.5,2], splinesteps=32);
|
||||
// Example: this example shows most of the different types of patches that rounded_prism creates. Note that some of the patches are close to interfering with each other across the top of the polyhedron, which would create an invalid result.
|
||||
N = apply(rot(180)*yscale(.8),turtle(["length",3,"left", "move", 2, "right", 135, "move", sqrt(2), "left", "move", sqrt(2), "right", 135, "move", 2]));
|
||||
rounded_prism(N, height=3, joint_bot=0.5, joint_top=1.25, joint_sides=[[1,1.75],0,.5,.5,2], debug=true);
|
||||
// N = apply(rot(180)*yscale(.8),turtle(["length",3,"left", "move", 2, "right", 135, "move", sqrt(2), "left", "move", sqrt(2), "right", 135, "move", 2]));
|
||||
// rounded_prism(N, height=3, joint_bot=0.5, joint_top=1.25, joint_sides=[[1,1.75],0,.5,.5,2], debug=true);
|
||||
// Example: This object has different scales on its different axies. Here is the largest symmetric rounding that fits. Note that the rounding is slightly smaller than the object dimensions because of roundoff error.
|
||||
// rounded_prism(square([100.1,30.1]), height=8.1, joint_top=4, joint_bot=4, joint_sides=15, k_sides=0.3, splinesteps=32);
|
||||
// Example: Using asymetric rounding enables a much more rounded form:
|
||||
|
@ -1886,8 +1886,8 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
|
|||
let(
|
||||
// Entries in the next two lists have the form [edges, vnf] where
|
||||
// edges is a list [leftedge, rightedge, topedge, botedge]
|
||||
top_samples = [for(patch=top_patch) bezier_patch_degenerate(patch,splinesteps,reverse=true,return_edges=true) ],
|
||||
bot_samples = [for(patch=bot_patch) bezier_patch_degenerate(patch,splinesteps,reverse=false,return_edges=true) ],
|
||||
top_samples = [for(patch=top_patch) bezier_patch_degenerate(patch,splinesteps,reverse=false,return_edges=true) ],
|
||||
bot_samples = [for(patch=bot_patch) bezier_patch_degenerate(patch,splinesteps,reverse=true,return_edges=true) ],
|
||||
leftidx=0,
|
||||
rightidx=1,
|
||||
topidx=2,
|
||||
|
|
124
skin.scad
124
skin.scad
|
@ -5,7 +5,6 @@
|
|||
// - https://github.com/openscad/list-comprehension-demos/blob/master/skin.scad
|
||||
// Includes:
|
||||
// include <BOSL2/std.scad>
|
||||
// include <BOSL2/skin.scad>
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
@ -13,9 +12,9 @@
|
|||
|
||||
// Function&Module: skin()
|
||||
// Usage: As module:
|
||||
// skin(profiles, slices, <z=>, <refine=>, <method=>, <sampling=>, <caps=>, <closed=>, <convexity=>, <anchor=>,<cp=>,<spin=>,<orient=>,<extent=>) <attachments>;
|
||||
// skin(profiles, slices, <z=>, <refine=>, <method=>, <sampling=>, <caps=>, <closed=>, <style=>, <convexity=>, <anchor=>,<cp=>,<spin=>,<orient=>,<extent=>) <attachments>;
|
||||
// Usage: As function:
|
||||
// vnf = skin(profiles, slices, <z=>, <refine=>, <method=>, <sampling=>, <caps=>, <closed=>);
|
||||
// vnf = skin(profiles, slices, <z=>, <refine=>, <method=>, <sampling=>, <caps=>, <closed=>, <style=>);
|
||||
// Description:
|
||||
// Given a list of two or more path `profiles` in 3d space, produces faces to skin a surface between
|
||||
// the profiles. Optionally the first and last profiles can have endcaps, or the first and last profiles
|
||||
|
@ -152,6 +151,7 @@
|
|||
// orient = Vector to rotate top towards after spin (module only)
|
||||
// extent = use extent method for computing anchors. (module only) Default: false
|
||||
// cp = set centerpoint for anchor computation. (module only) Default: object centroid
|
||||
// style = vnf_vertex_array style. Default: "min_edge"
|
||||
// Example:
|
||||
// skin([octagon(4), circle($fn=70,r=2)], z=[0,3], slices=10);
|
||||
// Example: Rotating the pentagon place the zero index at different locations, giving a twist
|
||||
|
@ -376,10 +376,10 @@
|
|||
// stroke(zrot(30, p=yscale(0.5, p=circle(d=120))),width=10,closed=true);
|
||||
// }
|
||||
// }
|
||||
module skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, convexity=10,
|
||||
module skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, style="min_edge", convexity=10,
|
||||
anchor="origin",cp,spin=0, orient=UP, extent=false)
|
||||
{
|
||||
vnf = skin(profiles, slices, refine, method, sampling, caps, closed, z);
|
||||
{
|
||||
vnf = skin(profiles, slices, refine, method, sampling, caps, closed, z, style=style);
|
||||
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
|
||||
{
|
||||
vnf_polyhedron(vnf,convexity=convexity);
|
||||
|
@ -388,7 +388,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) =
|
||||
function skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, style="min_edge") =
|
||||
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])
|
||||
|
@ -464,7 +464,7 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
|
|||
: reindex_polygon(resampled[i-1],resampled[i])],
|
||||
sliced = slice_profiles(fixedprof, slices, closed)
|
||||
)
|
||||
!closed ? sliced : concat(sliced,[sliced[0]])
|
||||
[!closed ? sliced : concat(sliced,[sliced[0]])]
|
||||
: // There are duplicators, so use approach where each pair is treated separately
|
||||
[for(i=[0:profcount-1])
|
||||
let(
|
||||
|
@ -481,63 +481,59 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
|
|||
". Method ",method[i]," requires equal values"))
|
||||
refine[i] * len(pair[0])
|
||||
)
|
||||
each subdivide_and_slice(pair,slices[i], nsamples, method=sampling)]
|
||||
subdivide_and_slice(pair,slices[i], nsamples, method=sampling)]
|
||||
)
|
||||
_skin_core(full_list, caps=fullcaps);
|
||||
|
||||
|
||||
vnf_merge(cleanup=false,
|
||||
[for(i=idx(full_list))
|
||||
vnf_vertex_array(full_list[i], cap1=i==0 && fullcaps[0], cap2=i==len(full_list)-1 && fullcaps[1],
|
||||
col_wrap=true, style=style)]);
|
||||
|
||||
function _skin_core(profiles, caps) =
|
||||
let(
|
||||
vertices = [for (prof=profiles) each prof],
|
||||
plens = [for (prof=profiles) len(prof)],
|
||||
sidefaces = [
|
||||
vertices = flatten(profiles),
|
||||
plen = len(profiles[0]),
|
||||
faces = [
|
||||
for(pidx=idx(profiles,e=-2))
|
||||
let(
|
||||
prof1 = profiles[pidx%len(profiles)],
|
||||
prof2 = profiles[(pidx+1)%len(profiles)],
|
||||
voff = default(sum([for (i=[0:1:pidx-1]) plens[i]]),0),
|
||||
prof1 = profiles[pidx],
|
||||
prof2 = profiles[pidx+1],
|
||||
voff = pidx*plen,
|
||||
faces = [
|
||||
for(
|
||||
first = true,
|
||||
finishing = false,
|
||||
finished = false,
|
||||
plen1 = len(prof1),
|
||||
plen2 = len(prof2),
|
||||
i=0, j=0, side=0;
|
||||
i=0, j=0, side=false;
|
||||
|
||||
!finished;
|
||||
|
||||
side =
|
||||
let(
|
||||
p1a = prof1[(i+0)%plen1],
|
||||
p1b = prof1[(i+1)%plen1],
|
||||
p2a = prof2[(j+0)%plen2],
|
||||
p2b = prof2[(j+1)%plen2],
|
||||
p1a = prof1[i%plen],
|
||||
p1b = prof1[(i+1)%plen],
|
||||
p2a = prof2[j%plen],
|
||||
p2b = prof2[(j+1)%plen],
|
||||
dist1 = norm(p1a-p2b),
|
||||
dist2 = norm(p1b-p2a)
|
||||
) (i==j) ? (dist1>dist2? 1 : 0) : (i<j ? 1 : 0) ,
|
||||
p1 = voff + (i%plen1),
|
||||
p2 = voff + (j%plen2) + plen1,
|
||||
p3 = voff + (side? ((i+1)%plen1) : (((j+1)%plen2) + plen1)),
|
||||
) (i==j) ? dist1>dist2 : i<j,
|
||||
p1 = voff + (i%plen),
|
||||
p2 = voff + (j%plen) + plen,
|
||||
p3 = voff + (side? (i+1)%plen : (j+1)%plen + plen),
|
||||
face = [p1, p3, p2],
|
||||
i = i + (side? 1 : 0),
|
||||
j = j + (side? 0 : 1),
|
||||
first = false,
|
||||
finished = finishing,
|
||||
finishing = i>=plen1 && j>=plen2
|
||||
finishing = i>=plen && j>=plen
|
||||
) if (!first) face
|
||||
]
|
||||
) each faces
|
||||
],
|
||||
firstcap = !caps[0] ? [] : let(
|
||||
prof1 = profiles[0]
|
||||
) [[for (i=idx(prof1)) plens[0]-1-i]],
|
||||
secondcap = !caps[1] ? [] : let(
|
||||
prof2 = last(profiles),
|
||||
eoff = sum(list_head(plens))
|
||||
) [[for (i=idx(prof2)) eoff+i]]
|
||||
) [vertices, concat(sidefaces,firstcap,secondcap)];
|
||||
) each faces,
|
||||
if (caps[0]) count(plen,reverse=true),
|
||||
if (caps[1]) count(plen,plen*(len(profiles)-1))
|
||||
]
|
||||
) [vertices, faces];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -903,9 +899,9 @@ function associate_vertices(polygons, split, curpoly=0) =
|
|||
|
||||
// Function&Module: sweep()
|
||||
// Usage: As Module
|
||||
// sweep(shape, transforms, <closed>, <caps>, <convexity=>, <anchor=>, <spin=>, <orient=>, <extent=>) <attachments>;
|
||||
// sweep(shape, transforms, <closed>, <caps>, <style>, <convexity=>, <anchor=>, <spin=>, <orient=>, <extent=>) <attachments>;
|
||||
// Usage: As Function
|
||||
// vnf = sweep(shape, transforms, <closed>, <caps>);
|
||||
// vnf = sweep(shape, transforms, <closed>, <caps>, <style>);
|
||||
// Description:
|
||||
// The input `shape` must be a non-self-intersecting 2D polygon or region, and `transforms`
|
||||
// is a list of 4x4 transformation matrices. The sweep algorithm applies each transformation in sequence
|
||||
|
@ -925,6 +921,7 @@ function associate_vertices(polygons, split, curpoly=0) =
|
|||
// transforms = list of 4x4 matrices to apply
|
||||
// closed = set to true to form a closed (torus) model. Default: false
|
||||
// caps = true to create endcap faces when closed is false. Can be a singe boolean to specify endcaps at both ends, or a length 2 boolean array. Default is true if closed is false.
|
||||
// style = vnf_vertex_array style. Default: "min_edge"
|
||||
// ---
|
||||
// convexity = convexity setting for use with polyhedron. (module only) Default: 10
|
||||
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
|
||||
|
@ -953,7 +950,7 @@ function associate_vertices(polygons, split, curpoly=0) =
|
|||
// inside = [for(i=[24:-1:2]) up(i)*rot(i)*scale(1.2*i/24+1)];
|
||||
// sweep(shape, concat(outside,inside));
|
||||
|
||||
function sweep(shape, transforms, closed=false, caps) =
|
||||
function sweep(shape, transforms, closed=false, caps, style="min_edge") =
|
||||
assert(is_consistent(transforms, ident(4)), "Input transforms must be a list of numeric 4x4 matrices in sweep")
|
||||
assert(is_path(shape,2) || is_region(shape), "Input shape must be a 2d path or a region.")
|
||||
let(
|
||||
|
@ -979,13 +976,14 @@ function sweep(shape, transforms, closed=false, caps) =
|
|||
vnf = vnf_merge(vnfs)
|
||||
) vnf :
|
||||
assert(len(shape)>=3, "shape must be a path of at least 3 non-colinear points")
|
||||
_skin_core([for(i=[0:len(transforms)-(closed?0:1)]) apply(transforms[i%len(transforms)],path3d(shape))],caps=fullcaps);
|
||||
vnf_vertex_array([for(i=[0:len(transforms)-(closed?0:1)]) apply(transforms[i%len(transforms)],path3d(shape))],
|
||||
cap1=fullcaps[0],cap2=fullcaps[1],col_wrap=true,style=style);
|
||||
|
||||
|
||||
module sweep(shape, transforms, closed=false, caps, convexity=10,
|
||||
module sweep(shape, transforms, closed=false, caps, style="min_edge", convexity=10,
|
||||
anchor="origin",cp,spin=0, orient=UP, extent=false)
|
||||
{
|
||||
vnf = sweep(shape, transforms, closed, caps);
|
||||
vnf = sweep(shape, transforms, closed, caps, style);
|
||||
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
|
||||
{
|
||||
vnf_polyhedron(vnf,convexity=convexity);
|
||||
|
@ -996,8 +994,8 @@ module sweep(shape, transforms, closed=false, caps, convexity=10,
|
|||
|
||||
// Function&Module: path_sweep()
|
||||
// Usage: As module
|
||||
// path_sweep(shape, path, <method>, <normal=>, <closed=>, <twist=>, <twist_by_length=>, <symmetry=>, <last_normal=>, <tangent=>, <relaxed=>, <caps=>, <convexity=>, <transforms=>, <anchor=>, <cp=>, <spin=>, <orient=>, <extent=>) <attachments>;
|
||||
// vnf = path_sweep(shape, path, <method>, <normal=>, <closed=>, <twist=>, <twist_by_length=>, <symmetry=>, <last_normal=>, <tangent=>, <relaxed=>, <caps=>, <convexity=>, <transforms=>);
|
||||
// path_sweep(shape, path, <method>, <normal=>, <closed=>, <twist=>, <twist_by_length=>, <symmetry=>, <last_normal=>, <tangent=>, <relaxed=>, <caps=>, <style=>, <convexity=>, <transforms=>, <anchor=>, <cp=>, <spin=>, <orient=>, <extent=>) <attachments>;
|
||||
// vnf = path_sweep(shape, path, <method>, <normal=>, <closed=>, <twist=>, <twist_by_length=>, <symmetry=>, <last_normal=>, <tangent=>, <relaxed=>, <caps=>, <style=>, <convexity=>, <transforms=>);
|
||||
// Description:
|
||||
// Takes as input a 2D polygon path, 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`
|
||||
|
@ -1058,6 +1056,7 @@ module sweep(shape, transforms, closed=false, caps, convexity=10,
|
|||
// tangent = a list of tangent vectors in case you need more accuracy (particularly at the end points of your curve)
|
||||
// relaxed = set to true with the "manual" method to relax the orthogonality requirement of cross sections to the path tangent. Default: false
|
||||
// caps = Can be a boolean or vector of two booleans. Set to false to disable caps at the two ends. Default: true
|
||||
// style = vnf_vertex_array style. Default: "min_edge"
|
||||
// transforms = set to true to return transforms instead of a VNF. These transforms can be manipulated and passed to sweep(). Default: false.
|
||||
// convexity = convexity parameter for polyhedron(). Only accepted by the module version. Default: 10
|
||||
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
|
||||
|
@ -1291,11 +1290,11 @@ module sweep(shape, transforms, closed=false, caps, convexity=10,
|
|||
// circle(r=16,$fn=75),closed=true,
|
||||
// twist=360/5*2,symmetry=5);
|
||||
module path_sweep(shape, path, method="incremental", normal, closed=false, twist=0, twist_by_length=true,
|
||||
symmetry=1, last_normal, tangent, relaxed=false, caps, convexity=10,
|
||||
symmetry=1, last_normal, tangent, relaxed=false, caps, style="min_edge", convexity=10,
|
||||
anchor="origin",cp,spin=0, orient=UP, extent=false)
|
||||
{
|
||||
vnf = path_sweep(shape, path, method, normal, closed, twist, twist_by_length,
|
||||
symmetry, last_normal, tangent, relaxed, caps);
|
||||
symmetry, last_normal, tangent, relaxed, caps, style);
|
||||
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
|
||||
{
|
||||
vnf_polyhedron(vnf,convexity=convexity);
|
||||
|
@ -1305,7 +1304,7 @@ module path_sweep(shape, path, method="incremental", normal, closed=false, twist
|
|||
|
||||
|
||||
function path_sweep(shape, path, method="incremental", normal, closed=false, twist=0, twist_by_length=true,
|
||||
symmetry=1, last_normal, tangent, relaxed=false, caps, transforms=false) =
|
||||
symmetry=1, last_normal, tangent, relaxed=false, caps, style="min_edge", transforms=false) =
|
||||
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")
|
||||
|
@ -1377,6 +1376,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
let (pathnormal = path_normals(path, tangents, closed))
|
||||
assert(all_defined(pathnormal),"Natural normal vanishes on your curve, select a different method")
|
||||
let( testnormals = [for(i=[0:len(pathnormal)-1-(closed?1:2)]) pathnormal[i]*select(pathnormal,i+2)],
|
||||
a=[for(i=idx(testnormals)) testnormals[i]<.5 ? echo(str("Big change at index ",i," pn=",pathnormal[i]," pn2= ",select(pathnormal,i+2))):0],
|
||||
dummy = min(testnormals) < .5 ? echo("WARNING: ***** Abrupt change in normal direction. Consider a different method *****") :0
|
||||
)
|
||||
[for(i=[0:L-(closed?0:1)]) let(
|
||||
|
@ -1393,14 +1393,14 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
apply(transform_list[L], rshape)),
|
||||
dummy = ends_match ? 0 : echo("WARNING: ***** The points do not match when closing the model *****")
|
||||
)
|
||||
transforms ? transform_list : sweep(is_path(shape)?clockwise_polygon(shape):shape, transform_list, closed=false, caps=fullcaps);
|
||||
transforms ? transform_list : sweep(is_path(shape)?clockwise_polygon(shape):shape, transform_list, closed=false, caps=fullcaps,style=style);
|
||||
|
||||
|
||||
// Function&Module: path_sweep2d()
|
||||
// Usage: as module
|
||||
// path_sweep2d(shape, path, <closed>, <caps>, <quality>, <convexity=>, <anchor=>, <spin=>, <orient=>, <extent=>, <cp=>) <attachments>;
|
||||
// path_sweep2d(shape, path, <closed>, <caps>, <quality>, <style>, <convexity=>, <anchor=>, <spin=>, <orient=>, <extent=>, <cp=>) <attachments>;
|
||||
// Usage: as function
|
||||
// vnf = path_sweep2d(shape, path, <closed>, <caps>, <quality>);
|
||||
// vnf = path_sweep2d(shape, path, <closed>, <caps>, <quality>, <style>);
|
||||
// Description:
|
||||
// Takes an input 2D polygon (the shape) and a 2d 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.
|
||||
|
@ -1417,6 +1417,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
// closed = path is a closed loop. Default: false
|
||||
// caps = true to create endcap faces when closed is false. Can be a length 2 boolean array. Default is true if closed is false.
|
||||
// quality = quality of offset used in calculation. Default: 1
|
||||
// style = vnf_vertex_array style. Default: "min_edge"
|
||||
// ---
|
||||
// convexity = convexity parameter for polyhedron (module only) Default: 10
|
||||
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
|
||||
|
@ -1440,7 +1441,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
// path_sweep2d(circle(r=3.25, $fn=32), select(ellipse,floor(L*.2),ceil(L*.8)),closed=false);
|
||||
// path_sweep2d(circle(r=3.25, $fn=32), select(ellipse,floor(L*.7),ceil(L*.3)),closed=false);
|
||||
|
||||
function path_sweep2d(shape, path, closed=false, caps, quality=1) =
|
||||
function path_sweep2d(shape, path, closed=false, caps, quality=1, style="min_edge") =
|
||||
let(
|
||||
caps = is_def(caps) ? caps
|
||||
: closed ? false : true,
|
||||
|
@ -1464,15 +1465,16 @@ function path_sweep2d(shape, path, closed=false, caps, quality=1) =
|
|||
]
|
||||
)
|
||||
)
|
||||
_skin_core([
|
||||
each proflist,
|
||||
if (closed) proflist[0]
|
||||
],caps=fullcaps);
|
||||
vnf_vertex_array([
|
||||
each proflist,
|
||||
if (closed) proflist[0]
|
||||
],cap1=fullcaps[0],cap2=fullcaps[1],col_wrap=true,style=style);
|
||||
|
||||
module path_sweep2d(profile, path, closed=false, caps, quality=1, convexity=10,
|
||||
|
||||
module path_sweep2d(profile, path, closed=false, caps, quality=1, style="min_edge", convexity=10,
|
||||
anchor="origin", cp, spin=0, orient=UP, extent=false)
|
||||
{
|
||||
vnf = path_sweep2d(profile, path, closed, caps, quality);
|
||||
vnf = path_sweep2d(profile, path, closed, caps, quality, style);
|
||||
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
|
||||
{
|
||||
vnf_polyhedron(vnf,convexity=convexity);
|
||||
|
|
|
@ -8,10 +8,10 @@ module test_skin() {
|
|||
[[-100,-100,100], [-100,100,100], [100,100,100], [100,-100,100]],
|
||||
];
|
||||
vnf1 = skin(profiles, slices=0, caps=false, method="distance");
|
||||
assert_equal(vnf1, [[[-100,-100,0],[0,100,0],[0,100,0],[100,-100,0],[-100,-100,100],[-100,100,100],[100,100,100],[100,-100,100]],[[0,5,4],[0,1,5],[1,6,5],[1,2,6],[2,3,6],[3,7,6],[3,4,7],[3,0,4]]]);
|
||||
assert_equal(vnf1, [[[-100,-100,0],[0,100,0],[0,100,0],[100,-100,0],[-100,-100,100],[-100,100,100],[100,100,100],[100,-100,100]],[[0,5,4],[0,1,5],[5,2,6],[2,3,6],[6,3,7],[3,0,7],[7,0,4]]]);
|
||||
|
||||
vnf2 = skin(profiles, slices=0, caps=true, method="distance");
|
||||
assert_equal(vnf2, [[[-100,-100,0],[0,100,0],[0,100,0],[100,-100,0],[-100,-100,100],[-100,100,100],[100,100,100],[100,-100,100]],[[0,5,4],[0,1,5],[1,6,5],[1,2,6],[2,3,6],[3,7,6],[3,4,7],[3,0,4],[3,2,1,0],[4,5,6,7]]]);
|
||||
vnf_polyhedron(vnf2);
|
||||
assert_equal(vnf2,[[[-100,-100,0],[0,100,0],[0,100,0],[100,-100,0],[-100,-100,100],[-100,100,100],[100,100,100],[100,-100,100]],[[0,5,4],[0,1,5],[5,2,6],[2,3,6],[6,3,7],[3,0,7],[7,0,4],[3,2,1,0],[4,5,6,7]]]);
|
||||
}
|
||||
test_skin();
|
||||
|
||||
|
|
165
vnf.scad
165
vnf.scad
|
@ -155,17 +155,19 @@ function vnf_merge(vnfs, cleanup=false) =
|
|||
) [
|
||||
[for (vnf=vnfs) each vnf[0]],
|
||||
[
|
||||
for (i = idx(vnfs)) let(
|
||||
vnf = vnfs[i],
|
||||
verts = vnf[0],
|
||||
faces = vnf[1]
|
||||
)
|
||||
for (face = faces) let(
|
||||
dface = !cleanup ? face :
|
||||
deduplicate_indexed(verts, face, closed=true)
|
||||
)
|
||||
if (len(dface) >= 3)
|
||||
[ for (j = dface) offs[i] + j ]
|
||||
for (i = idx(vnfs))
|
||||
let(
|
||||
vnf = vnfs[i],
|
||||
verts = vnf[0],
|
||||
faces = vnf[1]
|
||||
)
|
||||
for (face = faces)
|
||||
let(
|
||||
dface = !cleanup ? face
|
||||
: deduplicate_indexed(verts, face, closed=true)
|
||||
)
|
||||
if (len(dface) >= 3)
|
||||
[ for (j = dface) offs[i] + j ]
|
||||
]
|
||||
];
|
||||
|
||||
|
@ -215,7 +217,12 @@ function vnf_triangulate(vnf) =
|
|||
// Creates a VNF structure from a vertex list, by dividing the vertices into columns and rows,
|
||||
// adding faces to tile the surface. You can optionally have faces added to wrap the last column
|
||||
// back to the first column, or wrap the last row to the first. Endcaps can be added to either
|
||||
// the first and/or last rows.
|
||||
// the first and/or last rows. The style parameter determines how the quadrilaterals are divided into
|
||||
// triangles. The default style is an arbitrary, systematic subdivision in the same direction. The "alt" style
|
||||
// is the uniform subdivision in the other (alternate) direction. The "min_edge" style picks the shorter edge to
|
||||
// subdivide for each quadrilateral, so the division may not be uniform across the shape. The "quincunx" style
|
||||
// adds a vertex in the center of each quadrilateral and creates four triangles, and the "convex" style
|
||||
// chooses the locally convex subdivision.
|
||||
// Arguments:
|
||||
// points = A list of vertices to divide into columns and rows.
|
||||
// caps = If true, add endcap faces to the first AND last rows.
|
||||
|
@ -224,7 +231,7 @@ function vnf_triangulate(vnf) =
|
|||
// col_wrap = If true, add faces to connect the last column to the first.
|
||||
// row_wrap = If true, add faces to connect the last row to the first.
|
||||
// reverse = If true, reverse all face normals.
|
||||
// style = The style of subdividing the quads into faces. Valid options are "default", "alt", "quincunx", and "convex".
|
||||
// style = The style of subdividing the quads into faces. Valid options are "default", "alt", "min_edge", "quincunx", and "convex".
|
||||
// vnf = If given, add all the vertices and faces to this existing VNF structure.
|
||||
// Example(3D):
|
||||
// vnf = vnf_vertex_array(
|
||||
|
@ -287,82 +294,84 @@ function vnf_vertex_array(
|
|||
reverse=false,
|
||||
style="default",
|
||||
vnf=EMPTY_VNF
|
||||
) =
|
||||
assert((!caps)||(caps&&col_wrap))
|
||||
assert(in_list(style,["default","alt","quincunx", "convex"]))
|
||||
assert(is_consistent(points), "Non-rectangular or invalid point array")
|
||||
) =
|
||||
assert(!(any([caps,cap1,cap2]) && !col_wrap), "col_wrap must be true if caps are requested")
|
||||
assert(!(any([caps,cap1,cap2]) && row_wrap), "Cannot combine caps with row_wrap")
|
||||
assert(in_list(style,["default","alt","quincunx", "convex","min_edge"]))
|
||||
assert(is_consistent(points), "Non-rectangular or invalid point array")
|
||||
let(
|
||||
pts = flatten(points),
|
||||
pcnt = len(pts),
|
||||
rows = len(points),
|
||||
cols = len(points[0]),
|
||||
cols = len(points[0])
|
||||
)
|
||||
rows<=1 || cols<=1 ? vnf :
|
||||
let(
|
||||
cap1 = first_defined([cap1,caps,false]),
|
||||
cap2 = first_defined([cap2,caps,false]),
|
||||
colcnt = cols - (col_wrap?0:1),
|
||||
rowcnt = rows - (row_wrap?0:1),
|
||||
verts = [
|
||||
each pts,
|
||||
if (style=="quincunx") (
|
||||
for (r = [0:1:rowcnt-1]) (
|
||||
for (c = [0:1:colcnt-1]) (
|
||||
let(
|
||||
i1 = ((r+0)%rows)*cols + ((c+0)%cols),
|
||||
i2 = ((r+1)%rows)*cols + ((c+0)%cols),
|
||||
i3 = ((r+1)%rows)*cols + ((c+1)%cols),
|
||||
i4 = ((r+0)%rows)*cols + ((c+1)%cols)
|
||||
) mean([pts[i1], pts[i2], pts[i3], pts[i4]])
|
||||
)
|
||||
)
|
||||
)
|
||||
if (style=="quincunx")
|
||||
for (r = [0:1:rowcnt-1], c = [0:1:colcnt-1])
|
||||
let(
|
||||
i1 = ((r+0)%rows)*cols + ((c+0)%cols),
|
||||
i2 = ((r+1)%rows)*cols + ((c+0)%cols),
|
||||
i3 = ((r+1)%rows)*cols + ((c+1)%cols),
|
||||
i4 = ((r+0)%rows)*cols + ((c+1)%cols)
|
||||
)
|
||||
mean([pts[i1], pts[i2], pts[i3], pts[i4]])
|
||||
]
|
||||
)
|
||||
rows<=1 || cols<=1 ? vnf :
|
||||
vnf_merge(cleanup=true, [
|
||||
vnf, [
|
||||
verts,
|
||||
concat(
|
||||
[
|
||||
for (r = [0:1:rowcnt-1]) (
|
||||
for (c = [0:1:colcnt-1]) each (
|
||||
let(
|
||||
i1 = ((r+0)%rows)*cols + ((c+0)%cols),
|
||||
i2 = ((r+1)%rows)*cols + ((c+0)%cols),
|
||||
i3 = ((r+1)%rows)*cols + ((c+1)%cols),
|
||||
i4 = ((r+0)%rows)*cols + ((c+1)%cols),
|
||||
faces = style=="quincunx"? (
|
||||
let(i5 = pcnt + r*colcnt + c)
|
||||
[[i1,i5,i2],[i2,i5,i3],[i3,i5,i4],[i4,i5,i1]]
|
||||
) : style=="alt"? (
|
||||
[[i1,i4,i2],[i2,i4,i3]]
|
||||
) : style=="convex"? let(
|
||||
fsets = [
|
||||
[[i1,i4,i2],[i2,i4,i3]],
|
||||
[[i1,i3,i2],[i1,i4,i3]]
|
||||
],
|
||||
cps = [for (fset=fsets) [for (f=fset) mean(select(pts,f))]],
|
||||
ns = cps + [for (fset=fsets) [for (f=fset) polygon_normal(select(pts,f))]],
|
||||
dists = [for (i=idx(fsets)) norm(cps[i][1]-cps[i][0]) - norm(ns[i][1]-ns[i][0])],
|
||||
test = reverse? dists[0]>dists[1] : dists[0]<dists[1]
|
||||
) fsets[test?0:1] : (
|
||||
[[i1,i3,i2],[i1,i4,i3]]
|
||||
),
|
||||
rfaces = reverse? [for (face=faces) reverse(face)] : faces,
|
||||
ffaces = [for (face=rfaces) if(len(deduplicate_indexed(verts,face,closed=true))>=3) face]
|
||||
) faces
|
||||
)
|
||||
)
|
||||
],
|
||||
!cap1? [] : [
|
||||
reverse?
|
||||
[for (c = [0:1:cols-1]) c] :
|
||||
[for (c = [cols-1:-1:0]) c]
|
||||
],
|
||||
!cap2? [] : [
|
||||
reverse?
|
||||
[for (c = [cols-1:-1:0]) (rows-1)*cols + c] :
|
||||
[for (c = [0:1:cols-1]) (rows-1)*cols + c]
|
||||
]
|
||||
)
|
||||
vnf_merge(cleanup=false, [
|
||||
vnf,
|
||||
[
|
||||
verts,
|
||||
[
|
||||
for (r = [0:1:rowcnt-1], c=[0:1:colcnt-1])
|
||||
each
|
||||
let(
|
||||
i1 = ((r+0)%rows)*cols + ((c+0)%cols),
|
||||
i2 = ((r+1)%rows)*cols + ((c+0)%cols),
|
||||
i3 = ((r+1)%rows)*cols + ((c+1)%cols),
|
||||
i4 = ((r+0)%rows)*cols + ((c+1)%cols),
|
||||
faces =
|
||||
style=="quincunx"?
|
||||
let(i5 = pcnt + r*colcnt + c)
|
||||
[[i1,i5,i2],[i2,i5,i3],[i3,i5,i4],[i4,i5,i1]]
|
||||
: style=="alt"?
|
||||
[[i1,i4,i2],[i2,i4,i3]]
|
||||
: style=="min_edge"?
|
||||
let(
|
||||
d42=norm(pts[i4]-pts[i2]),
|
||||
d13=norm(pts[i1]-pts[i3]),
|
||||
shortedge = d42<=d13 ? [[i1,i4,i2],[i2,i4,i3]]
|
||||
: [[i1,i3,i2],[i1,i4,i3]]
|
||||
)
|
||||
shortedge
|
||||
: style=="convex"?
|
||||
let( // Find normal for 3 of the points. Is the other point above or below?
|
||||
n = (reverse?-1:1)*cross(pts[i2]-pts[i1],pts[i3]-pts[i1]),
|
||||
convexfaces = n==0 ? [[i1,i4,i3]]
|
||||
: n*pts[i4] > n*pts[i1] ? [[i1,i4,i2],[i2,i4,i3]]
|
||||
: [[i1,i3,i2],[i1,i4,i3]]
|
||||
)
|
||||
convexfaces
|
||||
: [[i1,i3,i2],[i1,i4,i3]],
|
||||
// remove degenerate faces
|
||||
culled_faces= [for(face=faces)
|
||||
if (norm(verts[face[0]]-verts[face[1]])>EPSILON &&
|
||||
norm(verts[face[1]]-verts[face[2]])>EPSILON &&
|
||||
norm(verts[face[2]]-verts[face[0]])>EPSILON)
|
||||
face
|
||||
],
|
||||
rfaces = reverse? [for (face=culled_faces) reverse(face)] : culled_faces
|
||||
)
|
||||
rfaces,
|
||||
if (cap1) count(cols,reverse=!reverse),
|
||||
if (cap2) count(cols,(rows-1)*cols, reverse=reverse)
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
|
@ -435,7 +444,7 @@ function vnf_tri_array(points, row_wrap=false, reverse=false, vnf=EMPTY_VNF) =
|
|||
for(j=[0:1:count]) reverse ? [j+rowstart, j+nextrow, j+nextrow+1] : [j+rowstart, j+nextrow+1, j+nextrow], // bot triangles left
|
||||
for(j=[count+1:1:select(lens,i+1)-2]) reverse ? [j+rowstart-1, j+nextrow, j+nextrow+1] : [j+rowstart-1, j+nextrow+1, j+nextrow], // bot triangles right
|
||||
] :
|
||||
delta == -2 ?
|
||||
delta == -2 ?
|
||||
[
|
||||
for(j=[0:1:count-2]) reverse ? [j+nextrow, j+nextrow+1, j+rowstart+1] : [j+nextrow, j+rowstart+1, j+nextrow+1],
|
||||
for(j=[count-1:1:lens[i]-4]) reverse ? [j+nextrow,j+nextrow+1,j+rowstart+2] : [j+nextrow,j+rowstart+2, j+nextrow+1],
|
||||
|
|
Loading…
Reference in a new issue