-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathmain.py
More file actions
349 lines (288 loc) · 18.4 KB
/
main.py
File metadata and controls
349 lines (288 loc) · 18.4 KB
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
# Import custom .py Files
import filereader as fr
import prusa_slicer as ps
import surface as sf
import transform_method_1 as tm1
import transform_method_2 as tm2
# Import official Librarys
import os
import platform
import dearpygui.dearpygui as dpg # install with pip: "pip install dearpygui" -> Version 1.10.0
import numpy as np # install with pip: "pip install numpy" -> Version 1.24.3
import scipy # install with pip: "python -m pip install scipy" -> Version 1.11.1
import shapely # install with pip: "pip install shapely" -> Version 2.0.1
if __name__ == "__main__":
# Setup Default Paths if nothing is marked
stl_default = "test_files/Keil.stl"
config_default = "test_files/generic_config_Deltiq2.ini"
# Create the window with its Context
dpg.create_context()
# get current Operating system -> "windows" = Windows, "darwin" = Mac OS
os_current = platform.system()
if os_current == "Windows":
prusaslicer_default_path = "C:\Program Files\Prusa3D\PrusaSlicer"
if os_current == "Darwin":
prusaslicer_default_path = 'Use "Change Prusaslicer DIR" for defining Path'
# Standard comment as path
stl_dir = "C:/ "
config_dir = "C:/ "
stl_path_dir_default = stl_dir
config_path_dir_default = config_dir
# default Parameter definition
max_angle_default = 40 # default value for visualisation
outline_offset_default = 3.5 # in mm
resolution_zmesh = 0.05
outline_active = False # default GUI visibility for the offset
default_planar_baselayer = 2
method2_upscale_iteration = 0
transform_method = 'mirror'
# Interupthandling if a button or similiar is activated
def stl_chosen(sender, app_data, user_data):
stl_dir = app_data["file_path_name"]
dpg.set_value("checkbox_cad", False)
dpg.set_value("stl_text", stl_dir)
def config_chosen(sender, app_data, user_data):
config_dir = app_data["file_path_name"]
dpg.set_value("checkbox_config", False)
dpg.set_value("config_text", config_dir)
def slicer_chosen(sender, app_data, user_data):
prusaslicer_default_path = app_data["file_path_name"]
dpg.set_value("slicer_text", prusaslicer_default_path)
def default_cad_path(sender, app_data, user_data):
if app_data:
stl_dir = stl_default
dpg.set_value("stl_text", stl_dir)
def default_config_path(sender, app_data, user_data):
if app_data:
config_dir = config_default
dpg.set_value("config_text", config_dir)
def case1_marked(sender, app_data, user_data):
if app_data:
dpg.set_value("checkbox_case2", False)
dpg.hide_item("text_planar_baselayer")
dpg.hide_item("planar_baselayer")
dpg.hide_item("dropdown")
dpg.hide_item("dropdown_text")
dpg.show_item("checkbox_outline_offset")
dpg.show_item("text_offset_value")
else:
dpg.set_value("checkbox_case1", True)
def case2_marked(sender, app_data, user_data):
if app_data:
dpg.set_value("checkbox_case1", False)
dpg.show_item("text_planar_baselayer")
dpg.show_item("planar_baselayer")
dpg.show_item("dropdown")
dpg.show_item("dropdown_text")
dpg.hide_item("checkbox_outline_offset")
dpg.hide_item("text_offset_value")
else:
dpg.set_value("checkbox_case2", True)
def outline_offset_marked(sender, app_data, user_data):
if app_data:
dpg.show_item("outline_offset_value")
else:
dpg.hide_item("outline_offset_value")
def dropdown_callback(sender, app_data, user_data):
if app_data:
transform_method = app_data
def show_gcode_prusaslicer(sender, app_data, user_data):
output_path = dpg.get_value("stl_text").rsplit('.')[0] + '.gcode'
ps.viewGCODE(output_path, dpg.get_value("slicer_text"))
def calculate_button(sender, app_data, user_data):
# if the paths are changed to a custom one
if dpg.get_value("stl_text") == stl_path_dir_default :
stl_dir = stl_default
dpg.set_value("stl_text", stl_dir)
dpg.set_value("checkbox_cad", True)
if dpg.get_value("config_text") == config_path_dir_default:
config_dir = config_default
dpg.set_value("config_text", config_dir)
dpg.set_value("checkbox_config", True)
dpg.set_value("showtext_calculate_button", "calculation started")
max_angle = np.deg2rad(dpg.get_value('max_angle_input'))
# Start with the calculation
dpg.show_item("loading")
# ---------------------------------Function for slicing etc. here ---------------------------------------
if os.path.exists(dpg.get_value("slicer_text")+"\prusa-slicer-console.exe"):
if dpg.get_value("checkbox_case1"):
# Here goes the calculations for Case 1
# Open the .stl to the triangle Array
triangle_array = fr.openSTL(dpg.get_value("stl_text"))
# Get the Config as String to determine the layerheight etc.
config = fr.slicer_config(fr.openINI(dpg.get_value("config_text")))
# Define PrintInfo for Layerheight infos etc.
printSetting = tm1.PrintInfo(config,FullBottomLayers=4, FullTopLayers=4, resolution_zmesh = resolution_zmesh)
# set new path for the resulting .gcode
printSetting.path = dpg.get_value("stl_text").rsplit('.')[0] + '.gcode'
# Calculate the Surface Array
# Give Feedback on the current progress
print("Calculating Surface Interpolation")
if dpg.get_value("checkbox_outline_offset"): # Run this condition, when the offset in the GUI is true
# Create the surface
Oberflaeche, limits = sf.create_surface(triangle_array, max_angle)
# Extract the contour (from the buildplate) and sort the points in the array
points_sorted = sf.sort_contour(triangle_array)
# Create an offset of the contour and apply it on the surface
surface_filtered = sf.offset_contour(points_sorted[:,0], points_sorted[:,1], Oberflaeche, dpg.get_value("outline_offset_value"))
# Create a 'nearest' interpolation of the whole meshgrid
xmesh, ymesh, zmesh = sf.create_surface_extended_case1(surface_filtered, limits, printSetting.resolution)
# Create a gradient mesh of the whole structure
gradx_mesh, grady_mesh, gradz = sf.create_gradient(Oberflaeche, limits)
else:
filtered_surface, limits = sf.create_surface(triangle_array, max_angle) # Winkel
# Calculate the nearest extrapolated points outside of the surface
xmesh, ymesh, zmesh = sf.create_surface_extended(filtered_surface, limits, printSetting.resolution)
# Calculate the gradient of the surface for extruding optimizing
gradx_mesh, grady_mesh, gradz = sf.create_gradient(filtered_surface, limits)
# Transform the .stl for slicing
transformed_stl = tm1.trans_stl(triangle_array, zmesh, limits, printSetting)
# write the .stl to a temp file
temp_stl_path = fr.writeSTL(transformed_stl)
# repair damaged triangles in the .stl (wrong surface direction -> normalvector wrong)
ps.repairSTL(temp_stl_path)
# Slice the transformed .stl
ps.sliceSTL(temp_stl_path,dpg.get_value("config_text"),'--info', dpg.get_value("slicer_text"))
# Load the sliced and generated .gcode in an array
orig_gcode, config = fr.openGCODE_keepcoms("output.gcode", get_config=True)
# Transform the gcode according to the Surface
tm1.trans_gcode(orig_gcode, gradz, zmesh, printSetting, limits, config_string=config)
# Delete the temp generated .stl
os.remove(temp_stl_path)
os.remove('output.gcode')
if dpg.get_value("checkbox_case2"):
# Repair stl to check for faults in the stl
ps.repairSTL(dpg.get_value("stl_text"))
# Read STL to the orig_stl Array with Shape [NR_OF_TRIANGLES, 12]
orig_stl = fr.openSTL(dpg.get_value("stl_text"))
# Upscale the STL for higher accuracy of the Nodes from the STL
upscaled_stl = sf.upscale_stl(orig_stl, method2_upscale_iteration)
# Calculate the Surface
surface, limits = sf.create_surface(upscaled_stl, max_angle)
#Create the extended Surface with 'nearest' extrapolated mesh points
xmesh, ymesh, zmesh = sf.create_surface_extended(surface, limits, resolution_zmesh)
# Create an Array with the xmesh, ymesh and zmesh values
filtered_surface = np.concatenate(([xmesh.flatten()],[ymesh.flatten()],[zmesh.flatten()]),axis=0).T
# Read the GCode Config from the Config File
ini_config = fr.slicer_config(fr.openINI(dpg.get_value("config_text")))
# read the layer height from the Config
layer_height = ini_config.get_config_param('layer_height')
# calculate the offset for the desired planar baselayers
planarBaseOffset = (dpg.get_value('planar_baselayer') + 1) * float(layer_height)
# Slice the STL planar only for the baselayers
ps.sliceSTL(dpg.get_value("stl_text"),dpg.get_value("config_text"),f'{"--skirts 2 --skirt-height 2 --skirt-distance 6"}', dpg.get_value("slicer_text"))
# Open the GCode to the Array
planar_base_gcode, prusa_generated_config_planar = fr.openGCODE_keepcoms('output.gcode')
# Extract the desired number of baselayer from the sliced GCode
base_layer_gcode = fr.readBaseLayers(planar_base_gcode,dpg.get_value('planar_baselayer'))
# Transform the STL to get a flat top (mirroring upside down)
transformed_stl = tm2.projectSTL(stl_data=upscaled_stl,filtered_surface=filtered_surface,planarBaseOffset=0.0,method=transform_method)
# Write the transformed Nodes as a STL
temp_stl_path = fr.writeSTL(transformed_stl)
# Repair the created transformed STL
ps.repairSTL(temp_stl_path)
# Slice the Transformed STL to get a GCode
ps.sliceSTL(temp_stl_path,dpg.get_value("config_text"),'', dpg.get_value("slicer_text"))
# Read the GCode to the Array with the used config
planar_gcode, prusa_generated_config = fr.openGCODE_keepcoms('output.gcode')
# Retransform the Gcode to get a flat bottom layer
tm2.transformGCODE(planar_gcode, base_layer_gcode, dpg.get_value("stl_text"), planarBaseOffset,filtered_surface, prusa_generated_config, layer_height)
# Remove the created temp files for clean structure in the explorer
os.remove(temp_stl_path)
os.remove('output.gcode')
os.remove('temp_gcode.gcode')
# finishing informations for the User
dpg.hide_item("loading")
dpg.set_value("showtext_calculate_button", "Finished Gcode ready")
else:
dpg.hide_item("loading")
dpg.set_value("showtext_calculate_button", "Prusaslicer Executable couldn't be found in given directory!")
# Here are the File dialog defined
with dpg.file_dialog(directory_selector=False, show=False, callback=stl_chosen, id="stl_select", width=700 ,height=400):
dpg.add_file_extension(".stl", color=(255, 255, 255, 255))
with dpg.file_dialog(directory_selector=True, show=False, callback=slicer_chosen, id="slicer_select", width=700 ,height=400):
dpg.add_file_extension("")
with dpg.file_dialog(directory_selector=False, show=False, callback=config_chosen, id="slicer_config", width=700 ,height=400):
dpg.add_file_extension(".ini", color=(255, 255, 255, 255))
# Custom Window with the corresponding buttons etc.
with dpg.window(label="GCode Transformation", width=1000, height=500):
with dpg.group(horizontal=True):
dpg.add_text("------------------------------------ Select Path -------------------------------------------------")
# Select the CAD File
with dpg.group(horizontal=True):
dpg.add_text("default:", tag="text_default_cad")
dpg.add_checkbox(label=" ", callback=default_cad_path, tag="checkbox_cad")
dpg.add_button(label="Select CAD File", callback=lambda: dpg.show_item("stl_select"), width=200)
dpg.add_text(" Directory: ")
dpg.add_text(stl_dir, tag="stl_text")
# Select the Config File
with dpg.group(horizontal=True):
dpg.add_text("default:", tag="text_default_config")
dpg.add_checkbox(label=" ", callback=default_config_path, tag="checkbox_config")
dpg.add_button(label="Select Prusaslicer Config", callback=lambda: dpg.show_item("slicer_config"), width=200)
dpg.add_text(" Directory: ")
dpg.add_text(config_dir, tag="config_text")
with dpg.group(horizontal=True):
dpg.add_button(label="Change Prusaslicer DIR", callback=lambda: dpg.show_item("slicer_select"), width=200)
dpg.add_text(" Current Directory: ")
dpg.add_text(prusaslicer_default_path, tag="slicer_text")
with dpg.group(horizontal=True):
dpg.add_text("")
with dpg.group(horizontal=True):
dpg.add_text("----------------------------------- Slicing options ----------------------------------------------")
# Select the Case with checkboxes
with dpg.group(horizontal=True):
dpg.add_text("Select a Method: ")
dpg.add_text("Method 1 ", tag="text_case1")
dpg.add_checkbox(label=" ", tag="checkbox_case1", callback=case1_marked, default_value=True)
dpg.add_text("Method 2", tag="text_case2")
dpg.add_checkbox(tag="checkbox_case2", callback=case2_marked)
dpg.add_text(' Set numbers of planar baselayer', show=False, tag='text_planar_baselayer')
dpg.add_input_int(tag = 'planar_baselayer', default_value=default_planar_baselayer, width=70, show=False)
with dpg.group(horizontal=True):
dpg.add_text("Select the maximal printing angle:", tag ='text_max_angle')
dpg.add_input_int(tag = 'max_angle_input', default_value=max_angle_default, width=100)
dpg.add_text(" select STL transform method:",tag="dropdown_text", show=False)
dpg.add_listbox(tag = 'dropdown', items=["mirror", "interpolate"], callback=dropdown_callback, show = False, width=90)
with dpg.group(horizontal=True):
dpg.add_text("Add offset from outline", tag = "text_offset_value")
dpg.add_checkbox(tag="checkbox_outline_offset", default_value=False, callback=outline_offset_marked, show=True)
dpg.add_text(" ")
dpg.add_input_float(label = "in mm", tag="outline_offset_value", default_value= outline_offset_default, show=False, width= 100)
with dpg.group(horizontal=True):
dpg.add_text("---------------------------------- Start Calculation ---------------------------------------------")
# Select the Calculate Button
with dpg.group(horizontal=True):
dpg.add_button(label="Calculate GCode", callback=calculate_button)
dpg.add_button(label="Open Nonplanar GCode", callback=show_gcode_prusaslicer)
with dpg.group(horizontal=True):
dpg.add_loading_indicator(tag="loading", show=False, radius=1.5)
dpg.add_text("", tag="showtext_calculate_button")
#Tooltips: (hovering over Items to show additional Information)
# Case 1 Infos
with dpg.tooltip("text_case1"):
dpg.add_text("Method 1 scales a sliced and transformed .stl depending on the surface\n The Layerheight is calculated depending on the surface its mean height\nSurfacepoints above the mean get stretched and those below get compressed")
# Case 2 Infos
with dpg.tooltip("text_case2"):
dpg.add_text("Method 2 transforms the gcode of the mirrored .stl\n The Layerheight is constant but every layer is parallel to the surface\n The original .stl gets mirrored upside down and transformed, until the bottom is flat")
with dpg.tooltip("slicer_text"):
dpg.add_text("Add the Path to the folder where the prusa-slicer.exe is located. \nExample: \Prusa3D\PrusaSlicer\prusa-slicer.exe -> Path = \Prusa3D\PrusaSlicer ")
with dpg.tooltip("text_offset_value"):
dpg.add_text("The Offset is required when you have a radius on the outer edge of the part. \nChoose the offset according to the radius in mm")
# Default Path for CAD Infos
with dpg.tooltip("text_default_cad"):
dpg.add_text("The default Path is:")
dpg.add_text(stl_default)
# Default Path for Config file Infos
with dpg.tooltip("text_default_config"):
dpg.add_text("The default Path is:")
dpg.add_text(config_default)
with dpg.tooltip('text_max_angle'):
dpg.add_text("Angle between the horizontal plane and the surface")
# Create the Window with custom Commands
dpg.create_viewport(title='Nonplanar Slicing', width=1000, height=500)
# Create the window and show it to the user
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()