Skip to content

Commit fa9745d

Browse files
author
cuttlefisch
committed
check in wip code for first hobby splines
1 parent d962740 commit fa9745d

File tree

4 files changed

+899
-315
lines changed

4 files changed

+899
-315
lines changed

solid/extrude_along_path.py

Lines changed: 109 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,50 +10,54 @@
1010
FacetIndices = Tuple[int, int, int]
1111
Point3Transform = Callable[[Point3, Optional[float], Optional[float]], Point3]
1212

13+
1314
# ==========================
1415
# = Extrusion along a path =
1516
# ==========================
16-
def extrude_along_path( shape_pts:Points,
17-
path_pts:Points,
18-
scales:Sequence[Union[Vector2, float, Tuple2]] = None,
19-
rotations: Sequence[float] = None,
20-
transforms: Sequence[Point3Transform] = None,
21-
connect_ends = False,
22-
cap_ends = True) -> OpenSCADObject:
23-
'''
17+
def extrude_along_path(
18+
shape_pts: Points,
19+
path_pts: Points,
20+
scales: Sequence[Union[Vector2, float, Tuple2]] = None,
21+
rotations: Sequence[float] = None,
22+
transforms: Sequence[Callable] = None,
23+
connect_ends=False,
24+
cap_ends=True,
25+
transform_args: List = None,
26+
transform_kwargs: Dict = None,
27+
) -> OpenSCADObject:
28+
"""
2429
Extrude the curve defined by shape_pts along path_pts.
2530
-- For predictable results, shape_pts must be planar, convex, and lie
2631
in the XY plane centered around the origin. *Some* nonconvexity (e.g, star shapes)
2732
and nonplanarity will generally work fine
28-
33+
2934
-- len(scales) should equal len(path_pts). No-op if not supplied
30-
Each entry may be a single number for uniform scaling, or a pair of
35+
Each entry may be a single number for uniform scaling, or a pair of
3136
numbers (or Point2) for differential X/Y scaling
3237
If not supplied, no scaling will occur.
33-
38+
3439
-- len(rotations) should equal 1 or len(path_pts). No-op if not supplied.
3540
Each point in shape_pts will be rotated by rotations[i] degrees at
3641
each point in path_pts. Or, if only one rotation is supplied, the shape
3742
will be rotated smoothly over rotations[0] degrees in the course of the extrusion
38-
43+
3944
-- len(transforms) should be 1 or be equal to len(path_pts). No-op if not supplied.
40-
Each entry should be have the signature:
45+
Each entry should be have the signature:
4146
def transform_func(p:Point3, path_norm:float, loop_norm:float): Point3
4247
where path_norm is in [0,1] and expresses progress through the extrusion
4348
and loop_norm is in [0,1] and express progress through a single loop of the extrusion
44-
49+
4550
-- if connect_ends is True, the first and last loops of the extrusion will
4651
be joined, which is useful for toroidal geometries. Overrides cap_ends
4752
4853
-- if cap_ends is True, each point in the first and last loops of the extrusion
4954
will be connected to the centroid of that loop. For planar, convex shapes, this
5055
works nicely. If shape is less planar or convex, some self-intersection may happen.
5156
Not applied if connect_ends is True
52-
'''
53-
57+
"""
5458

55-
polyhedron_pts:Points= []
56-
facet_indices:List[Tuple[int, int, int]] = []
59+
polyhedron_pts: Points = []
60+
facet_indices: List[Tuple[int, int, int]] = []
5761

5862
# Make sure we've got Euclid Point3's for all elements
5963
shape_pts = euclidify(shape_pts, Point3)
@@ -66,7 +70,7 @@ def transform_func(p:Point3, path_norm:float, loop_norm:float): Point3
6670
tangent_path_points: List[Point3] = []
6771

6872
# If first & last points are the same, let's close the shape
69-
first_last_equal = ((path_pts[0] - path_pts[-1]).magnitude_squared() < EPSILON)
73+
first_last_equal = (path_pts[0] - path_pts[-1]).magnitude_squared() < EPSILON
7074
if first_last_equal:
7175
connect_ends = True
7276
path_pts = path_pts[:][:-1]
@@ -77,33 +81,68 @@ def transform_func(p:Point3, path_norm:float, loop_norm:float): Point3
7781
first = Point3(*(path_pts[0] - (path_pts[1] - path_pts[0])))
7882
last = Point3(*(path_pts[-1] - (path_pts[-2] - path_pts[-1])))
7983
tangent_path_points = [first] + path_pts + [last]
80-
tangents = [tangent_path_points[i+2] - tangent_path_points[i] for i in range(len(path_pts))]
84+
tangents = [
85+
tangent_path_points[i + 2] - tangent_path_points[i]
86+
for i in range(len(path_pts))
87+
]
8188

8289
for which_loop in range(len(path_pts)):
8390
# path_normal is 0 at the first path_pts and 1 at the last
84-
path_normal = which_loop/ (len(path_pts) - 1)
91+
path_normal = which_loop / (len(path_pts) - 1)
8592

8693
path_pt = path_pts[which_loop]
8794
tangent = tangents[which_loop]
8895
scale = scales[which_loop] if scales else 1
8996

9097
rotate_degrees = None
9198
if rotations:
92-
rotate_degrees = rotations[which_loop] if len(rotations) > 1 else rotations[0] * path_normal
99+
rotate_degrees = (
100+
rotations[which_loop]
101+
if len(rotations) > 1
102+
else rotations[0] * path_normal
103+
)
93104

94105
transform_func = None
95106
if transforms:
96-
transform_func = transforms[which_loop] if len(transforms) > 1 else transforms[0]
107+
transform_func = (
108+
transforms[which_loop] if len(transforms) > 1 else transforms[0]
109+
)
110+
# Default to no *args or **kwargs
111+
this_transform_func_args = None
112+
if transform_args:
113+
this_transform_func_args = (
114+
transform_args[which_loop]
115+
if len(transform_args) > 1
116+
else transform_args[0]
117+
)
118+
this_transform_func_kwargs = None
119+
if transform_kwargs:
120+
this_transform_func_kwargs = (
121+
transform_kwargs[which_loop]
122+
if len(transform_kwargs) > 1
123+
else transform_kwargs[0]
124+
)
97125

98126
this_loop = shape_pts[:]
99-
this_loop = _scale_loop(this_loop, scale)
127+
this_loop = _scale_loop(
128+
this_loop,
129+
scale,
130+
)
100131
this_loop = _rotate_loop(this_loop, rotate_degrees)
101-
this_loop = _transform_loop(this_loop, transform_func, path_normal)
102-
103-
this_loop = transform_to_point(this_loop, dest_point=path_pt, dest_normal=tangent, src_up=src_up)
132+
this_loop = _transform_loop(
133+
this_loop,
134+
transform_func,
135+
path_normal,
136+
this_transform_func_args,
137+
this_transform_func_kwargs,
138+
)
139+
140+
this_loop = transform_to_point(
141+
this_loop, dest_point=path_pt, dest_normal=tangent, src_up=src_up
142+
)
104143
loop_start_index = which_loop * shape_pt_count
105144

106-
if (which_loop < len(path_pts) - 1):
145+
if which_loop < len(path_pts) - 1:
107146
loop_facets = _loop_facet_indices(loop_start_index, shape_pt_count)
108147
facet_indices += loop_facets
109148

@@ -117,54 +156,76 @@ def transform_func(p:Point3, path_norm:float, loop_norm:float): Point3
117156

118157
elif cap_ends:
119158
# OpenSCAD's polyhedron will automatically triangulate faces as needed.
120-
# So just include all points at each end of the tube
121-
last_loop_start_index = len(polyhedron_pts) - shape_pt_count
159+
# So just include all points at each end of the tube
160+
last_loop_start_index = len(polyhedron_pts) - shape_pt_count
122161
start_loop_indices = list(reversed(range(shape_pt_count)))
123-
end_loop_indices = list(range(last_loop_start_index, last_loop_start_index + shape_pt_count))
162+
end_loop_indices = list(
163+
range(last_loop_start_index, last_loop_start_index + shape_pt_count)
164+
)
124165
facet_indices.append(start_loop_indices)
125166
facet_indices.append(end_loop_indices)
126167

127-
return polyhedron(points=euc_to_arr(polyhedron_pts), faces=facet_indices) # type: ignore
168+
return polyhedron(points=euc_to_arr(polyhedron_pts), faces=facet_indices) # type: ignore
169+
128170

129-
def _loop_facet_indices(loop_start_index:int, loop_pt_count:int, next_loop_start_index=None) -> List[FacetIndices]:
171+
def _loop_facet_indices(
172+
loop_start_index: int, loop_pt_count: int, next_loop_start_index=None
173+
) -> List[FacetIndices]:
130174
facet_indices: List[FacetIndices] = []
131175
# nlsi == next_loop_start_index
132176
if next_loop_start_index == None:
133177
next_loop_start_index = loop_start_index + loop_pt_count
134-
loop_indices = list(range(loop_start_index, loop_pt_count + loop_start_index)) + [loop_start_index]
135-
next_loop_indices = list(range(next_loop_start_index, loop_pt_count + next_loop_start_index )) + [next_loop_start_index]
178+
loop_indices = list(range(loop_start_index, loop_pt_count + loop_start_index)) + [
179+
loop_start_index
180+
]
181+
next_loop_indices = list(
182+
range(next_loop_start_index, loop_pt_count + next_loop_start_index)
183+
) + [next_loop_start_index]
136184

137185
for i, (a, b) in enumerate(zip(loop_indices[:-1], loop_indices[1:])):
138-
c, d = next_loop_indices[i: i+2]
186+
c, d = next_loop_indices[i : i + 2]
139187
# OpenSCAD's polyhedron will accept quads and do its own triangulation with them,
140-
# so we could just append (a,b,d,c).
188+
# so we could just append (a,b,d,c).
141189
# However, this lets OpenSCAD (Or CGAL?) do its own triangulation, leading
142190
# to some strange outcomes. Prefer to do our own triangulation.
143191
# c--d
144192
# |\ |
145193
# | \|
146-
# a--b
194+
# a--b
147195
# facet_indices.append((a,b,d,c))
148-
facet_indices.append((a,b,c))
149-
facet_indices.append((b,d,c))
196+
facet_indices.append((a, b, c))
197+
facet_indices.append((b, d, c))
150198
return facet_indices
151199

152-
def _rotate_loop(points:Sequence[Point3], rotation_degrees:float=None) -> List[Point3]:
200+
201+
def _rotate_loop(
202+
points: Sequence[Point3], rotation_degrees: float = None
203+
) -> List[Point3]:
153204
if rotation_degrees is None:
154205
return points
155-
up = Vector3(0,0,1)
206+
up = Vector3(0, 0, 1)
156207
rads = radians(rotation_degrees)
157208
return [p.rotate_around(up, rads) for p in points]
158209

159-
def _scale_loop(points:Sequence[Point3], scale:Union[float, Point2, Tuple2]=None) -> List[Point3]:
210+
211+
def _scale_loop(
212+
points: Sequence[Point3], scale: Union[float, Point2, Tuple2] = None
213+
) -> List[Point3]:
160214
if scale is None:
161215
return points
162216

163217
if isinstance(scale, (float, int)):
164218
scale = [scale] * 2
165219
return [Point3(point.x * scale[0], point.y * scale[1], point.z) for point in points]
166220

167-
def _transform_loop(points:Sequence[Point3], transform_func:Point3Transform = None, path_normal:float = None) -> List[Point3]:
221+
222+
def _transform_loop(
223+
points: Sequence[Point3],
224+
transform_func: Callable = None,
225+
path_normal: float = None,
226+
*args,
227+
**kwargs,
228+
) -> List[Point3]:
168229
# transform_func is a function that takes a point and optionally two floats,
169230
# a `path_normal`, in [0,1] that indicates where this loop is in a path extrusion,
170231
# and `loop_normal` in [0,1] that indicates where this point is in a list of points
@@ -174,8 +235,9 @@ def _transform_loop(points:Sequence[Point3], transform_func:Point3Transform = No
174235
result = []
175236
for i, p in enumerate(points):
176237
# i goes from 0 to 1 across points
177-
loop_normal = i/(len(points) -1)
178-
new_p = transform_func(p, path_normal, loop_normal)
238+
loop_normal = i / (len(points) - 1)
239+
# print(f"args:\t{args}")
240+
# print(f"kwargs:\t{kwargs}")
241+
new_p = transform_func(p, path_normal, loop_normal, *args, **kwargs)
179242
result.append(new_p)
180243
return result
181-

0 commit comments

Comments
 (0)