Skip to content

Commit cfb778f

Browse files
useruser
user
authored and
user
committed
Add plot centeral line function
1 parent 2a05884 commit cfb778f

File tree

2 files changed

+221
-23
lines changed

2 files changed

+221
-23
lines changed

d3dslic3r/d3dslic3r_common.py

+142-12
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,95 @@ def get_polydata_from_stl(fname):
154154

155155
return polydata
156156

157+
def get_skeleton_dict(outline,whole_angle,step_size,suggested_hatching_offset):
158+
"""
159+
Returns a dictionary of skeleton lines for each hatch angle
160+
161+
skeleton_dict structure:
162+
{
163+
hatch_angle1: {
164+
skeleton1: [midpoint1, midpoint2, ...]
165+
skeleton2: [midpoint1, midpoint2, ...]
166+
}
167+
hatch_angle2: {
168+
skeleton1: [midpoint1, midpoint2, ...]
169+
skeleton2: [midpoint1, midpoint2, ...]
170+
}
171+
}
172+
"""
173+
174+
midpoint_line_dict = {hatch_angle: [] for hatch_angle in np.arange(0, whole_angle, step_size)}
175+
for hatch_angle in np.arange(0, whole_angle, step_size):
176+
intersecting_lines, _, num_of_line_list = get_intersections(outline,hatch_angle,
177+
suggested_hatching_offset,0.5)
178+
179+
# Get all the unique values in num_of_line_list
180+
lines_index_list = list(set(num_of_line_list))
181+
midpoint_line_dict[hatch_angle] = {line_index: [] for line_index in lines_index_list}
182+
183+
for line_index, number_of_line in enumerate(num_of_line_list):
184+
if number_of_line in lines_index_list:
185+
midpoint = (intersecting_lines[line_index][0] + intersecting_lines[line_index][1]) / 2
186+
midpoint_line_dict[hatch_angle][number_of_line].append(midpoint)
187+
return midpoint_line_dict
188+
189+
def get_central_line_path(skeleton_dict, entry):
190+
"""
191+
Returns a list of central line paths for each entry
192+
"""
193+
certral_line_path = []
194+
for rotate_angle_line1, path_index_line1 in skeleton_dict.items():
195+
for skeleton_line1 in path_index_line1.values():
196+
if len(skeleton_line1) > 1:
197+
for line_index1 in range(len(skeleton_line1) - 1):
198+
P1 = skeleton_line1[line_index1][:2]
199+
P2 = skeleton_line1[line_index1 + 1][:2]
200+
line1 = [P1, P2]
201+
for rotate_angle_line2, path_index_line2 in skeleton_dict.items():
202+
if rotate_angle_line2 != rotate_angle_line1:
203+
for skeleton_line2 in path_index_line2.values():
204+
if len(skeleton_line2) > 1:
205+
for line_index2 in range(len(skeleton_line2) - 1):
206+
P3 = skeleton_line2[line_index2][:2]
207+
P4 = skeleton_line2[line_index2 + 1][:2]
208+
line2 = [P3, P4]
209+
local_intersection = line_intersection(line1, line2)
210+
if local_intersection is not None:
211+
certral_line_path.append(np.array([local_intersection[0],
212+
local_intersection[1],entry]))
213+
return np.array(certral_line_path)
214+
215+
def reorder_points_based_on_intersetion(cleaned_certral_line_path, outline):
216+
"""
217+
Reorder the points in the central line path based on the intersection with the outline
218+
If intersection is found, the points before the intersection will be reversed
219+
if the distance between the first point and the intersection is less than 1,
220+
(this needs to be adjusted based on the actual distance, need to be improved)
221+
the points after the interseciton will also be reversed.
222+
"""
223+
reordered_certral_line_path = []
224+
for line1_index in range(len(cleaned_certral_line_path) - 1):
225+
P1 = cleaned_certral_line_path[line1_index]
226+
P2 = cleaned_certral_line_path[line1_index + 1]
227+
line1 = [P1, P2]
228+
for line2_index in range(len(outline) - 1):
229+
P3 = outline[line2_index]
230+
P4 = outline[line2_index + 1]
231+
line2 = [P3, P4]
232+
local_intersection = line_intersection(line1, line2)
233+
if local_intersection is not None:
234+
distance = np.linalg.norm(np.array(P2) - np.array(cleaned_certral_line_path[0]))
235+
# To be improved
236+
if distance < 1:
237+
reordered_certral_line_path = np.concatenate((cleaned_certral_line_path[:line1_index+1][::-1],
238+
cleaned_certral_line_path[line1_index+1:]))
239+
return reordered_certral_line_path
240+
else:
241+
reordered_certral_line_path = np.concatenate((cleaned_certral_line_path[:line1_index+1][::-1],
242+
cleaned_certral_line_path[line1_index+1:][::-1]))
243+
return reordered_certral_line_path
244+
return reordered_certral_line_path
245+
157246
def actor_from_polydata(polydata):
158247
"""
159248
Wrap the provided vtkPolyData object in a mapper and an actor, returning
@@ -170,6 +259,24 @@ def actor_from_polydata(polydata):
170259

171260
return stl_actor
172261

262+
def get_starting_point(inp):
263+
"""
264+
get the starting point of the outline based on the distance between points
265+
if the distance between two points is the largest, the starting point is the first point
266+
"""
267+
268+
points = inp[:,0:2]
269+
dist = []
270+
for i in range(len(points)-1):
271+
distance = np.linalg.norm(points[i+1] - points[i])
272+
dist.append(distance)
273+
274+
mean = np.mean(dist)
275+
std = np.std(dist)
276+
z_scores = [(x - mean) / std for x in dist]
277+
outliers = np.argmax(z_scores)
278+
return outliers
279+
173280
def sort_ccw(inp):
174281
"""
175282
Sorts in 2D x,y pairs according to ccw position from the centroid.
@@ -319,31 +426,54 @@ def get_intersections(outline, angular_offset, width, bead_offset = 0.5):
319426
#break up on the basis of bead width
320427
num_passes = int(np.floor((limits[1]-(width*bead_offset) - limits[0]+(width*bead_offset))/(width*bead_offset)))
321428
xrange = np.linspace(limits[0]+(width*bead_offset),limits[1]-(width*bead_offset),num_passes)
322-
323-
actual_bead_offset = (xrange[1]-xrange[0])/width
429+
430+
if len(xrange) < 2:
431+
actual_bead_offset = 0
432+
else:
433+
actual_bead_offset = (xrange[1]-xrange[0])/width
324434

