-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSlicerMain.py
1608 lines (1235 loc) · 59.7 KB
/
SlicerMain.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import numpy as np # Used throughout for arrays
from stl import mesh # Used only to import mesh data
import sys # Used to increase recursive limit
import vg # Used only to determine adjacency
from copy import deepcopy # Used to create layers
from geomdl import BSpline # Used to create layers and tool paths?
from geomdl import utilities # Used to create layers and tool paths?
from geomdl import tessellate # Used to tessellate the evaluated surfaces from set control points
import math # Used to calculate angles between 2 lines
# TODO: Remove these imports when code is finished; used only for testing
import time # Used for timing the code
from matplotlib import pyplot # Used to plot the facets to confirm algorithms work
from mpl_toolkits import mplot3d # Used to plot the facets to confirm algorithms work
EDGE_ANGLE = 80 # The value that determines if a facet is part of a side surface; units degrees
PERMITTED_LAYER_HEIGHTS = [.1, .2, .3, .4, .5] # Available layer height values; units mm
NURBS_DEGREES = 4 # The degree value used to calculate the NURBS surface in layer generation
MAX_CTRL_PTS = 5 # The maximum distance between control points; units mm
# TODO: Remove this variable when code is finished; only to save time in testing
LAYER_HEIGHT = .1 # Layer height for slices; unit mm
# This class offers more flexibility in accessing facet data such as specific sides and adjacency information
class Facet:
def __init__(self, facet, normal):
self.facet = facet
self.normal = normal
self.edge = []
self.corner = None
self.adjacent = {'01': None, '02': None, '12': None}
self.sides = {'01': facet[:2], '02': np.array([facet[0], facet[-1]]), '12': facet[1:3]}
def set_adjacency(self, key, value):
if key[0] > key[1]:
temp = list(key)
temp[0], temp[1] = temp[1], temp[0]
key = ''.join(temp)
self.adjacent[key] = value
def get_adjacency(self, key=None):
if key is None:
return self.adjacent.values()
else:
return self.adjacent[key]
def set_edge(self, value):
self.edge.append(value)
def set_corner(self, value):
self.corner = value
# This class offers more flexibility in accessing line information
class Edge:
def __init__(self, side, f_index):
self.side = side
self.f_index = f_index
self.c_index = None
self.corner = False
self.adjacent = {0: None, 1: None}
self.angles = {0: None, 1: None}
self.relation = {0: None, 1: None}
def set_adjacency(self, key, value):
self.adjacent[key] = value
def get_adjacency(self, key=None):
if key is None:
return self.adjacent.values()
else:
return self.adjacent[key]
def set_angles(self, key, value):
self.angles[key] = value
def set_relations(self, key, value):
self.relation[key] = value
if value == 'corner':
self.corner = True
def get_relation(self):
if self.relation[0] == 'straight' and self.relation[1] == 'straight':
return 'straight'
elif self.corner:
return 'corner'
else:
return 'curved'
def main():
sys.setrecursionlimit(1500)
is_sorted_dict, facets, stl_mesh = initialize()
slice_the_model(is_sorted_dict, facets, stl_mesh)
"""
Simplest decomposition for a 5-axis slicer:
generate_tool_paths()
generate_gcode()
Export G-code
"""
def graph_layer(stl_mesh, layer):
"""
Graphs a list of facets into a 3D space. Initial idea was to just show a single layer with this function,
but can be used with any single list of facets.
:param stl_mesh: Mesh data from STL file
:param layer: A list of facets to be graphed
:return:
"""
# Create a new plot
figure = pyplot.figure()
axes = mplot3d.Axes3D(figure)
# Load the STL files and add the vectors to the plot
axes.add_collection3d(mplot3d.art3d.Poly3DCollection(layer))
# Auto scale to the mesh size
scale = stl_mesh.points.flatten('F')
axes.auto_scale_xyz(scale, scale, scale)
# Show the plot to the screen
pyplot.show()
def graph_edge(stl_mesh, edge):
"""
Graphs a list of lines into a 3D space to show proper placement of toolpaths.
:param stl_mesh: Mesh data from STL file
:param edge: A list of lines to be graphed
:return:
"""
# Create a new plot
figure = pyplot.figure()
axes = mplot3d.Axes3D(figure)
# Load the STL files and add the vectors to the plot
axes.add_collection3d(mplot3d.art3d.Line3DCollection(edge))
# Auto scale to the mesh size
scale = stl_mesh.points.flatten('F')
axes.auto_scale_xyz(scale, scale, scale)
# Show the plot to the screen
pyplot.show()
def graph_layers(stl_mesh, layers):
"""
Graphs a list of a list of facets into a 3D space. This similar to the graph_layer function,
except that it is designed to graph multiple layers as opposed to one. This requires the list
to be passed in to be populated with lists of facets, like how the graph_layer function was
intended to be used.
:param stl_mesh: Mesh data from STL file
:param layers: A list of facets to be graphed
:return:
"""
# Create a new plot
figure = pyplot.figure()
# axes = figure.add_subplot(1, 1, 1, projection='3d')
axes = mplot3d.Axes3D(figure)
color = ['b', 'r', 'g', 'm', 'c', 'y']
# Load the STL files and add the vectors to the plot
for clr, layer in enumerate(layers):
axes.add_collection3d(mplot3d.art3d.Poly3DCollection(layer))
# axes.add_collection3d(mplot3d.art3d.Poly3DCollection(layer, facecolor=color[clr]))
"""axes = figure.add_subplot(1, 1, 2, projection='3d')
axes.add_collection3d(mplot3d.art3d.Poly3DCollection(stl_mesh.vectors))"""
# Auto scale to the mesh size
scale = stl_mesh.points.flatten('F')
axes.auto_scale_xyz(scale, scale, scale)
# Show the plot to the screen
pyplot.show()
def read_stl():
"""
This function reads in the data from an stl file of the same name inputted by the user.
It is important that the file to be read in is already within the SlicerMain folder
before running the program.
:return: the mesh of the uploaded stl file
"""
# TODO: Reinstate lines below once program is finished
"""
while True:
# Tries to open a file, based on a value given by the user
try:
file_name = input("Please enter the name of the stl file you wish to open with the file extension: ")
stl_mesh = mesh.Mesh.from_file(file_name)
# If no such file is found, exception is thrown and loop restarts
except FileNotFoundError:
print("Sorry, no such file was found.")
print("Please check that you entered the name correctly and/or added .STL at the end")
# better try again... Return to the start of the loop
continue
else:
# Got the correct format
# we're ready to exit the loop.
break
"""
stl_mesh = mesh.Mesh.from_file('Plane1.STL')
return stl_mesh
def get_layer_height():
"""
Asks the user for their desired layer height for their free form surface to be printed with.
If value given is not of the type float or in the permitted value list, an exception is
thrown and user is asked again.
:return: The layer height value given by the user
"""
while True:
# Tries to get a float value from user
try:
layer_height = float(input("Please enter your desired layer height in mm: "))
# If value is not a float, then exception is thrown and loop restarts
except ValueError:
print("Sorry, I didn't understand that.")
continue
# If value is a float, but one of the accepted values, then user is informed of allowed values and loop restarts
if layer_height not in PERMITTED_LAYER_HEIGHTS:
print("That is not an accepted layer height. Accepted heights are:")
print(*PERMITTED_LAYER_HEIGHTS, sep=', ')
continue
else:
# layer height was successfully parsed, and we're happy with its value.
# we're ready to exit the loop.
break
return layer_height
def initialize():
"""
Initializes the various objects used throughout the program, while also saving some of the memory space.
:return:
"""
stl_mesh = read_stl()
# TODO: Reinstate later, only to save time in testing
# layer_height = get_layer_height()
is_sorted_dict = make_dict(stl_mesh)
facets = [] # A list that holds all the objects of type Facet
# Populates list facets with all the facets and their relevant data
for i, f1 in enumerate(stl_mesh.vectors):
facets.append(Facet(f1, stl_mesh.normals[i]))
# TODO: Add layer_height to the return list and remove stl_mesh
return is_sorted_dict, facets, stl_mesh
def get_curves(is_sorted_dict):
curved_surfaces = []
print("Separating curved surfaces from side surfaces.")
sort_via_dict(is_sorted_dict, curved_surfaces)
print("Separation complete.")
return curved_surfaces
def slice_the_model(is_sorted_dict, facets, stl_mesh):
"""
Slices the STL mesh into curved layers.
:param is_sorted_dict: A dictionary that keeps track of whether the facets have been sorted
:param facets: A list with facet metadata
:return: Layer slices
"""
side_surfaces = detect_side_surfaces(is_sorted_dict, facets)
curved_surfaces = get_curves(is_sorted_dict)
set_edges(facets, curved_surfaces, side_surfaces)
top_layer, bottom_layer = separate_layers(curved_surfaces, facets, is_sorted_dict)
layer_slices = create_layers(top_layer, bottom_layer, facets, side_surfaces, stl_mesh)
return layer_slices
def detect_side_surfaces(is_sorted_dict, facets):
"""
From the STL mesh, determines which facet vectors make up the
4 side surfaces and which make up the 2 curved surfaces.
:param is_sorted_dict: A dictionary that keeps track of whether the facets have been sorted
:param facets: A list with facet metadata
:return: the list of objects type Facet
"""
side_surfaces = [] # List that will hold all side surface facets
# Another list that was used to extract data and improve global variable values
normals_angle = []
# Print statement to let the user know program is running
print("Detecting side surfaces. Please wait.")
for i in range(0, len(facets)):
f1 = facets[i].facet # Holds the facet data at the given index
# Nested for loop is intentionally made to start one after the outer for loop
# to save time by not checking earlier facets
for j in range(i + 1, len(facets)):
f2 = facets[j].facet # Holds the facet data at the given index
# Prevents duplicate bug
if (f1 == f2).all():
continue
# Skips checking adjacency if all sides have had adjacency set
if all(facets[i].get_adjacency()):
break
is_adjacent, side_str1, side_str2 = check_adjacency(f1, f2)
if is_adjacent:
facets[i].set_adjacency(side_str1, j)
facets[j].set_adjacency(side_str2, i)
# Determines angle between the 2 adjacent normals in units degrees
angle = vg.angle(facets[i].normal, facets[j].normal, units='deg')
normals_angle.append(angle)
# if angle is greater than EDGE_ANGLE, than outer loop facet is part of the side surfaces
# Second conditional prevents duplication bug with corner facets
if angle > EDGE_ANGLE and not is_sorted_dict[i]:
side_surfaces.append(i)
is_sorted_dict[i] = True
print("Side surfaces found.")
"""
Writes the list of angle differences between adjacent normals to a txt file.
This data was used to help determine appropriate values for ZERO and MAX_ANGLE-DIFFERENCE.
with open('angle list.txt', 'w') as filehandle:
for listitem in normals_angle:
filehandle.write('%s\n' % listitem)
"""
return side_surfaces
def check_adjacency(f1, f2):
"""
Determines if two facets are adjacent to each other. Was coded to work with any tesselation shape.
:param f1: first facet
:param f2: second facet
:return:is_adjacent: a bool value, True if adjacent, False if not
side_str1, side_str2: a string that represents which sides
of the facet are adjacent based on index values for facet pts
ie, '01', '02', '12'
"""
count_matching_pts = 0 # Counts how many vertices have matched
is_adjacent = False # A bool representing whether facets are adjacent
side_str1 = "" # A string that represents which side of f1 is adjacent to f2
side_str2 = "" # A string that represents which side of f2 is adjacent to f1
# Nested for loop which compares each point of one facet with each point of another facet, looking for matching pts
for i in range(len(f1)):
for j in range(len(f2)):
# If all values of a point match with another, the counter is incremented by one
# and index values are added to corresponding strings
if (f1[i] == f2[j]).all():
count_matching_pts += 1
side_str1 += str(i)
side_str2 += str(j)
# Once 2 matching points are found, the facets are concluded to be adjacent
if count_matching_pts == 2:
is_adjacent = True
return is_adjacent, side_str1, side_str2
return is_adjacent, side_str1, side_str2
def set_edges(facets, curved_surfaces, side_surfaces):
# Gets the index in layer
for f1 in curved_surfaces:
# Using the index, get the key to the adjacent dictionary associated with that facet
for key in facets[f1].adjacent:
# Gets the index in side_surfaces
for f2 in side_surfaces:
# Check if the adjacent dictionary value is the same as the index value from side_surfaces
if facets[f1].adjacent[key] == f2:
# Sets side data as an edge
facets[f1].set_edge(facets[f1].sides[key])
break
def separate_layers(curved_surfaces, facets, is_sorted_dict):
"""
Separates the top and bottom surfaces into their own lists
:param curved_surfaces: The list of facets that makes up the curved surfaces
:param facets: List containing relevant facet data
:param is_sorted_dict: The dictionary of index keys and boolean values of whether or not it was sorted
:return: Two lists, one containing all the facets for the top surface, and one for the bottom surface
"""
bottom_lyr = [curved_surfaces[-1]] # A list to hold all facets that make up the top most surface of the STL
# Changes the value in the is_sorted_dict to True
is_sorted_dict[curved_surfaces[-1]] = True
print("Separating top layer from bottom layer.")
sort_via_adjacency(curved_surfaces[-1], facets, is_sorted_dict, bottom_lyr)
curved_surfaces = [x for x in curved_surfaces if x not in bottom_lyr]
top_lyr = [curved_surfaces[-1]]
# Changes the value in the is_sorted_dict to True
is_sorted_dict[curved_surfaces[-1]] = True
sort_via_adjacency(curved_surfaces[-1], facets, is_sorted_dict, top_lyr)
curved_surfaces = [x for x in curved_surfaces if x not in top_lyr]
# Using the logic that only the bottom surface facets remain 'unsorted',
# they are now officially sorted into bottom_layer.
# bottom_lyr = [] # A list to hold all facets that make up the bottom most surface of the STL
# sort_via_dict(is_sorted_dict, bottom_lyr, say_sorted=True)
print("Layers have been separated.")
return top_lyr, bottom_lyr
def sort_via_adjacency(top, facets, is_sorted_dict, top_lyr):
"""
A recursive function that takes in a facet index value, among other needed lists and dicts, that checks for
the adjacency values stored. If the value has not been listed as sorted, then it is appended to top_lyr,
its corresponding value in is_sorted_dict is changed to True, and the function is called passing in newly
sorted value as the new facet index value.
:param top: index of facet to get metadata
:param facets: List containing relevant facet data
:param is_sorted_dict: The dictionary of index keys and boolean values of whether or not it was sorted
:param top_lyr: list containing indexes of facets that are apart of the top surface
:return: No return value due to passing in by reference
"""
# Gets the key to the adjacent dictionary associated with facet at index top
for key in facets[top].adjacent:
# Stores dictionary value using key in variable index, which is an index associated with a facet
index = facets[top].get_adjacency(key)
# If facet associated with index has yet to be sorted, add index to top_lyr,
# change is_sorted_dict value to True, and recursively call this function
# again with index as the first given parameter.
if not is_sorted_dict[index]:
top_lyr.append(index)
is_sorted_dict[index] = True
sort_via_adjacency(index, facets, is_sorted_dict, top_lyr)
def make_dict(stl_mesh):
"""
Makes a dictionary in which the keys are the indices of the facets in the
stl mesh and the values are a bool set to False.
:param stl_mesh: Mesh data from STL file
:return: The dictionary of indices
"""
dict_of_ids = {}
for i in range(len(stl_mesh.vectors)):
dict_of_ids[i] = False
return dict_of_ids
def get_top_facet(curved_surface, facets):
"""
Finds the top most facet, in terms of z value, in the stl mesh.
:param facets: List containing relevant facet data
:param curved_surface: A list of sll facets that ar+e apart of the curved surfaces
:return: The top most facet and its index value in the stl mesh
"""
top_facet = np.zeros((3, 3)) # A variable to hold the highest facet in terms of z-value
# Checks only the z values within the facet. When a facet is found
# with at least one z-point higher than the current top_facet,
# that facet becomes the new top_facet
for item in curved_surface:
temp_facet = facets[item].facet
if (temp_facet[0:3, 2] > top_facet[0:3, 2]).any():
top_facet = facets[item].facet
index = item
return top_facet
def sort_via_dict(is_sorted_dict, list_to_sort, say_sorted=False):
"""
Sorts facets based on what has already been sorted according to the is_sorted dictionary.
:param is_sorted_dict: The dictionary of index keys and boolean values of whether or not it was sorted
:param list_to_sort: The list in which the facets will be sorted into
:param say_sorted: A boolean value which says whether to change the dictionary to True during sorting.
Default value is False
:return: No return necessary as dictionaries and lists are mutable objects
"""
# Iterates through all keys in the dictionary
for key in is_sorted_dict:
# If the value attached to that key is False,
# the facet associated with that key is added to the list passed through
if not is_sorted_dict[key]:
list_to_sort.append(key)
# If say_sorted is passed in as True, changes dictionary value to True for current key
if say_sorted:
is_sorted_dict[key] = True
def create_layers(top, bottom, facets, side_surfaces, stl_mesh):
"""
Creates the layers to generate the tool paths onto
:param bottom: list of indexes of all facets comprising of the bottom layer
:param top: list of indexes of all facets comprising of the top layer
:param facets: List containing relevant facet data
:param side_surfaces: list of indexes of all facets comprising of the side surfaces
:return: A list of the layers of the model
"""
layer_slices = [] # List to hold all layers of facets
# layer = []
temp = []
print("Creating layer slices.")
# Calculates the total number of layers to be generated
range_num = calc_num_layers(top, bottom, side_surfaces, facets)
bttm = []
for f in bottom:
bttm.append(Facet(facets[f].facet, facets[f].normal))
temp.append(facets[f].facet)
"""# Gets the edges of layer, then sets the control points
edges = find_edges(facets, bottom, side_surfaces)
control_pts, num_x, num_y = get_control_pts(facets, bottom, edges)
# Evaluates the layer using the control points
surf = evaluate_surface(control_pts)
for face in surf.faces:
vector = []
for vertex in face.vertices:
vert = np.array(vertex)
vector.append(vert)
layer.append(vector)
graph_layer(stl_mesh, layer)"""
# TODO: Fix code so that the new layers made work with the code implemented for already existing surfaces
# Layers are created by going over the total number of layers minus 2 (for the top and bottom)
edges = find_edges_id(facets, bottom, side_surfaces)
control_pts, control_pts_id, num_x, num_y = get_control_pts(facets, bottom, edges)
layer = []
for i in range(range_num - 2):
# First time through uses bottom layer, every other time uses the previous made layer
"""if i == 0:
# layer = create_layer(facets, bottom)
graph_layer(stl_mesh, temp)
layer = create_layer(bttm)
temp.clear()
for f in layer:
temp.append(f.facet)
graph_layer(stl_mesh, temp)
# edges = find_edges_math(facets, layer, side_surfaces)
# graph_edge(stl_mesh, edges)
else:
# layer = create_layer(facets, layer_slices[i-1])
layer = create_layer(layer_slices[i - 1])
# Gets the edges of layer, then sets the control points
# edges = find_edges_math(facets, layer, side_surfaces)
# graph_edge(stl_mesh, edges)"""
# control_pts, num_x, num_y = get_control_pts(facets, layer, edges)
control_pts = translate_ctrl_pts(control_pts, control_pts_id, facets)
# Evaluates the layer using the control points
surf = evaluate_surface(control_pts)
graph_layer(stl_mesh, surf)
# Layer is cleared and then repopulated using facets from the surface
layer.clear()
for face in surf.faces:
vector = []
for vertex in face.vertices:
vert = np.array(vertex)
vector.append(vert)
layer.append(vector)
# Finally layer is deepcopied into layer_slices, to avoid list of duplicates bug
layer_slices.append(deepcopy(layer))
# TODO: make it so the code below adds a list of facets and not indexes to the list
# layer_slices.insert(0, bottom)
# layer_slices.append(top)
# TODO: Determine if code below should be integrated or not
"""
Makes the final layer equal to the difference between the last layer generated
and the top layer from stl, if the the difference is greater than or equal to
half of the layer height.
index = get_top_facet(bottom, stl_mesh)
top_bottom_facet = temp_layer[bottom.index(index)]
diff = min(top_facet[:, 2] - top_bottom_facet[:, 2])
if diff >= (LAYER_HEIGHT / 2):
temp_layer = []
for facet in bottom:
temp_facet = deepcopy(stl_mesh.vectors[facet])
for point in temp_facet:
point[2] = point[2] + (LAYER_HEIGHT * i) + diff
temp_layer.append(temp_facet)
layer_slices.append(temp_layer)
"""
return layer_slices
def translate_ctrl_pts(ctrl_pts, ctrl_pts_ids, facets):
new_ctrl_pts = []
for i in range(len(ctrl_pts)):
temp = []
for j in range(len(ctrl_pts[i])):
increment = facets[ctrl_pts_ids[i, j]].normal * LAYER_HEIGHT
new_pt = deepcopy(ctrl_pts[i, j])
new_pt += increment
temp.append(new_pt)
new_ctrl_pts.append(deepcopy(temp))
return new_ctrl_pts
def calc_num_layers(top, bottom, side_surfaces, facets):
"""
Calculates how many layers will need to be generated. This is done by using the pythagoras theorem
to find the distance between the corners closest to the origin and divides this distance by the set layer height.
:param top: A list of indexes representing all facets on the top surface
:param bottom: A list of indexes representing all facets on the bottom surface
:param side_surfaces: A list of indexes representing all facets on the side surfaces
:param facets: List containing relevant facet data
:return: A int value that represent the total number of layers needed
"""
# Gets the edges of the top and bottom layers
e1 = find_edges_id(facets, top, side_surfaces)
e2 = find_edges_id(facets, bottom, side_surfaces)
# Uses the edges to find the corners in their respective layers
c1, d1 = find_corners(e1)
c2, d2 = find_corners(e2)
# Stores the first corner in each list for readability.
# Any corner could be used so long its the same position in both lists.
p1 = c1[0]
p2 = c2[0]
# Calculates the difference between each coordinate and squares the difference
x = math.pow(p2[0] - p1[0], 2)
y = math.pow(p2[1] - p1[1], 2)
z = math.pow(p2[2] - p1[2], 2)
# Calculates the distance between the points using the pythagoras theorem and rounds the answer
diff = round(math.sqrt(x + y + z))
# Total number of layers calculated by dividing diff by LAYER_HEIGHT
return int(diff // LAYER_HEIGHT)
# def create_layer(facets, layer):
def create_layer(layer):
"""
Creates a new layer by incrementing by the LAYER_HEIGHT across the normal vectors of every facet
:param facets: List of all facets
:param layer: List of of all facets in layer
:return: The new layer
"""
new_layer = [] # List to hold all the new facets
# TODO: Modify code to work for all layers. Currently can make the first layer after the bottom,
# but will not work for all subsequent layers.
# TODO: Modify code so increment is equal to average of facet normals*LAYER_HEIGHT associated with the point.
# Iterates over each facet and uses their normal vector multiplied by the LAYER_HEIGHT to create increment vector
for f in layer:
# increment = facets[f].normal * LAYER_HEIGHT
increment = f.normal * LAYER_HEIGHT
# Deepcopies the facet to prevent retroactive alteration bug
# new_facet = deepcopy(facets[f].facet)
new_facet = deepcopy(f)
# Every vertex has the increment vector added to it
for vert in new_facet.facet:
vert += increment
# Adds the new facet to the layer
new_layer.append(new_facet)
return new_layer
def find_corners(edges):
"""
Finds the corners of the of the layer using the edge data
:param edges: A list of line segments that make up the edge of the layer.
:return: A list of the corner points.
"""
corners = [] # A list that will hold the corner points
c_index = [] # Holds the index values of the corners in pairs
angles = []
for i in range(len(edges)):
e1 = edges[i].side
count = 0 # Counts the number of matched points to speed up run time
for j in range(i + 1, len(edges)):
e2 = edges[j].side
# A boolean of whether the 2 segments are adjacent and the shared point
is_adjacent, point, in1, in2 = line_adjacency(e1, e2)
if is_adjacent:
count += 1
edges[i].set_adjacency(in1, j)
edges[j].set_adjacency(in2, i)
# Calculate the vector representations of the line segments, used to calculate angle
v1, v2 = line_2_vector(e1, e2)
# Calculate the angle between the line segment, range 0 - 180 degrees
angle = calc_angle(v1, v2)
angles.append(angle)
edges[i].set_angles(in1, angle)
edges[j].set_angles(in2, angle)
# Tolerance of 10 degrees was used to differentiate corners from curve approximations
if 10 < angle < 170:
corners.append(deepcopy(point))
# corners.append(point)
# Appends indexes after sorting them with the greater difference in the x-direction first
c_index.append(greater_x_diff(e1, e2, i, j))
edges[i].set_relations(in1, 'corner')
edges[j].set_relations(in2, 'corner')
elif angle == 0 or angle == 180:
edges[i].set_relations(in1, 'straight')
edges[j].set_relations(in2, 'straight')
else:
edges[i].set_relations(in1, 'curved')
edges[j].set_relations(in2, 'curved')
# Speeds up run time
if count == 2:
break
# Sort the corners so that the bottom left hand corner from the XY perspective is in the 0th position,
# with the subsequent positions placed clockwise around the surface.
c_id_dict = sort_corners(corners, c_index)
"""with open('angle list2.txt', 'w') as filehandle:
for listitem in angles:
filehandle.write('%s\n' % listitem)"""
return corners, c_id_dict
def line_adjacency(e1, e2):
"""
Determines if two edge lines are adjacent to each other.
:param e1: first edge line
:param e2: second edge line
:return: is_adjacent: a bool value, True if adjacent, False if not
The adjacent point, if it exists, is the second return parameter
"""
is_adjacent = False # A bool representing whether facets are adjacent
# Nested for loop which compares each point of one facet with each point of another facet, looking for matching pts
for i in range(len(e1)):
for j in range(len(e2)):
# If all values of a point match with another, the counter is incremented by one
# and index values are added to corresponding strings
if (e1[i] == e2[j]).all():
is_adjacent = True
return is_adjacent, e1[i], i, j
# In the event that the lines are not adjacent, a value of None is returned
return is_adjacent, None, None, None
def line_2_vector(e1, e2):
"""
Calculates the vector representations of the given line segments via linear algebra.
:param e1: first line segment or edge
:param e2: second line segment or edge
:return: v1: the vector representation of the first edge
v2: the vector representation of the second edge
"""
# Vector calculated by subtracting the first coordinate from the respective second coordinate
v1 = [e1[1][0] - e1[0][0], e1[1][1] - e1[0][1], e1[1][2] - e1[0][2]]
v2 = [e2[1][0] - e2[0][0], e2[1][1] - e2[0][1], e2[1][2] - e2[0][2]]
return v1, v2
def calc_angle(v1, v2):
"""
Calculate the angle between 2 line segments via their vector representations with the use of the dot product and
arc cosine.
:param v1: First vector
:param v2: Second vector
:return: The angle between the lines in units degrees
"""
# Separates the vector into its x-, y-, and z-coordinates
x1, y1, z1 = v1
x2, y2, z2 = v2
# The dot product calculation
inner_product = x1 * x2 + y1 * y2 + z1 * z2
# Calculate the length of the vector
len1 = math.hypot(x1, y1, z1)
len2 = math.hypot(x2, y2, z2)
# Calculate the angle in radians
rads = math.acos(round(inner_product / (len1 * len2), 3))
# Return the angle in degrees
return abs(math.degrees(rads))
def greater_x_diff(e1, e2, i, j):
"""
Determines which edge has the greatest difference in the x-coordinate.
:param e1: First edge
:param e2: Second edge
:param i: First index
:param j: Second index
:return: List of the indexes ordered
"""
diff1 = abs(e1[0][0] - e1[1][0])
diff2 = abs(e2[0][0] - e2[1][0])
if diff1 < diff2:
return [j, i]
else:
return [i, j]
def sort_corners(corners, c_index):
"""
Sorts the corner points so that the bottom, left-hand corner is in the 0th position of the list,
with the 1st position holding the top, left-hand corner, and continuing in a clockwise fashion.
:param c_index: A list that holds the index values of the corners
:param corners: The list unsort corner points
:return: no return value due to passing in by reference
"""
temp = np.array(corners) # An array to hold the corner point data, used for slicing purposes
c_id_dict = {} # A dictionary to hold the indexes of the edges that belong to each corner
# Sorts the x- and y-coordinates from smallest to largest and stores their index value in a list,
# with the index of smallest coordinate in position 0
srtd_x_coords = smallest_2_largest(temp[:, 0:1])
srtd_y_coords = smallest_2_largest(temp[:, 1:2])
# Using the logic that the smallest values are in position 0 and 1, and the largest in 3 and 4,
# sorts the points in the corners list
for i in range(len(srtd_x_coords)):
for j in range(len(srtd_y_coords)):
if srtd_x_coords[i] == srtd_y_coords[j]:
index = srtd_x_coords[i]
if i < 2 and j < 2:
corners[0] = temp[index]
c_id_dict[0] = c_index[index]
elif i < 2 and j > 1:
corners[1] = temp[index]
c_id_dict[1] = c_index[index]
elif i > 1 and j > 1:
corners[2] = temp[index]
c_id_dict[2] = c_index[index]
elif i > 1 and j < 2:
corners[3] = temp[index]
c_id_dict[3] = c_index[index]
return c_id_dict
def smallest_2_largest(coordinates):
"""
Sorts an array of 4 coordinates from smallest to largest.
:param coordinates: A array of 4 coordinates
:return: A list of the indexes sorted from smallest to largest based on the values they represent within the array
"""
largest = None # Holds the largest value
lowest = None # Holds the lowest value
largest2 = None # Holds the 2nd largest value
lowest2 = None # Holds the 2nd lowest value
# Sorts the coordinate values by assigning them to the variables made above
for i in range(len(coordinates)):
if largest is None or coordinates[i] > largest:
largest2 = largest
largest = coordinates[i]
elif largest2 is None or largest2 < coordinates[i]:
largest2 = coordinates[i]
if lowest is None or coordinates[i] < lowest:
lowest2 = lowest
lowest = coordinates[i]
elif lowest2 is None or lowest2 > coordinates[i]:
lowest2 = coordinates[i]
# Changes the array to a list, done to use .index() function
corr_list = coordinates.tolist()
# Assigns the index of the found values to variables. If-else statements are used to avoid duplication bug
i1 = corr_list.index(lowest)
if lowest == lowest2:
for i in range(len(corr_list)):
if corr_list[i] == lowest2:
i2 = i
else:
i2 = corr_list.index(lowest2)
i3 = corr_list.index(largest2)
if largest == largest2:
for i in range(len(corr_list)):
if corr_list[i] == largest:
i4 = i
else:
i4 = corr_list.index(largest)
return [i1, i2, i3, i4]
def set_edge_ctrl_pts(edges, num_cpts, coord, pos, end):
"""
Sets the the control points along the edge of the layer.
:param edges: List of type class Edge
:param num_cpts: Calculated number of control points in given direction
:param coord: Integer to represent main coordinate, 0 for x and 1 for y
:param pos: Position of current control point being set
:param end: End point of where to set control points
:return: List of the layer edge control points