mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
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:
parent
b96fbca930
commit
ef9f54c369
9 changed files with 188 additions and 166 deletions
|
@ -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
|
||||
|
|
|
@ -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) =
|
||||
|
|
20
gears.scad
20
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):
|
||||
|
|
|
@ -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();
|
||||
|
|
68
paths.scad
68
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:
|
||||
|
|
|
@ -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),
|
||||
|
|
114
shapes3d.scad
114
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 <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()
|
||||
|
|
|
@ -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
119
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);
|
||||
|
|
Loading…
Reference in a new issue