325435
intersections = []
436+
point_list = []
437+
previous_max_point_num = 0
438+
mode_change_times = 0
326439
for k in range(len(xrange)):
327440
line = np.array([[xrange[k],yrange[0]], [xrange[k],yrange[1]]])
328441
line1 = tuple([tuple(x) for x in line.tolist()])
329442

443+
point_index = 0
444+
330445
for i in range(len(X)-1):
331446
line2 = tuple([tuple(x) for x in X[i:i+2,:]])
332447
local_intersection = line_intersection(line1, line2)
333448
if local_intersection is not None:
449+
point_index += 1
450+
334451
intersections.append(np.array([local_intersection[0],local_intersection[1],zval]))
452+
if point_index != previous_max_point_num:
453+
previous_max_point_num = point_index
454+
mode_change_times += 1
455+
temp_list = [x + mode_change_times*100 for x in range(point_index)]
456+
temp_list = [str(x) for x in temp_list]
457+
point_list.extend(temp_list)
458+
335459
#Needs to be 3D for transformation
336460
intersections = np.asarray(intersections)
337-
intersections = intersections[np.lexsort((intersections[:, 1], intersections[:, 0]))]
338-
trans_intersections = do_transform(intersections,np.linalg.inv(trans))
339-
trans_intersections = do_transform(trans_intersections,np.linalg.inv(trans_cent))
461+
if len(intersections) > 1:
462+
intersections = intersections[np.lexsort((intersections[:, 1], intersections[:, 0]))]
463+
trans_intersections = do_transform(intersections,np.linalg.inv(trans))
464+
trans_intersections = do_transform(trans_intersections,np.linalg.inv(trans_cent))
340465

341-
#pack up/pair intersections for line paths
342-
path_list = []
343-
for i in np.arange(len(trans_intersections)-1)[::2]:
344-
path_list.append(np.array([trans_intersections[i,:], trans_intersections[i+1,:]]))
466+
#pack up/pair intersections for line paths
467+
path_list = []
468+
line_index_list = []
469+
for i in np.arange(len(trans_intersections)-1)[::2]:
470+
path_list.append(np.array([trans_intersections[i,:], trans_intersections[i+1,:]]))
471+
line_index_list.append(point_list[i])
472+
else:
473+
path_list = []
474+
line_index_list = []
345475
#return intersections , actual bead offset
346-
return path_list, actual_bead_offset
476+
return path_list, actual_bead_offset, line_index_list
347477

