@@ -1143,7 +1143,8 @@ def path_2d_polygon(points:Sequence[Point23], width:float=1, closed:bool=False)
11431143# ==========================
11441144def extrude_along_path ( shape_pts :Points ,
11451145 path_pts :Points ,
1146- scale_factors :Sequence [Union [Vector2 , float ]]= None ) -> OpenSCADObject :
1146+ scale_factors :Sequence [Union [Vector2 , float ]]= None ,
1147+ connect_ends = False ) -> OpenSCADObject :
11471148 # Extrude the convex curve defined by shape_pts along path_pts.
11481149 # -- For predictable results, shape_pts must be planar, convex, and lie
11491150 # in the XY plane centered around the origin.
@@ -1161,72 +1162,91 @@ def extrude_along_path( shape_pts:Points,
11611162
11621163 src_up = Vector3 (* UP_VEC )
11631164
1165+ shape_pt_count = len (shape_pts )
1166+
1167+ tangent_path_points : List [Point3 ] = []
1168+ if connect_ends :
1169+ tangent_path_points = [path_pts [- 1 ]] + path_pts + [path_pts [0 ]]
1170+ else :
1171+ first = Point3 (* (path_pts [0 ] - (path_pts [1 ] - path_pts [0 ])))
1172+ last = Point3 (* (path_pts [- 1 ] - (path_pts [- 2 ] - path_pts [- 1 ])))
1173+ tangent_path_points = [first ] + path_pts + [last ]
1174+ tangents = [tangent_path_points [i + 2 ] - tangent_path_points [i ] for i in range (len (path_pts ))]
1175+
11641176 for which_loop in range (len (path_pts )):
11651177 path_pt = path_pts [which_loop ]
1178+ tangent = tangents [which_loop ]
1179+ scale_factor = scale_factors [which_loop ] if scale_factors else 1
1180+ this_loop = shape_pts [:]
1181+ this_loop = _scale_loop (this_loop , scale_factor )
1182+ this_loop = transform_to_point (this_loop , dest_point = path_pt , dest_normal = tangent , src_up = src_up )
1183+ loop_start_index = which_loop * shape_pt_count
1184+
1185+ if (which_loop < len (path_pts ) - 1 ):
1186+ loop_facets = _loop_facet_indices (loop_start_index , shape_pt_count )
1187+ facet_indices += loop_facets
1188+
1189+ # Add the transformed points & facets to our final list
1190+ polyhedron_pts += this_loop
11661191
1167- # calculate the tangent to the curve at this point
1168- if which_loop > 0 and which_loop < len (path_pts ) - 1 :
1169- prev_pt = path_pts [which_loop - 1 ]
1170- next_pt = path_pts [which_loop + 1 ]
1171-
1172- v_prev = path_pt - prev_pt
1173- v_next = next_pt - path_pt
1174- tangent = v_prev + v_next
1175- elif which_loop == 0 :
1176- tangent = path_pts [which_loop + 1 ] - path_pt
1177- elif which_loop == len (path_pts ) - 1 :
1178- tangent = path_pt - path_pts [which_loop - 1 ]
1179-
1180- # Scale points
1181- this_loop = shape_pts [:] # type: ignore
1182- scale_x , scale_y = [1 , 1 ]
1183- if scale_factors :
1184- scale = scale_factors [which_loop ]
1185- if isinstance (scale , (int , float )):
1186- scale_x , scale_y = scale , scale
1187- elif isinstance (scale , Vector2 ):
1188- scale_x , scale_y = scale .x , scale .y
1189- else :
1190- raise ValueError (f'Unable to scale shape_pts with scale value: { scale } ' )
1191- this_loop = [Point3 (v .x * scale_x , v .y * scale_y , v .z ) for v in this_loop ]
1192+ if connect_ends :
1193+ next_loop_start_index = len (polyhedron_pts ) - shape_pt_count
1194+ loop_facets = _loop_facet_indices (0 , shape_pt_count , next_loop_start_index )
1195+ facet_indices += loop_facets
11921196
1193- # Rotate & translate
1194- this_loop = transform_to_point (this_loop , dest_point = path_pt ,
1195- dest_normal = tangent , src_up = src_up )
1197+ else :
1198+ # endcaps at start & end of extrusion
1199+ # NOTE: this block adds points & indices to the polyhedron, so it's
1200+ # very sensitive to the order this is happening in
1201+ start_cap_index = len (polyhedron_pts )
1202+ end_cap_index = start_cap_index + 1
1203+ last_loop_start_index = len (polyhedron_pts ) - shape_pt_count
11961204
1197- # Add the transformed points to our final list
1198- polyhedron_pts += this_loop
1199- # And calculate the facet indices
1200- shape_pt_count = len (shape_pts )
1201- segment_start = which_loop * shape_pt_count
1202- segment_end = segment_start + shape_pt_count - 1
1203- if which_loop < len (path_pts ) - 1 :
1204- for i in range (segment_start , segment_end ):
1205- facet_indices .append ( (i , i + shape_pt_count , i + 1 ) )
1206- facet_indices .append ( (i + 1 , i + shape_pt_count , i + shape_pt_count + 1 ) )
1207- facet_indices .append ( (segment_start , segment_end , segment_end + shape_pt_count ) )
1208- facet_indices .append ( (segment_start , segment_end + shape_pt_count , segment_start + shape_pt_count ) )
1209-
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
1205+ start_loop_pts = polyhedron_pts [:shape_pt_count ]
1206+ end_loop_pts = polyhedron_pts [last_loop_start_index :]
1207+
1208+ start_loop_indices = list (range (0 , shape_pt_count ))
1209+ end_loop_indices = list (range (last_loop_start_index , last_loop_start_index + shape_pt_count ))
1210+
1211+ start_centroid , start_facet_indices = _end_cap (start_cap_index , start_loop_pts , start_loop_indices )
1212+ end_centroid , end_facet_indices = _end_cap (end_cap_index , end_loop_pts , end_loop_indices )
1213+ polyhedron_pts += [start_centroid , end_centroid ]
1214+ facet_indices += start_facet_indices
1215+ facet_indices += end_facet_indices
12261216
12271217 return polyhedron (points = euc_to_arr (polyhedron_pts ), faces = facet_indices ) # type: ignore
12281218
1229- def end_cap (new_point_index :int , points :Sequence [Point3 ], vertex_indices : Sequence [int ]) -> Tuple [Point3 , List [FacetIndices ]]:
1219+ def _loop_facet_indices (loop_start_index :int , loop_pt_count :int , next_loop_start_index = None ) -> List [FacetIndices ]:
1220+ facet_indices : List [FacetIndices ] = []
1221+ # nlsi == next_loop_start_index
1222+ if next_loop_start_index == None :
1223+ next_loop_start_index = loop_start_index + loop_pt_count
1224+ loop_indices = list (range (loop_start_index , loop_pt_count + loop_start_index )) + [loop_start_index ]
1225+ next_loop_indices = list (range (next_loop_start_index , loop_pt_count + next_loop_start_index )) + [next_loop_start_index ]
1226+
1227+ for i , (a , b ) in enumerate (zip (loop_indices [:- 1 ], loop_indices [1 :])):
1228+ # c--d
1229+ # |\ |
1230+ # | \|
1231+ # a--b
1232+ c , d = next_loop_indices [i : i + 2 ]
1233+ facet_indices .append ((a ,c ,b ))
1234+ facet_indices .append ((b ,c ,d ))
1235+ return facet_indices
1236+
1237+ def _scale_loop (points :Sequence [Point3 ], scale_factor :Union [float , Point2 ]= None ) -> List [Point3 ]:
1238+ scale_x , scale_y = [1 , 1 ]
1239+ if scale_factor :
1240+ if isinstance (scale_factor , (int , float )):
1241+ scale_x , scale_y = scale_factor , scale_factor
1242+ elif isinstance (scale_factor , Vector2 ):
1243+ scale_x , scale_y = scale_factor .x , scale_factor .y
1244+ else :
1245+ raise ValueError (f'Unable to scale shape_pts with scale_factor: { scale_factor } ' )
1246+ this_loop = [Point3 (v .x * scale_x , v .y * scale_y , v .z ) for v in points ]
1247+ return this_loop
1248+
1249+ def _end_cap (new_point_index :int , points :Sequence [Point3 ], vertex_indices : Sequence [int ]) -> Tuple [Point3 , List [FacetIndices ]]:
12301250 # Assume points are a basically planar, basically convex polygon with polyhedron
12311251 # indices `vertex_indices`.
12321252 # Return a new point that is the centroid of the polygon and a list of
0 commit comments