mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2025-01-06 12:19:47 +00:00
commit
59af31a531
2 changed files with 74 additions and 50 deletions
|
@ -337,7 +337,6 @@ module stroke(
|
||||||
);
|
);
|
||||||
v1 = unit(path2[i] - path2[i-1]);
|
v1 = unit(path2[i] - path2[i-1]);
|
||||||
v2 = unit(path2[i+1] - path2[i]);
|
v2 = unit(path2[i+1] - path2[i]);
|
||||||
vec = unit((v1+v2)/2);
|
|
||||||
mat = is_undef(joint_angle)
|
mat = is_undef(joint_angle)
|
||||||
? rot(from=BACK,to=v1)
|
? rot(from=BACK,to=v1)
|
||||||
: zrot(joint_angle);
|
: zrot(joint_angle);
|
||||||
|
|
123
regions.scad
123
regions.scad
|
@ -93,7 +93,8 @@ function check_and_fix_path(path, valid_dim=undef, closed=false, name="path") =
|
||||||
// Examples:
|
// Examples:
|
||||||
//
|
//
|
||||||
function sanitize_region(r,nonzero=false,eps=EPSILON) =
|
function sanitize_region(r,nonzero=false,eps=EPSILON) =
|
||||||
assert(is_region(r))
|
let(r=force_region(r))
|
||||||
|
assert(is_region(r), "Input is not a region")
|
||||||
exclusive_or(
|
exclusive_or(
|
||||||
[for(poly=r) each polygon_parts(poly,nonzero,eps)],
|
[for(poly=r) each polygon_parts(poly,nonzero,eps)],
|
||||||
eps=eps);
|
eps=eps);
|
||||||
|
@ -117,7 +118,8 @@ function sanitize_region(r,nonzero=false,eps=EPSILON) =
|
||||||
module region(r)
|
module region(r)
|
||||||
{
|
{
|
||||||
no_children($children);
|
no_children($children);
|
||||||
r = is_path(r) ? [r] : r;
|
r = force_region(r);
|
||||||
|
dummy=assert(is_region(r), "Input is not a region");
|
||||||
points = flatten(r);
|
points = flatten(r);
|
||||||
lengths = [for(path=r) len(path)];
|
lengths = [for(path=r) len(path)];
|
||||||
starts = [0,each cumsum(lengths)];
|
starts = [0,each cumsum(lengths)];
|
||||||
|
@ -156,12 +158,14 @@ function point_in_region(point, region, eps=EPSILON, _i=0, _cnt=0) =
|
||||||
// formed from a list of simple polygons that do not intersect each other.
|
// formed from a list of simple polygons that do not intersect each other.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// region = region to check
|
// region = region to check
|
||||||
// eps = tolerance for geometric omparisons. Default: `EPSILON` = 1e-9
|
// eps = tolerance for geometric comparisons. Default: `EPSILON` = 1e-9
|
||||||
function is_region_simple(region, eps=EPSILON) =
|
function is_region_simple(region, eps=EPSILON) =
|
||||||
|
let(region=force_region(region))
|
||||||
|
assert(is_region(region), "Input is not a region")
|
||||||
[for(p=region) if (!is_path_simple(p,closed=true,eps)) 1] == []
|
[for(p=region) if (!is_path_simple(p,closed=true,eps)) 1] == []
|
||||||
&&
|
&&
|
||||||
[for(i=[0:1:len(region)-2])
|
[for(i=[0:1:len(region)-2])
|
||||||
if (_path_region_intersections(region[i], list_tail(region,i+1), eps=eps) != []) 1
|
if (_region_region_intersections([region[i]], list_tail(region,i+1), eps=eps)[0][0] != []) 1
|
||||||
] ==[];
|
] ==[];
|
||||||
|
|
||||||
function _clockwise_region(r) = [for(p=r) clockwise_polygon(p)];
|
function _clockwise_region(r) = [for(p=r) clockwise_polygon(p)];
|
||||||
|
@ -181,7 +185,7 @@ function are_regions_equal(region1, region2, either_winding=false) =
|
||||||
region1=force_region(region1),
|
region1=force_region(region1),
|
||||||
region2=force_region(region2)
|
region2=force_region(region2)
|
||||||
)
|
)
|
||||||
assert(is_region(region1) && is_region(region2))
|
assert(is_region(region1) && is_region(region2), "One of the inputs is not a region")
|
||||||
len(region1) != len(region2)? false :
|
len(region1) != len(region2)? false :
|
||||||
__are_regions_equal(either_winding?_clockwise_region(region1):region1,
|
__are_regions_equal(either_winding?_clockwise_region(region1):region1,
|
||||||
either_winding?_clockwise_region(region2):region2,
|
either_winding?_clockwise_region(region2):region2,
|
||||||
|
@ -286,7 +290,7 @@ function _region_region_intersections(region1, region2, closed1=true,closed2=tru
|
||||||
// closed1 = if false then treat region1 as list of open paths. Default: true
|
// closed1 = if false then treat region1 as list of open paths. Default: true
|
||||||
// closed2 = if false then treat region2 as list of open paths. Default: true
|
// closed2 = if false then treat region2 as list of open paths. Default: true
|
||||||
// eps = Acceptable variance. Default: `EPSILON` (1e-9)
|
// eps = Acceptable variance. Default: `EPSILON` (1e-9)
|
||||||
// Example:
|
// Example(2D):
|
||||||
// path = square(50,center=false);
|
// path = square(50,center=false);
|
||||||
// region = [circle(d=80), circle(d=40)];
|
// region = [circle(d=80), circle(d=40)];
|
||||||
// paths = split_region_at_region_crossings(path, region);
|
// paths = split_region_at_region_crossings(path, region);
|
||||||
|
@ -299,7 +303,10 @@ function _region_region_intersections(region1, region2, closed1=true,closed2=tru
|
||||||
function split_region_at_region_crossings(region1, region2, closed1=true, closed2=true, eps=EPSILON) =
|
function split_region_at_region_crossings(region1, region2, closed1=true, closed2=true, eps=EPSILON) =
|
||||||
let(
|
let(
|
||||||
region1=force_region(region1),
|
region1=force_region(region1),
|
||||||
region2=force_region(region2),
|
region2=force_region(region2)
|
||||||
|
)
|
||||||
|
assert(is_region(region1) && is_region(region2),"One of the inputs is not a region")
|
||||||
|
let(
|
||||||
xings = _region_region_intersections(region1, region2, closed1, closed2, eps),
|
xings = _region_region_intersections(region1, region2, closed1, closed2, eps),
|
||||||
regions = [region1,region2],
|
regions = [region1,region2],
|
||||||
closed = [closed1,closed2]
|
closed = [closed1,closed2]
|
||||||
|
@ -347,7 +354,10 @@ function split_region_at_region_crossings(region1, region2, closed1=true, closed
|
||||||
// rainbow(region_list) region($item);
|
// rainbow(region_list) region($item);
|
||||||
function region_parts(region) =
|
function region_parts(region) =
|
||||||
let(
|
let(
|
||||||
region = force_region(region),
|
region = force_region(region)
|
||||||
|
)
|
||||||
|
assert(is_region(region), "Input is not a region")
|
||||||
|
let(
|
||||||
inside = [for(i=idx(region))
|
inside = [for(i=idx(region))
|
||||||
let(pt = mean([region[i][0], region[i][1]]))
|
let(pt = mean([region[i][0], region[i][1]]))
|
||||||
[for(j=idx(region)) i==j ? 0
|
[for(j=idx(region)) i==j ? 0
|
||||||
|
@ -436,7 +446,7 @@ function _cleave_connected_region(region) =
|
||||||
// vnf = If given, the faces are added to this VNF. Default: `EMPTY_VNF`
|
// vnf = If given, the faces are added to this VNF. Default: `EMPTY_VNF`
|
||||||
function region_faces(region, transform, reverse=false, vnf=EMPTY_VNF) =
|
function region_faces(region, transform, reverse=false, vnf=EMPTY_VNF) =
|
||||||
let (
|
let (
|
||||||
regions = region_parts(region),
|
regions = region_parts(force_region(region)),
|
||||||
vnfs = [
|
vnfs = [
|
||||||
if (vnf != EMPTY_VNF) vnf,
|
if (vnf != EMPTY_VNF) vnf,
|
||||||
for (rgn = regions) let(
|
for (rgn = regions) let(
|
||||||
|
@ -494,7 +504,8 @@ function region_faces(region, transform, reverse=false, vnf=EMPTY_VNF) =
|
||||||
// orgn = difference(mrgn,rgn3);
|
// orgn = difference(mrgn,rgn3);
|
||||||
// linear_sweep(orgn,height=20,convexity=16) show_anchors();
|
// linear_sweep(orgn,height=20,convexity=16) show_anchors();
|
||||||
module linear_sweep(region, height=1, center, twist=0, scale=1, slices, maxseg, style="default", convexity, anchor_isect=false, anchor, spin=0, orient=UP) {
|
module linear_sweep(region, height=1, center, twist=0, scale=1, slices, maxseg, style="default", convexity, anchor_isect=false, anchor, spin=0, orient=UP) {
|
||||||
region = is_path(region)? [region] : region;
|
region = force_region(region);
|
||||||
|
dummy=assert(is_region(region),"Input is not a region");
|
||||||
cp = mean(pointlist_bounds(flatten(region)));
|
cp = mean(pointlist_bounds(flatten(region)));
|
||||||
anchor = get_anchor(anchor, center, "origin", "origin");
|
anchor = get_anchor(anchor, center, "origin", "origin");
|
||||||
vnf = linear_sweep(
|
vnf = linear_sweep(
|
||||||
|
@ -512,11 +523,14 @@ module linear_sweep(region, height=1, center, twist=0, scale=1, slices, maxseg,
|
||||||
|
|
||||||
function linear_sweep(region, height=1, center, twist=0, scale=1, slices,
|
function linear_sweep(region, height=1, center, twist=0, scale=1, slices,
|
||||||
maxseg, style="default", anchor_isect=false, anchor, spin=0, orient=UP) =
|
maxseg, style="default", anchor_isect=false, anchor, spin=0, orient=UP) =
|
||||||
|
let(
|
||||||
|
region = force_region(region)
|
||||||
|
)
|
||||||
|
assert(is_region(region), "Input is not a region")
|
||||||
let(
|
let(
|
||||||
anchor = get_anchor(anchor,center,BOT,BOT),
|
anchor = get_anchor(anchor,center,BOT,BOT),
|
||||||
region = is_path(region)? [region] : region,
|
|
||||||
cp = mean(pointlist_bounds(flatten(region))),
|
|
||||||
regions = region_parts(region),
|
regions = region_parts(region),
|
||||||
|
cp = mean(pointlist_bounds(flatten(region))),
|
||||||
slices = default(slices, floor(twist/5+1)),
|
slices = default(slices, floor(twist/5+1)),
|
||||||
step = twist/slices,
|
step = twist/slices,
|
||||||
hstep = height/slices,
|
hstep = height/slices,
|
||||||
|
@ -706,64 +720,73 @@ function _point_dist(path,pathseg_unit,pathseg_len,pt) =
|
||||||
// return_faces = return face list. Default: False.
|
// return_faces = return face list. Default: False.
|
||||||
// firstface_index = starting index for face list. Default: 0.
|
// firstface_index = starting index for face list. Default: 0.
|
||||||
// flip_faces = flip face direction. Default: false
|
// flip_faces = flip face direction. Default: false
|
||||||
// Example(2D):
|
// Example(2D,NoAxes):
|
||||||
// star = star(5, r=100, ir=30);
|
// star = star(5, r=100, ir=30);
|
||||||
// #stroke(closed=true, star);
|
// #stroke(closed=true, star, width=3);
|
||||||
// stroke(closed=true, offset(star, delta=10, closed=true));
|
// stroke(closed=true, width=3, offset(star, delta=10, closed=true));
|
||||||
// Example(2D):
|
// Example(2D,NoAxes):
|
||||||
// star = star(5, r=100, ir=30);
|
// star = star(5, r=100, ir=30);
|
||||||
// #stroke(closed=true, star);
|
// #stroke(closed=true, star, width=3);
|
||||||
// stroke(closed=true, offset(star, delta=10, chamfer=true, closed=true));
|
// stroke(closed=true, width=3,
|
||||||
// Example(2D):
|
// offset(star, delta=10, chamfer=true, closed=true));
|
||||||
|
// Example(2D,NoAxes):
|
||||||
// star = star(5, r=100, ir=30);
|
// star = star(5, r=100, ir=30);
|
||||||
// #stroke(closed=true, star);
|
// #stroke(closed=true, star, width=3);
|
||||||
// stroke(closed=true, offset(star, r=10, closed=true));
|
// stroke(closed=true, width=3,
|
||||||
// Example(2D):
|
// offset(star, r=10, closed=true));
|
||||||
// star = star(5, r=100, ir=30);
|
// Example(2D,NoAxes):
|
||||||
// #stroke(closed=true, star);
|
// star = star(7, r=120, ir=50);
|
||||||
// stroke(closed=true, offset(star, delta=-10, closed=true));
|
// #stroke(closed=true, width=3, star);
|
||||||
// Example(2D):
|
// stroke(closed=true, width=3,
|
||||||
// star = star(5, r=100, ir=30);
|
// offset(star, delta=-15, closed=true));
|
||||||
// #stroke(closed=true, star);
|
// Example(2D,NoAxes):
|
||||||
// stroke(closed=true, offset(star, delta=-10, chamfer=true, closed=true));
|
// star = star(7, r=120, ir=50);
|
||||||
// Example(2D):
|
// #stroke(closed=true, width=3, star);
|
||||||
// star = star(5, r=100, ir=30);
|
// stroke(closed=true, width=3,
|
||||||
// #stroke(closed=true, star);
|
// offset(star, delta=-15, chamfer=true, closed=true));
|
||||||
// stroke(closed=true, offset(star, r=-10, closed=true, $fn=20));
|
// Example(2D,NoAxes):
|
||||||
// Example(2D): This case needs `quality=2` for success
|
// star = star(7, r=120, ir=50);
|
||||||
|
// #stroke(closed=true, width=3, star);
|
||||||
|
// stroke(closed=true, width=3,
|
||||||
|
// offset(star, r=-15, closed=true, $fn=20));
|
||||||
|
// Example(2D,NoAxes): This case needs `quality=2` for success
|
||||||
// test = [[0,0],[10,0],[10,7],[0,7], [-1,-3]];
|
// test = [[0,0],[10,0],[10,7],[0,7], [-1,-3]];
|
||||||
// polygon(offset(test,r=-1.9, closed=true, quality=2));
|
// polygon(offset(test,r=-1.9, closed=true, quality=2));
|
||||||
// //polygon(offset(test,r=-1.9, closed=true, quality=1)); // Fails with erroneous 180 deg path error
|
// //polygon(offset(test,r=-1.9, closed=true, quality=1)); // Fails with erroneous 180 deg path error
|
||||||
// %down(.1)polygon(test);
|
// %down(.1)polygon(test);
|
||||||
// Example(2D): This case fails if `check_valid=true` when delta is large enough because segments are too close to the opposite side of the curve.
|
// Example(2D,NoAxes): This case fails if `check_valid=true` when delta is large enough because segments are too close to the opposite side of the curve.
|
||||||
// star = star(5, r=22, ir=13);
|
// star = star(5, r=22, ir=13);
|
||||||
// stroke(star,width=.2,closed=true);
|
// stroke(star,width=.3,closed=true);
|
||||||
// color("green")
|
// color("green")
|
||||||
// stroke(offset(star, delta=-9, closed=true),width=.2,closed=true); // Works with check_valid=true (the default)
|
// stroke(offset(star, delta=-9, closed=true),width=.3,closed=true); // Works with check_valid=true (the default)
|
||||||
// color("red")
|
// color("red")
|
||||||
// stroke(offset(star, delta=-10, closed=true, check_valid=false), // Fails if check_valid=true
|
// stroke(offset(star, delta=-10, closed=true, check_valid=false), // Fails if check_valid=true
|
||||||
// width=.2,closed=true);
|
// width=.3,closed=true);
|
||||||
// Example(2D): But if you use rounding with offset then you need `check_valid=true` when `r` is big enough. It works without the validity check as long as the offset shape retains a some of the straight edges at the star tip, but once the shape shrinks smaller than that, it fails. There is no simple way to get a correct result for the case with `r=10`, because as in the previous example, it will fail if you turn on validity checks.
|
// Example(2D): But if you use rounding with offset then you need `check_valid=true` when `r` is big enough. It works without the validity check as long as the offset shape retains a some of the straight edges at the star tip, but once the shape shrinks smaller than that, it fails. There is no simple way to get a correct result for the case with `r=10`, because as in the previous example, it will fail if you turn on validity checks.
|
||||||
// star = star(5, r=22, ir=13);
|
// star = star(5, r=22, ir=13);
|
||||||
// color("green")
|
// color("green")
|
||||||
// stroke(offset(star, r=-8, closed=true,check_valid=false), width=.1, closed=true);
|
// stroke(offset(star, r=-8, closed=true,check_valid=false), width=.1, closed=true);
|
||||||
// color("red")
|
// color("red")
|
||||||
// stroke(offset(star, r=-10, closed=true,check_valid=false), width=.1, closed=true);
|
// stroke(offset(star, r=-10, closed=true,check_valid=false), width=.1, closed=true);
|
||||||
// Example(2D): The extra triangles in this example show that the validity check cannot be skipped
|
// Example(2D,NoAxes): The extra triangles in this example show that the validity check cannot be skipped
|
||||||
// ellipse = scale([20,4], p=circle(r=1,$fn=64));
|
// ellipse = scale([20,4], p=circle(r=1,$fn=64));
|
||||||
// stroke(ellipse, closed=true, width=0.3);
|
// stroke(ellipse, closed=true, width=0.3);
|
||||||
// stroke(offset(ellipse, r=-3, check_valid=false, closed=true), width=0.3, closed=true);
|
// stroke(offset(ellipse, r=-3, check_valid=false, closed=true),
|
||||||
// Example(2D): The triangles are removed by the validity check
|
// width=0.3, closed=true);
|
||||||
|
// Example(2D,NoAxes): The triangles are removed by the validity check
|
||||||
// ellipse = scale([20,4], p=circle(r=1,$fn=64));
|
// ellipse = scale([20,4], p=circle(r=1,$fn=64));
|
||||||
// stroke(ellipse, closed=true, width=0.3);
|
// stroke(ellipse, closed=true, width=0.3);
|
||||||
// stroke(offset(ellipse, r=-3, check_valid=true, closed=true), width=0.3, closed=true);
|
// stroke(offset(ellipse, r=-3, check_valid=true, closed=true),
|
||||||
|
// width=0.3, closed=true);
|
||||||
// Example(2D): Open path. The path moves from left to right and the positive offset shifts to the left of the initial red path.
|
// Example(2D): Open path. The path moves from left to right and the positive offset shifts to the left of the initial red path.
|
||||||
// sinpath = 2*[for(theta=[-180:5:180]) [theta/4,45*sin(theta)]];
|
// sinpath = 2*[for(theta=[-180:5:180]) [theta/4,45*sin(theta)]];
|
||||||
// #stroke(sinpath);
|
// #stroke(sinpath, width=2);
|
||||||
// stroke(offset(sinpath, r=17.5));
|
// stroke(offset(sinpath, r=17.5),width=2);
|
||||||
// Example(2D): Region
|
// Example(2D,NoAxes): Region
|
||||||
// rgn = difference(circle(d=100), union(square([20,40], center=true), square([40,20], center=true)));
|
// rgn = difference(circle(d=100),
|
||||||
// #linear_extrude(height=1.1) for (p=rgn) stroke(closed=true, width=0.5, p);
|
// union(square([20,40], center=true),
|
||||||
|
// square([40,20], center=true)));
|
||||||
|
// #linear_extrude(height=1.1) stroke(rgn, width=1);
|
||||||
// region(offset(rgn, r=-5));
|
// region(offset(rgn, r=-5));
|
||||||
function offset(
|
function offset(
|
||||||
path, r=undef, delta=undef, chamfer=false,
|
path, r=undef, delta=undef, chamfer=false,
|
||||||
|
@ -779,8 +802,10 @@ function offset(
|
||||||
chamfer=chamfer, check_valid=check_valid, quality=quality,closed=true)])]
|
chamfer=chamfer, check_valid=check_valid, quality=quality,closed=true)])]
|
||||||
)
|
)
|
||||||
union(ofsregs)
|
union(ofsregs)
|
||||||
: let(rcount = num_defined([r,delta]))
|
:
|
||||||
|
let(rcount = num_defined([r,delta]))
|
||||||
assert(rcount==1,"Must define exactly one of 'delta' and 'r'")
|
assert(rcount==1,"Must define exactly one of 'delta' and 'r'")
|
||||||
|
assert(is_path(path), "Input must be a path or region")
|
||||||
let(
|
let(
|
||||||
chamfer = is_def(r) ? false : chamfer,
|
chamfer = is_def(r) ? false : chamfer,
|
||||||
quality = max(0,round(quality)),
|
quality = max(0,round(quality)),
|
||||||
|
@ -936,8 +961,8 @@ function _filter_region_parts(region1, region2, keep1, keep2, eps=EPSILON) =
|
||||||
// Example(2D):
|
// Example(2D):
|
||||||
// shape1 = move([-8,-8,0], p=circle(d=50));
|
// shape1 = move([-8,-8,0], p=circle(d=50));
|
||||||
// shape2 = move([ 8, 8,0], p=circle(d=50));
|
// shape2 = move([ 8, 8,0], p=circle(d=50));
|
||||||
// for (shape = [shape1,shape2]) color("red") stroke(shape, width=0.5, closed=true);
|
|
||||||
// color("green") region(union(shape1,shape2));
|
// color("green") region(union(shape1,shape2));
|
||||||
|
// for (shape = [shape1,shape2]) color("red") stroke(shape, width=0.5, closed=true);
|
||||||
function union(regions=[],b=undef,c=undef,eps=EPSILON) =
|
function union(regions=[],b=undef,c=undef,eps=EPSILON) =
|
||||||
b!=undef? union(concat([regions],[b],c==undef?[]:[c]), eps=eps) :
|
b!=undef? union(concat([regions],[b],c==undef?[]:[c]), eps=eps) :
|
||||||
len(regions)==0? [] :
|
len(regions)==0? [] :
|
||||||
|
|
Loading…
Reference in a new issue