diff --git a/geometry.scad b/geometry.scad index e142de9..aab5cda 100644 --- a/geometry.scad +++ b/geometry.scad @@ -1454,5 +1454,146 @@ function polygon_normal(poly) = ) unit(n); +function _split_polygon_at_x(poly, x) = + let( + xs = subindex(poly,0) + ) (min(xs) >= x || max(xs) <= x)? [poly] : + let( + poly2 = [ + for (p = pair_wrap(poly)) each [ + p[0], + if( + (p[0].x < x && p[1].x > x) || + (p[1].x < x && p[0].x > x) + ) let( + u = (x - p[0].x) / (p[1].x - p[0].x) + ) [ + x, // Important for later exact match tests + u*(p[1].y-p[0].y)+p[0].y, + u*(p[1].z-p[0].z)+p[0].z, + ] + ] + ], + out1 = [for (p = poly2) if(p.x <= x) p], + out2 = [for (p = poly2) if(p.x >= x) p], + out = [ + if (len(out1)>=3) out1, + if (len(out2)>=3) out2, + ] + ) out; + + +function _split_polygon_at_y(poly, y) = + let( + ys = subindex(poly,1) + ) (min(ys) >= y || max(ys) <= y)? [poly] : + let( + poly2 = [ + for (p = pair_wrap(poly)) each [ + p[0], + if( + (p[0].y < y && p[1].y > y) || + (p[1].y < y && p[0].y > y) + ) let( + u = (y - p[0].y) / (p[1].y - p[0].y) + ) [ + u*(p[1].x-p[0].x)+p[0].x, + y, // Important for later exact match tests + u*(p[1].z-p[0].z)+p[0].z, + ] + ] + ], + out1 = [for (p = poly2) if(p.y <= y) p], + out2 = [for (p = poly2) if(p.y >= y) p], + out = [ + if (len(out1)>=3) out1, + if (len(out2)>=3) out2, + ] + ) out; + + +function _split_polygon_at_z(poly, z) = + let( + zs = subindex(poly,1) + ) (min(zs) >= z || max(zs) <= z)? [poly] : + let( + poly2 = [ + for (p = pair_wrap(poly)) each [ + p[0], + if( + (p[0].z < z && p[1].z > z) || + (p[1].z < z && p[0].z > z) + ) let( + u = (z - p[0].z) / (p[1].z - p[0].z) + ) [ + u*(p[1].x-p[0].x)+p[0].x, + u*(p[1].y-p[0].y)+p[0].y, + z, // Important for later exact match tests + ] + ] + ], + out1 = [for (p = poly2) if(p.z <= z) p], + out2 = [for (p = poly2) if(p.z >= z) p], + out = [ + if (len(out1)>=3) out1, + if (len(out2)>=3) out2, + ] + ) out; + + +// Function: split_polygons_at_each_x() +// Usage: +// splitpolys = split_polygons_at_each_x(polys, xs); +// Description: +// Given a list of 3D polygons, splits all of them wherever they cross any X value given in `xs`. +// Arguments: +// polys = A list of 3D polygons to split. +// xs = A list of scalar X values to split at. +function split_polygons_at_each_x(polys, xs, _i=0) = + _i>=len(xs)? polys : + split_polygons_at_each_x( + [ + for (poly = polys) + each _split_polygon_at_x(poly, xs[_i]) + ], xs, _i=_i+1 + ); + + +// Function: split_polygons_at_each_y() +// Usage: +// splitpolys = split_polygons_at_each_y(polys, ys); +// Description: +// Given a list of 3D polygons, splits all of them wherever they cross any Y value given in `ys`. +// Arguments: +// polys = A list of 3D polygons to split. +// ys = A list of scalar Y values to split at. +function split_polygons_at_each_y(polys, ys, _i=0) = + _i>=len(ys)? polys : + split_polygons_at_each_y( + [ + for (poly = polys) + each _split_polygon_at_y(poly, ys[_i]) + ], ys, _i=_i+1 + ); + + +// Function: split_polygons_at_each_z() +// Usage: +// splitpolys = split_polygons_at_each_z(polys, zs); +// Description: +// Given a list of 3D polygons, splits all of them wherever they cross any Z value given in `zs`. +// Arguments: +// polys = A list of 3D polygons to split. +// zs = A list of scalar Z values to split at. +function split_polygons_at_each_z(polys, zs, _i=0) = + _i>=len(zs)? polys : + split_polygons_at_each_z( + [ + for (poly = polys) + each _split_polygon_at_z(poly, zs[_i]) + ], zs, _i=_i+1 + ); + + // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/version.scad b/version.scad index 0525317..e40e25f 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,300]; +BOSL_VERSION = [2,0,301]; // Section: BOSL Library Version Functions diff --git a/vnf.scad b/vnf.scad index 8622761..c6c2f09 100644 --- a/vnf.scad +++ b/vnf.scad @@ -397,6 +397,87 @@ function vnf_centroid(vnf) = ) val[1]/val[0]/8; +function _triangulate_planar_convex_polygons(polys) = + polys==[]? [] : + let( + tris = [for (poly=polys) if (len(poly)==3) poly], + bigs = [for (poly=polys) if (len(poly)>3) poly], + newtris = [for (poly=bigs) select(poly,-2,0)], + newbigs = [for (poly=bigs) select(poly,0,-2)], + newtris2 = _triangulate_planar_convex_polygons(newbigs), + outtris = concat(tris, newtris, newtris2) + ) outtris; + + +// Function: vnf_bend_around_y_axis() +// Usage: +// bentvnf = vnf_bend_around_y_axis(vnf); +// Description: +// Given a VNF that is entirely above, or entirely below the Z=0 plane, bends the VNF around the +// Y axis, splitting up faces as necessary. Returns the bent VNF. Will error out if the VNF +// straddles the Z=0 plane, or if the bent VNF would wrap more than completely around. The 1:1 +// radius is where the curved length of the bent VNF matches the length of the original VNF. If the +// `r` or `d` arguments are given, then they will specify the 1:1 radius or diameter. If they are +// not given, then the 1:1 radius will be defined by the distance of the furthest vertex in the +// original VNF from the Z=0 plane. You can adjust the granularity of the bend using the standard +// `$fa`, `$fs`, and `$fn` variables. +// Arguments: +// vnf = The original VNF to bend. +// r = If given, the radius where the size of the original shape is the same as in the original. +// d = If given, the diameter where the size of the original shape is the same as in the original. +// Example(3D): +// vnf0 = cube([100,40,10], center=true); +// vnf1 = up(35, p=vnf0); +// vnf2 = down(50, p=vnf0); +// bent1 = vnf_bend_around_y_axis(vnf1); +// bent2 = vnf_bend_around_y_axis(vnf2); +// vnf_polyhedron([bent1,bent2]); +// Example(3D): +// vnf0 = linear_sweep(star(n=5,step=2,d=100), height=10); +// vnf1 = up(35, p=vnf0); +// vnf2 = down(50, p=vnf0); +// bent1 = vnf_bend_around_y_axis(vnf1); +// bent2 = vnf_bend_around_y_axis(vnf2); +// vnf_polyhedron([bent1,bent2]); +// Example(3D): +// rgn = union(rect([100,20],center=true), rect([20,100],center=true)); +// vnf0 = linear_sweep(zrot(45,p=rgn), height=10); +// vnf1 = up(35, p=vnf0); +// vnf2 = down(50, p=vnf0); +// bent1 = vnf_bend_around_y_axis(vnf1); +// bent2 = vnf_bend_around_y_axis(vnf2); +// vnf_polyhedron([bent1,bent2]); +function vnf_bend_around_y_axis(vnf,r,d) = + let( + vnf = vnf_triangulate(vnf), + verts = vnf[0], + bounds = pointlist_bounds(verts), + bmin = bounds[0], + bmax = bounds[1], + r = get_radius(r=r,d=d,dflt=max(abs(bmax.z), abs(bmin.z))), + width = bmax.x - bmin.x + ) + assert(bmin.z > 0 || bmax.z < 0, "Entire shape MUST be completely above or below z=0.") + assert(width <= 2*PI*r, "Shape would wrap more than completely around the cylinder.") + let( + min_ang = 180 * bmin.x / (PI * r), + max_ang = 180 * bmax.x / (PI * r), + ang_span = max_ang-min_ang, + steps = ceil(segs(r) * ang_span/360), + step = width / steps, + bend_at = [for(i = [1:1:steps-1]) i*step+bmin.x], + facepolys = [for (face=vnf[1]) select(verts,face)], + splits = split_polygons_at_each_x(facepolys, bend_at), + newtris = _triangulate_planar_convex_polygons(splits), + bent_faces = [ + for (tri = newtris) [ + for (p = tri) let( + a = 180*p.x/(r*PI) * sign(bmax.z) + ) [p.z*sin(a), p.y, p.z*cos(a)] + ] + ] + ) vnf_add_faces(faces=bent_faces); + // Function&Module: vnf_validate() // Usage: As Function