348478
def line_intersection(line1, line2):
349479
'''
@@ -370,9 +500,9 @@ def line_intersection(line1, line2):
370500

371501
if det == 0.0: # lines are parallel
372502

373-
if line1[0] == line2[0] or line1[1] == line2[0]:
503+
if np.array_equal(line1[0], line2[0]) or np.array_equal(line1[1], line2[0]):
374504
return line2[0]
375-
elif line1[0] == line2[1] or line1[1] == line2[1]:
505+
elif np.array_equal(line1[0], line2[1]) or np.array_equal(line1[1], line2[1]):
376506
return line2[1]
377507

378508
#Special cases

d3dslic3r/slic3_widget.py

+79-11
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ def setup(self, MainWindow):
178178
self.path_all_cb.setToolTip('Apply to all slices')
179179
self.path_outline_cb = QtWidgets.QCheckBox('Path outline')
180180
self.path_outline_cb.setToolTip('Include outline in pathing')
181+
self.path_central_line_cb = QtWidgets.QCheckBox('Path central line')
182+
self.path_central_line_cb.setToolTip('Get the central line of the outline')
181183
self.rotate_z_path_sb = QtWidgets.QDoubleSpinBox()
182184
self.rotate_z_path_sb.setToolTip('Rotational offset for pathing of slice. Positive is clockwise.')
183185
self.rotate_z_path_sb.setSingleStep(5.00)
@@ -209,11 +211,12 @@ def setup(self, MainWindow):
209211
#populate pathing layout
210212
path_gen_layout.addWidget(self.path_all_cb,0,0,1,1)
211213
path_gen_layout.addWidget(self.path_outline_cb,0,1,1,1)
212-
path_gen_layout.addWidget(self.rotate_z_path_sb,0,2,1,1)
213-
path_gen_layout.addWidget(self.by_width_sb,0,3,1,1)
214-
path_gen_layout.addWidget(self.bead_offset_sb,0,4,1,1)
214+
path_gen_layout.addWidget(self.path_central_line_cb,0,2,1,1)
215+
path_gen_layout.addWidget(self.rotate_z_path_sb,0,3,1,1)
216+
path_gen_layout.addWidget(self.by_width_sb,0,4,1,1)
217+
path_gen_layout.addWidget(self.bead_offset_sb,0,5,1,1)
215218
path_gen_layout.addWidget(self.num_paths_label,1,0,1,2)
216-
path_gen_layout.addWidget(self.activate_path_pb,1,4,1,1)
219+
path_gen_layout.addWidget(self.activate_path_pb,1,5,1,1)
217220

218221
self.path_box.setEnabled(False)
219222

@@ -661,6 +664,8 @@ def draw_slice(self):
661664
self.slice_data[entry] = slice_obj(self.outlines[entry])
662665

663666
outline = self.slice_data[entry].outline
667+
central_line = self.slice_data[entry].central_line
668+
intermediate_line = self.slice_data[entry].intermediate_line
664669

665670
self.current_outline_actor = gen_outline_actor(outline, (1,0,0), 4)
666671
self.outline_caption_actor = gen_caption_actor('%s'%entry, self.current_outline_actor, (1,0,0))
@@ -674,6 +679,15 @@ def draw_slice(self):
674679

675680
ax = self.ui.figure.add_subplot(111)
676681
ax.plot(outline[:,0], outline[:,1], 'k-')
682+
if intermediate_line != []:
683+
for line in intermediate_line:
684+
x_coords = [point[0] for point in line]
685+
y_coords = [point[1] for point in line]
686+
ax.plot(x_coords, y_coords, 'g-')
687+
# # plot the points
688+
# ax.plot(x_coords, y_coords, 'ro')
689+
if len(central_line) != 0:
690+
ax.plot(central_line[:,0], central_line[:,1], 'r-')
677691
ax.set_ylabel("y (mm)")
678692
ax.set_xlabel("x (mm)")
679693
ax.grid(visible=True, which='major', color='#666666', linestyle='-', alpha=0.1)
@@ -734,14 +748,25 @@ def make_paths(self):
734748
interior = offset_poly(outline,-self.ui.by_width_sb.value())
735749
midline = offset_poly(outline,-self.ui.by_width_sb.value()*0.5)
736750
innie = offset_poly(outline,-self.ui.by_width_sb.value())
737-
intersecting_lines, param = get_intersections(innie,theta,self.ui.by_width_sb.value(),offset)
751+
intersecting_lines, param, _ = get_intersections(
752+
innie,theta,self.ui.by_width_sb.value(),offset)
738753
local_path_collection.append(midline)
739754
local_path_collection.extend(intersecting_lines)
755+
self.ui.num_paths_label.setText('N = %i at %0.2f%%'%(len(intersecting_lines),param*100))
756+
# update interactor with result
757+
self.ui.num_paths_label.setText('N = %i at %0.2f%%'%(len(intersecting_lines),param*100))
758+
759+
elif self.ui.path_central_line_cb.isChecked():
760+
ordered_certral_line_path, skeleton_path = self.get_ordered_central_line_path(outline, entry)
761+
self.slice_data[entry].central_line = np.array(ordered_certral_line_path)
762+
self.slice_data[entry].intermediate_line = skeleton_path
763+
740764
else:
741-
intersecting_lines, param = get_intersections(outline,theta,self.ui.by_width_sb.value(),offset)
765+
intersecting_lines, param, _ = get_intersections(
766+
outline,theta,self.ui.by_width_sb.value(),offset)
742767
local_path_collection = intersecting_lines
743768

744-
self.slice_data[entry].paths = local_path_collection #list of paths
769+
self.slice_data[entry].paths = local_path_collection #list of paths
745770

746771
else: #iterate over all entries
747772
for i in range(self.ui.slice_num_cb.count()):
@@ -754,20 +779,61 @@ def make_paths(self):
754779
interior = offset_poly(outline,-self.ui.by_width_sb.value())
755780
midline = offset_poly(outline,-self.ui.by_width_sb.value()*0.5)
756781
innie = offset_poly(outline,-self.ui.by_width_sb.value())
757-
intersecting_lines, param = get_intersections(innie,theta,self.ui.by_width_sb.value(),offset)
782+
intersecting_lines, param, _ = get_intersections(
783+
innie,theta,self.ui.by_width_sb.value(),offset)
758784
local_path_collection.append(midline)
759785
local_path_collection.extend(intersecting_lines)
786+
# update interactor with result
787+
self.ui.num_paths_label.setText('N = %i at %0.2f%%'%(len(intersecting_lines),param*100))
788+
789+
elif self.ui.path_central_line_cb.isChecked():
790+
ordered_certral_line_path, skeleton_path = self.get_ordered_central_line_path(outline, entry)
791+
self.slice_data[entry].central_line = np.array(ordered_certral_line_path)
792+
self.slice_data[entry].intermediate_line = skeleton_path
793+
760794
else:
761-
intersecting_lines, param = get_intersections(outline,theta,self.ui.by_width_sb.value(),offset)
795+
intersecting_lines, param, _ = get_intersections(
796+
outline,theta,self.ui.by_width_sb.value(),offset)
762797
local_path_collection = intersecting_lines
763798

764799
self.slice_data[entry].paths = local_path_collection #list of paths
765800

766-
# update interactor with result
767-
self.ui.num_paths_label.setText('N = %i at %0.2f%%'%(len(intersecting_lines),param*100))
801+
768802
self.ui.export_slice.setEnabled(True)
769803
self.draw_slice() #to clear any existing intersections
770804

805+
def get_ordered_central_line_path(self, outline, entry):
806+
step_size = 30.01
807+
limits = get_limits(outline,0)
808+
suggested_hatching_offset = min((limits[1]-limits[0]),(limits[3]-limits[2]))/15
809+
whole_angle = 180
810+
skeleton_dict = get_skeleton_dict(outline,whole_angle,step_size,
811+
suggested_hatching_offset)
812+
813+
certral_line_path = get_central_line_path(skeleton_dict, entry)
814+
ordered_certral_line_path = order_points_in_loop(certral_line_path)
815+
starting_point_index = get_starting_point(ordered_certral_line_path)
816+
ordered_certral_line_path = np.concatenate((ordered_certral_line_path[starting_point_index:],
817+
ordered_certral_line_path[:starting_point_index]), axis=0)
818+
ordered_certral_line_path = self.remove_repeated_points_preserve_order(ordered_certral_line_path)
819+
ordered_certral_line_path = order_points_in_loop(ordered_certral_line_path)[:-1]
820+
# get the skeleton of the outline
821+
skeleton = []
822+
for key in skeleton_dict.keys():
823+
for value in skeleton_dict[key].values():
824+
skeleton.append(value)
825+
return ordered_certral_line_path, skeleton
826+
827+
def remove_repeated_points_preserve_order(self, points):
828+
seen = set()
829+
unique_points = []
830+
for point in points:
831+
point_tuple = tuple(point)
832+
if point_tuple not in seen:
833+
seen.add(point_tuple)
834+
unique_points.append(point)
835+
return np.array(unique_points)
836+
771837
def draw_paths(self):
772838

773839
#get active outline
@@ -797,6 +863,8 @@ def __init__(self, outline):
797863
self.outline = outline
798864
self.alpha = None
799865
self.paths = None
866+
self.central_line = []
867+
self.intermediate_line = []
800868

801869
if __name__ == "__main__":
802870
launch()

0 commit comments

Comments
 (0)