mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 00:09:41 +00:00
renamed is_region_simple to is_valid_region and fixed bugs and added examples
fixed bugs in pair and triplet and added degenerate test cases
This commit is contained in:
parent
530f3b3449
commit
fe0586180e
8 changed files with 175 additions and 35 deletions
|
@ -554,6 +554,7 @@ function dashed_stroke(path, dashpat=[3,3], closed=false) =
|
|||
|
||||
|
||||
module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) {
|
||||
no_children($children);
|
||||
segs = dashed_stroke(path, dashpat=dashpat*width, closed=closed);
|
||||
for (seg = segs)
|
||||
stroke(seg, width=width, endcaps=false);
|
||||
|
@ -731,6 +732,7 @@ module arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false)
|
|||
// stroke(helix(turns=-2.5, h=100, r=50), dots=true, dots_color="blue");
|
||||
// Example(3D): Flat helix (note points are still 3d)
|
||||
// stroke(helix(h=0,r1=50,r2=25,l=0, turns=4));
|
||||
module helix(l,h,turns,angle, r, r1, r2, d, d1, d2) {no_module();}
|
||||
function helix(l,h,turns,angle, r, r1, r2, d, d1, d2)=
|
||||
let(
|
||||
r1=get_radius(r=r,r1=r1,d=d,d1=d1,dflt=1),
|
||||
|
|
|
@ -1439,7 +1439,7 @@ function _region_centroid(region,eps=EPSILON) =
|
|||
total[0]/total[1];
|
||||
|
||||
|
||||
/// Function: _polygon_centroid()
|
||||
/// Internal Function: _polygon_centroid()
|
||||
/// Usage:
|
||||
/// cpt = _polygon_centroid(poly);
|
||||
/// Topics: Geometry, Polygons, Centroid
|
||||
|
|
13
lists.scad
13
lists.scad
|
@ -955,11 +955,13 @@ function enumerate(l,idx=undef) =
|
|||
function pair(list, wrap=false) =
|
||||
assert(is_list(list)||is_string(list), "Invalid input." )
|
||||
assert(is_bool(wrap))
|
||||
let(
|
||||
ll = len(list)
|
||||
) wrap
|
||||
? [for (i=[0:1:ll-1]) [list[i], list[(i+1) % ll]]]
|
||||
: [for (i=[0:1:ll-2]) [list[i], list[i+1]]];
|
||||
let( L = len(list)-1)
|
||||
L<1 ? [] :
|
||||
[
|
||||
for (i=[0:1:L-1]) [list[i], list[i+1]],
|
||||
if(wrap) [list[L], list[0]]
|
||||
];
|
||||
|
||||
|
||||
|
||||
// Function: triplet()
|
||||
|
@ -993,6 +995,7 @@ function triplet(list, wrap=false) =
|
|||
assert(is_list(list)||is_string(list), "Invalid input." )
|
||||
assert(is_bool(wrap))
|
||||
let(L=len(list))
|
||||
L<3 ? [] :
|
||||
[
|
||||
if(wrap) [list[L-1], list[0], list[1]],
|
||||
for (i=[0:1:L-3]) [list[i],list[i+1],list[i+2]],
|
||||
|
|
|
@ -530,6 +530,57 @@ module path_extrude2d(path, caps=false, closed=false) {
|
|||
right_half(planar=true) children();
|
||||
}
|
||||
}
|
||||
module new_path_extrude2d(path, caps=false, closed=false) {
|
||||
extra_ang = 0.1; // Extra angle for overlap of joints
|
||||
assert(caps==false || closed==false, "Cannot have caps on a closed extrusion");
|
||||
path = deduplicate(path);
|
||||
|
||||
|
||||
for (i=[0:1:len(path)-(closed?1:2)]){
|
||||
// for (i=[0:1:1]){
|
||||
difference(){
|
||||
extrude_from_to(path[i],select(path,i+1)) xflip()rot(-90)children();
|
||||
# for(t = [select(path,i-1,i+1)]){ //, select(path,i,i+2)]){
|
||||
ang = -(180-vector_angle(t)) * sign(_point_left_of_line2d(t[2],[t[0],t[1]]));
|
||||
echo(ang=ang);
|
||||
delt = point3d(t[2] - t[1]);
|
||||
if (ang!=0)
|
||||
translate(t[1]) {
|
||||
frame_map(y=delt, z=UP)
|
||||
rotate(-sign(ang)*extra_ang/2)
|
||||
rotate_extrude(angle=ang+sign(ang)*extra_ang)
|
||||
if (ang<0)
|
||||
left_half(planar=true) children();
|
||||
else
|
||||
right_half(planar=true) children();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (t=triplet(path,wrap=closed)) {
|
||||
ang = -(180-vector_angle(t)) * sign(_point_left_of_line2d(t[2],[t[0],t[1]]));
|
||||
echo(oang=ang);
|
||||
delt = point3d(t[2] - t[1]);
|
||||
if (ang!=0)
|
||||
translate(t[1]) {
|
||||
frame_map(y=delt, z=UP)
|
||||
rotate(-sign(ang)*extra_ang/2)
|
||||
rotate_extrude(angle=ang+sign(ang)*extra_ang)
|
||||
if (ang<0)
|
||||
right_half(planar=true) children();
|
||||
else
|
||||
left_half(planar=true) children();
|
||||
}
|
||||
|
||||
}
|
||||
if (caps) {
|
||||
move_copies([path[0],last(path)])
|
||||
rotate_extrude()
|
||||
right_half(planar=true) children();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Module: cylindrical_extrude()
|
||||
|
|
123
regions.scad
123
regions.scad
|
@ -24,28 +24,98 @@
|
|||
// compliant. You can construct regions by making a list of polygons, or by using
|
||||
// boolean function operations such as union() or difference(), which all except paths, as
|
||||
// well as regions, as their inputs. And if you must you
|
||||
// can clean up an ill-formed region using sanitize_region().
|
||||
// can clean up an ill-formed region using make_region().
|
||||
|
||||
|
||||
// Function: is_region()
|
||||
// Usage:
|
||||
// is_region(x);
|
||||
// Description:
|
||||
// Returns true if the given item looks like a region. A region is defined as a list of zero or more paths.
|
||||
// Returns true if the given item looks like a region. A region is a list of non-crossing simple paths. This test just checks
|
||||
// that the argument is a list whose first entry is a path.
|
||||
function is_region(x) = is_list(x) && is_path(x.x);
|
||||
|
||||
|
||||
// Function: force_region()
|
||||
// Function: is_valid_region()
|
||||
// Usage:
|
||||
// region = force_region(path)
|
||||
// bool = is_valid_region(region, [eps]);
|
||||
// Description:
|
||||
// If the input is a path then return it as a region. Otherwise return it unaltered.
|
||||
function force_region(path) = is_path(path) ? [path] : path;
|
||||
// Returns true if the input is a valid region, meaning that it is a list of simple paths whose segments do not cross each other.
|
||||
// This test can be time consuming with regions that contain many points.
|
||||
// It differs from `is_region()` which simply checks that the object appears to be a list of paths
|
||||
// because it searches all the region paths for any self-intersections or intersections with each other.
|
||||
// Will also return true if given a single simple path. Use {{make_region()}} to convert sets of self-intersecting polygons into
|
||||
// a region.
|
||||
// Arguments:
|
||||
// region = region to check
|
||||
// eps = tolerance for geometric comparisons. Default: `EPSILON` = 1e-9
|
||||
// Example(2D,noaxes): Nested squares form a region
|
||||
// region = [for(i=[3:2:10]) square(i,center=true)];
|
||||
// rainbow(region)stroke($item, width=.1,closed=true);
|
||||
// back(6)text(is_valid_region(region) ? "region" : "non-region", size=2,halign="center");
|
||||
// Example(2D,noaxes): Two non-intersecting squares make a valid region:
|
||||
// region = [square(10), right(11,square(8))];
|
||||
// rainbow(region)stroke($item, width=.1,closed=true);
|
||||
// back(12)text(is_valid_region(region) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): Not a region due to a self-intersecting (non-simple) hourglass path
|
||||
// object = [move([-2,-2],square(14)), [[0,0],[10,0],[0,10],[10,10]]];
|
||||
// rainbow(object)stroke($item, width=.1,closed=true);
|
||||
// move([-1.5,13])text(is_valid_region(object) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): Breaking hourglass in half fixes it. Now it's a region:
|
||||
// region = [move([-2,-2],square(14)), [[0,0],[10,0],[5,5]], [[5,5],[0,10],[10,10]]];
|
||||
// rainbow(region)stroke($item, width=.1,closed=true);
|
||||
// move([1,13])text(is_valid_region(region) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): As with the "broken" hourglass, Touching at corners is OK. This is a region.
|
||||
// region = [square(10), move([10,10], square(8))];
|
||||
// rainbow(region)stroke($item, width=.1,closed=true);
|
||||
// back(12)text(is_valid_region(region) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): The squares cross each other, so not a region
|
||||
// object = [square(10), move([8,8], square(8))];
|
||||
// rainbow(object)stroke($item, width=.1,closed=true);
|
||||
// back(17)text(is_valid_region(object) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): A union is one way to fix the above example and get a region. (Note that union is run here on two simple paths, which are valid regions themselves and hence acceptable inputs to union.
|
||||
// region = union([square(10), move([8,8], square(8))]);
|
||||
// rainbow(region)stroke($item, width=.1,closed=true);
|
||||
// back(12)text(is_valid_region(region) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): These two squares share part of an edge, hence not a region
|
||||
// object = [square(10), move([10,2], square(7))];
|
||||
// stroke(object[0], width=0.1,closed=true);
|
||||
// color("red")dashed_stroke(object[1], width=0.1,closed=true);
|
||||
// back(12)text(is_valid_region(object) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): These two squares share a full edge, hence not a region
|
||||
// object = [square(10), right(10, square(10))];
|
||||
// stroke(object[0], width=0.1,closed=true);
|
||||
// color("red")dashed_stroke(object[1], width=0.1,closed=true);
|
||||
// back(12)text(is_valid_region(object) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): Sharing on edge on the inside, also not a regionn
|
||||
// object = [square(10), [[0,0], [2,2],[2,8],[0,10]]];
|
||||
// stroke(object[0], width=0.1,closed=true);
|
||||
// color("red")dashed_stroke(object[1], width=0.1,closed=true);
|
||||
// back(12)text(is_valid_region(object) ? "region" : "non-region", size=2);
|
||||
function is_valid_region(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=eps)) 1] == []
|
||||
&&
|
||||
[for(i=[0:1:len(region)-2])
|
||||
|
||||
let( isect = _region_region_intersections([region[i]], list_tail(region,i+1), eps=eps))
|
||||
each [
|
||||
// check for intersection points not at the end of a segment
|
||||
for(pts=flatten(isect[0])) if (pts[2]!=0 && pts[2]!=1) 1,
|
||||
// check for full segment
|
||||
for(seg=pair(flatten(isect[0])))
|
||||
if (seg[0][0]==seg[1][0] // same path
|
||||
&& seg[0][1]==seg[1][1] // same segment
|
||||
&& seg[0][2]==0 && seg[1][2]==1) // both ends
|
||||
1]
|
||||
] ==[];
|
||||
|
||||
|
||||
// Function: sanitize_region()
|
||||
|
||||
// Function: make_region()
|
||||
// Usage:
|
||||
// r_fixed = sanitize_region(r, [nonzero], [eps]);
|
||||
// r_fixed = make_region(r, [nonzero], [eps]);
|
||||
// Description:
|
||||
// Takes a malformed input region that contains self-intersecting polygons or polygons
|
||||
// that cross each other and converts it into a properly defined region without
|
||||
|
@ -56,7 +126,7 @@ function force_region(path) = is_path(path) ? [path] : path;
|
|||
// eps = Epsilon for geometric comparisons. Default: `EPSILON` (1e-9)
|
||||
// Examples:
|
||||
//
|
||||
function sanitize_region(r,nonzero=false,eps=EPSILON) =
|
||||
function make_region(r,nonzero=false,eps=EPSILON) =
|
||||
let(r=force_region(r))
|
||||
assert(is_region(r), "Input is not a region")
|
||||
exclusive_or(
|
||||
|
@ -64,6 +134,17 @@ function sanitize_region(r,nonzero=false,eps=EPSILON) =
|
|||
eps=eps);
|
||||
|
||||
|
||||
|
||||
// Function: force_region()
|
||||
// Usage:
|
||||
// region = force_region(path)
|
||||
// Description:
|
||||
// If the input is a path then return it as a region. Otherwise return it unaltered.
|
||||
function force_region(path) = is_path(path) ? [path] : path;
|
||||
|
||||
|
||||
// Section: Turning a region into geometry
|
||||
|
||||
// Module: region()
|
||||
// Usage:
|
||||
// region(r);
|
||||
|
@ -93,6 +174,8 @@ module region(r)
|
|||
|
||||
|
||||
|
||||
// Section: Gometrical calculations with region
|
||||
|
||||
// Function: point_in_region()
|
||||
// Usage:
|
||||
// check = point_in_region(point, region, [eps]);
|
||||
|
@ -128,23 +211,6 @@ function region_area(region) =
|
|||
-sum([for(R=parts, poly=R) polygon_area(poly,signed=true)]);
|
||||
|
||||
|
||||
// Function: is_region_simple()
|
||||
// Usage:
|
||||
// bool = is_region_simple(region, [eps]);
|
||||
// Description:
|
||||
// Returns true if the region is entirely non-self-intersecting, meaning that it is
|
||||
// formed from a list of simple polygons that do not intersect each other.
|
||||
// Arguments:
|
||||
// region = region to check
|
||||
// eps = tolerance for geometric comparisons. Default: `EPSILON` = 1e-9
|
||||
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(i=[0:1:len(region)-2])
|
||||
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)];
|
||||
|
||||
|
@ -182,7 +248,7 @@ function __are_regions_equal(region1, region2, i) =
|
|||
/// Returns a pair of sorted lists such that risect[0] is a list of intersection
|
||||
/// points for every path in region1, and similarly risect[1] is a list of intersection
|
||||
/// points for the paths in region2. For each path the intersection list is
|
||||
/// a sorted list of the form [SEGMENT, U]. You can specify that the paths in either
|
||||
/// a sorted list of the form [PATHIND, SEGMENT, U]. You can specify that the paths in either
|
||||
/// region be regarded as open paths if desired. Default is to treat them as
|
||||
/// regions and hence the paths as closed polygons.
|
||||
/// .
|
||||
|
@ -252,6 +318,9 @@ function _region_region_intersections(region1, region2, closed1=true,closed2=tru
|
|||
[for(i=[0:1]) [for(j=counts[i]) _sort_vectors(select(risect[i],pathind[i][j]))]];
|
||||
|
||||
|
||||
// Section: Breaking up regions into subregions
|
||||
|
||||
|
||||
// Function: split_region_at_region_crossings()
|
||||
// Usage:
|
||||
// split_region = split_region_at_region_crossings(region1, region2, [closed1], [closed2], [eps])
|
||||
|
|
|
@ -351,6 +351,12 @@ module test_pair() {
|
|||
assert(pair("ABCD",true) == [["A","B"], ["B","C"], ["C","D"], ["D","A"]]);
|
||||
assert(pair([3,4,5,6],wrap=true) == [[3,4], [4,5], [5,6], [6,3]]);
|
||||
assert(pair("ABCD",wrap=true) == [["A","B"], ["B","C"], ["C","D"], ["D","A"]]);
|
||||
assert_equal(pair([],wrap=true),[]);
|
||||
assert_equal(pair([],wrap=false),[]);
|
||||
assert_equal(pair([1],wrap=true),[]);
|
||||
assert_equal(pair([1],wrap=false),[]);
|
||||
assert_equal(pair([1,2],wrap=false),[[1,2]]);
|
||||
assert_equal(pair([1,2],wrap=true),[[1,2],[2,1]]);
|
||||
}
|
||||
test_pair();
|
||||
|
||||
|
@ -361,6 +367,14 @@ module test_triplet() {
|
|||
assert(triplet([3,4,5,6],true) == [[6,3,4],[3,4,5], [4,5,6], [5,6,3]]);
|
||||
assert(triplet("ABCD",true) == [["D","A","B"],["A","B","C"], ["B","C","D"], ["C","D","A"]]);
|
||||
assert(triplet("ABCD",wrap=true) == [["D","A","B"],["A","B","C"], ["B","C","D"], ["C","D","A"]]);
|
||||
assert_equal(triplet([],wrap=true),[]);
|
||||
assert_equal(triplet([],wrap=false),[]);
|
||||
assert_equal(triplet([1],wrap=true),[]);
|
||||
assert_equal(triplet([1],wrap=false),[]);
|
||||
assert_equal(triplet([1,2],wrap=true),[]);
|
||||
assert_equal(triplet([1,2],wrap=false),[]);
|
||||
assert_equal(triplet([1,2,3],wrap=true),[[3,1,2],[1,2,3],[2,3,1]]);
|
||||
assert_equal(triplet([1,2,3],wrap=false),[[1,2,3]]);
|
||||
}
|
||||
test_triplet();
|
||||
|
||||
|
|
|
@ -427,6 +427,7 @@ function _turtle3d_state_valid(state) =
|
|||
&& is_num(state[3])
|
||||
&& is_num(state[4]);
|
||||
|
||||
module turtle3d(commands, state=RIGHT, transforms=false, full_state=false, repeat=1) {no_module();}
|
||||
function turtle3d(commands, state=RIGHT, transforms=false, full_state=false, repeat=1) =
|
||||
assert(is_bool(transforms))
|
||||
let(
|
||||
|
|
4
vnf.scad
4
vnf.scad
|
@ -620,7 +620,7 @@ function _split_2dpolygons_at_each_x(polys, xs, _i=0) =
|
|||
], xs, _i=_i+1
|
||||
);
|
||||
|
||||
/// Function: _slice_3dpolygons()
|
||||
/// Internal Function: _slice_3dpolygons()
|
||||
/// Usage:
|
||||
/// splitpolys = _slice_3dpolygons(polys, dir, cuts);
|
||||
/// Topics: Geometry, Polygons, Intersections
|
||||
|
@ -760,7 +760,7 @@ function vnf_area(vnf) =
|
|||
sum([for(face=vnf[1]) polygon_area(select(verts,face))]);
|
||||
|
||||
|
||||
/// Function: _vnf_centroid()
|
||||
/// Internal Function: _vnf_centroid()
|
||||
/// Usage:
|
||||
/// vol = _vnf_centroid(vnf);
|
||||
/// Description:
|
||||
|
|
Loading…
Reference in a new issue