Skip to content

Commit 455a19b

Browse files
-- Fixed a bug in catmull_rom_points() that made closed catmull rom shapes C1-discontinuous.
-- Added `catmull_rom_prism_smooth_edges()` and added a `smooth_edges` argument to `catmull_rom_prism()`
1 parent 7ab68ea commit 455a19b

File tree

1 file changed

+69
-25
lines changed

1 file changed

+69
-25
lines changed

solid/splines.py

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,16 @@ def catmull_rom_points( points: Sequence[Point23Input],
7777
points_list = list([euclidify(p, Point3) for p in points])
7878

7979
if close_loop:
80-
cat_points = euclidify([points_list[-1]] + points_list + [points_list[0]], Point3)
80+
cat_points = euclidify([points_list[-1]] + points_list + points_list[0:2], Point3)
8181
else:
8282
# Use supplied tangents or just continue the ends of the supplied points
8383
start_tangent = start_tangent or (points_list[1] - points_list[0])
84+
start_tangent = euclidify(start_tangent, Vector3)
8485
end_tangent = end_tangent or (points_list[-2] - points_list[-1])
86+
end_tangent = euclidify(end_tangent, Vector3)
8587
cat_points = [points_list[0]+ start_tangent] + points_list + [points_list[-1] + end_tangent]
8688

87-
last_point_range = len(cat_points) - 2 if close_loop else len(cat_points) - 3
89+
last_point_range = len(cat_points) - 3 if close_loop else len(cat_points) - 3
8890

8991
for i in range(0, last_point_range):
9092
include_last = True if i == last_point_range - 1 else False
@@ -159,7 +161,10 @@ def catmull_rom_patch(patch:Tuple[PointInputs, PointInputs], subdivisions:int =
159161
def catmull_rom_prism( control_curves:Sequence[PointInputs],
160162
subdivisions:int = DEFAULT_SUBDIVISIONS,
161163
closed_ring:bool = True,
162-
add_caps:bool = True ) -> polyhedron:
164+
add_caps:bool = True,
165+
smooth_edges: bool = False ) -> polyhedron:
166+
if smooth_edges:
167+
return catmull_rom_prism_smooth_edges(control_curves, subdivisions, closed_ring, add_caps)
163168

164169
verts: List[Point3] = []
165170
faces: List[FaceTrio] = []
@@ -187,22 +192,62 @@ def catmull_rom_prism( control_curves:Sequence[PointInputs],
187192
if closed_ring and add_caps:
188193
bot_indices = range(0, len(verts), curve_length)
189194
top_indices = range(curve_length-1, len(verts), curve_length)
190-
bot_points = [verts[i] for i in bot_indices]
191-
top_points = [verts[i] for i in top_indices]
192-
193-
# FIXME: This won't work, since it assumes that the points making
194-
# up the two end caps are all in order. In fact, that's not the case;
195-
# the indexes of the points at the base cap are 0, 41, 82, etc. on a curve
196-
# with 5 points and 10 subdivisions.
197-
bot_centroid, bot_faces = centroid_endcap(bot_points, bot_indices, len(verts))
198-
top_centroid, top_faces = centroid_endcap(top_points, top_indices, len(verts) + 1, invert=True)
199-
verts += [bot_centroid, top_centroid]
195+
196+
bot_centroid, bot_faces = centroid_endcap(verts, bot_indices)
197+
verts.append(bot_centroid)
200198
faces += bot_faces
199+
# Note that bot_centroid must be added to verts before creating the
200+
# top endcap; otherwise both endcaps would point to the same centroid point
201+
top_centroid, top_faces = centroid_endcap(verts, top_indices, invert=True)
202+
verts.append(top_centroid)
201203
faces += top_faces
202204

203205
p = polyhedron(faces=faces, points=verts, convexity=3)
204206
return p
205207

208+
def catmull_rom_prism_smooth_edges( control_curves:Sequence[PointInputs],
209+
subdivisions:int = DEFAULT_SUBDIVISIONS,
210+
closed_ring:bool = True,
211+
add_caps:bool = True ) -> polyhedron:
212+
213+
verts: List[Point3] = []
214+
faces: List[FaceTrio] = []
215+
216+
# TODO: verify that each control_curve has the same length
217+
218+
curves = list([euclidify(c) for c in control_curves])
219+
220+
expanded_curves = [catmull_rom_points(c, subdivisions, close_loop=False) for c in curves]
221+
expanded_length = len(expanded_curves[0])
222+
for i in range(expanded_length):
223+
contour_controls = [c[i] for c in expanded_curves]
224+
contour = catmull_rom_points(contour_controls, subdivisions, close_loop=closed_ring)
225+
verts += contour
226+
227+
contour_length = len(contour)
228+
# generate the face triangles between the last two rows of vertices
229+
if i > 0:
230+
a_start = len(verts) - 2 * contour_length
231+
b_start = len(verts) - contour_length
232+
new_faces = face_strip_list(a_start, b_start, length=contour_length, close_loop=closed_ring)
233+
faces += new_faces
234+
235+
if closed_ring and add_caps:
236+
bot_indices = range(0, contour_length)
237+
top_indices = range(len(verts) - contour_length, len(verts))
238+
239+
bot_centroid, bot_faces = centroid_endcap(verts, bot_indices)
240+
verts.append(bot_centroid)
241+
faces += bot_faces
242+
# Note that bot_centroid must be added to verts before creating the
243+
# top endcap; otherwise both endcaps would point to the same centroid point
244+
top_centroid, top_faces = centroid_endcap(verts, top_indices, invert=True)
245+
verts.append(top_centroid)
246+
faces += top_faces
247+
248+
p = polyhedron(faces=faces, points=verts, convexity=3)
249+
return p
250+
206251
# ==================
207252
# = BEZIER SPLINES =
208253
# ==================
@@ -215,7 +260,7 @@ def bezier_polygon( controls: FourPoints,
215260
show_controls: bool = False,
216261
center: bool = True) -> OpenSCADObject:
217262
'''
218-
Return an OpenSCAD representing a closed quadratic Bezier curve.
263+
Return an OpenSCAD object representing a closed quadratic Bezier curve.
219264
If extrude_height == 0, return a 2D `polygon()` object.
220265
If extrude_height > 0, return a 3D extrusion of specified height.
221266
Note that OpenSCAD won't render 2D & 3D objects together correctly, so pick
@@ -356,22 +401,21 @@ def fan_endcap_list(cap_points:int=3, index_start:int=0) -> List[FaceTrio]:
356401
faces.append((index_start, i, i+1))
357402
return faces
358403

359-
def centroid_endcap(points:Sequence[Point3], indices:Sequence[int], total_vert_count:int, invert:bool = False) -> Tuple[Point3, List[FaceTrio]]:
360-
# Given a list of points at one end of a polyhedron tube, and their
361-
# accompanying indices, make a centroid point, and return all the triangle
362-
# information needed to make an endcap polyhedron.
363-
364-
# `total_vert_count` should be the number of vertices in the existing shape
365-
# *before* calling this function; we'll return a point that should be appended
366-
# to the total vertex list for the polyhedron-to-be
367-
404+
def centroid_endcap(tube_points:Sequence[Point3], indices:Sequence[int], invert:bool = False) -> Tuple[Point3, List[FaceTrio]]:
405+
# tube_points: all points in a polyhedron tube
406+
# indices: the indexes of the points at the desired end of the tube
407+
# invert: if True, invert the order of the generated faces. One endcap in
408+
# each pair should be inverted
409+
#
410+
# Return all the triangle information needed to make an endcap polyhedron
411+
#
368412
# This is sufficient for some moderately concave polygonal endcaps,
369413
# (a star shape, say), but wouldn't be enough for more irregularly convex
370414
# polygons (anyplace where a segment from the centroid to a point on the
371415
# polygon crosses an edge of the polygon)
372416
faces: List[FaceTrio] = []
373-
center = centroid(points)
374-
centroid_index = total_vert_count
417+
center = centroid([tube_points[i] for i in indices])
418+
centroid_index = len(tube_points)
375419

376420
for a,b in zip(indices[:-1], indices[1:]):
377421
faces.append((centroid_index, a, b))

0 commit comments

Comments
 (0)