|
20 | 20 | from typing import Any, Union, Tuple, Sequence, List, Optional, Callable, Dict, cast |
21 | 21 | Point23 = Union[Point2, Point3] |
22 | 22 | Vector23 = Union[Vector2, Vector3] |
| 23 | +PointVec23 = Union[Point2, Point3, Vector2, Vector3] |
23 | 24 | Line23 = Union[Line2, Line3] |
24 | 25 | LineSegment23 = Union[LineSegment2, LineSegment3] |
25 | 26 |
|
| 27 | +FacetIndices = Tuple[int, int, int] |
| 28 | + |
26 | 29 | Tuple2 = Tuple[float, float] |
27 | 30 | Tuple3 = Tuple[float, float, float] |
28 | 31 | EucOrTuple = Union[Point3, |
@@ -107,7 +110,6 @@ def grid_plane(grid_unit:int=12, count:int=10, line_weight:float=0.1, plane:str= |
107 | 110 |
|
108 | 111 | return t |
109 | 112 |
|
110 | | - |
111 | 113 | def distribute_in_grid(objects:Sequence[OpenSCADObject], |
112 | 114 | max_bounding_box:Tuple[float,float], |
113 | 115 | rows_and_cols: Tuple[int,int]=None) -> OpenSCADObject: |
@@ -814,6 +816,27 @@ def scad_matrix(euclid_matrix4): |
814 | 816 | [a.m, a.n, a.o, a.p] |
815 | 817 | ] |
816 | 818 |
|
| 819 | +def centroid(points:Sequence[PointVec23]) -> PointVec23: |
| 820 | + if not points: |
| 821 | + raise ValueError(f"centroid(): argument `points` is empty") |
| 822 | + first = points[0] |
| 823 | + is_3d = isinstance(first, (Vector3, Point3)) |
| 824 | + if is_3d: |
| 825 | + total = Vector3(0,0,0) |
| 826 | + else: |
| 827 | + total = Vector2(0, 0) |
| 828 | + |
| 829 | + for p in points: |
| 830 | + total += p |
| 831 | + total /= len(points) |
| 832 | + |
| 833 | + if isinstance(first, Point2): |
| 834 | + return Point2(*total) |
| 835 | + elif isinstance(first, Point3): |
| 836 | + return Point3(*total) |
| 837 | + else: |
| 838 | + return total |
| 839 | + |
817 | 840 | # ============== |
818 | 841 | # = Transforms = |
819 | 842 | # ============== |
@@ -1184,19 +1207,45 @@ def extrude_along_path( shape_pts:Points, |
1184 | 1207 | facet_indices.append( (segment_start, segment_end, segment_end + shape_pt_count) ) |
1185 | 1208 | facet_indices.append( (segment_start, segment_end + shape_pt_count, segment_start + shape_pt_count) ) |
1186 | 1209 |
|
1187 | | - # Cap the start of the polyhedron |
1188 | | - for i in range(1, shape_pt_count - 1): |
1189 | | - facet_indices.append((0, i, i + 1)) |
1190 | | - |
1191 | | - # And the end (could be rolled into the earlier loop) |
1192 | | - # FIXME: concave cross-sections will cause this end-capping algorithm |
1193 | | - # to fail |
1194 | | - end_cap_base = len(polyhedron_pts) - shape_pt_count |
1195 | | - for i in range(end_cap_base + 1, len(polyhedron_pts) - 1): |
1196 | | - facet_indices.append( (end_cap_base, i + 1, i) ) |
| 1210 | + # endcap at start of extrusion |
| 1211 | + start_cap_index = len(polyhedron_pts) |
| 1212 | + start_loop_pts = polyhedron_pts[:shape_pt_count] |
| 1213 | + start_loop_indices = list(range(shape_pt_count)) |
| 1214 | + start_centroid, start_facet_indices = end_cap(start_cap_index, start_loop_pts, start_loop_indices) |
| 1215 | + polyhedron_pts.append(start_centroid) |
| 1216 | + facet_indices += start_facet_indices |
| 1217 | + |
| 1218 | + # endcap at end of extrusion |
| 1219 | + end_cap_index = len(polyhedron_pts) |
| 1220 | + last_loop_start_index = len(polyhedron_pts) - shape_pt_count - 1 |
| 1221 | + end_loop_pts = polyhedron_pts[last_loop_start_index:-1] |
| 1222 | + end_loop_indices = list(range(last_loop_start_index, len(polyhedron_pts) - 1)) |
| 1223 | + end_centroid, end_facet_indices = end_cap(end_cap_index, end_loop_pts, end_loop_indices) |
| 1224 | + polyhedron_pts.append(end_centroid) |
| 1225 | + facet_indices += end_facet_indices |
1197 | 1226 |
|
1198 | 1227 | return polyhedron(points=euc_to_arr(polyhedron_pts), faces=facet_indices) # type: ignore |
1199 | 1228 |
|
| 1229 | +def end_cap(new_point_index:int, points:Sequence[Point3], vertex_indices: Sequence[int]) -> Tuple[Point3, List[FacetIndices]]: |
| 1230 | + # Assume points are a basically planar, basically convex polygon with polyhedron |
| 1231 | + # indices `vertex_indices`. |
| 1232 | + # Return a new point that is the centroid of the polygon and a list of |
| 1233 | + # vertex triangle indices that covers the whole polygon. |
| 1234 | + # (We can actually accept relatively non-planar and non-convex polygons, |
| 1235 | + # but not anything pathological. Stars are fine, internal pockets would |
| 1236 | + # cause incorrect faceting) |
| 1237 | + |
| 1238 | + # NOTE: In order to deal with moderately-concave polygons, we add a point |
| 1239 | + # to the center of the end cap. This will have a new index that we require |
| 1240 | + # as an argument. |
| 1241 | + |
| 1242 | + new_point = centroid(points) |
| 1243 | + new_facets = [] |
| 1244 | + second_indices = vertex_indices[1:] + [vertex_indices[0]] |
| 1245 | + new_facets = [(new_point_index, a, b) for a, b in zip(vertex_indices, second_indices)] |
| 1246 | + |
| 1247 | + return (new_point, new_facets) |
| 1248 | + |
1200 | 1249 | def frange(*args): |
1201 | 1250 | """ |
1202 | 1251 | # {{{ http://code.activestate.com/recipes/577068/ (r1) |
|
0 commit comments