Wed, 20 Jan 2021 10:15:13 +0100
updated and added new files for printrun
15 | 1 | #!/usr/bin/env python |
2 | ||
29 | 3 | # FILE MODIFIED BY NEOSOFT - MALTE DI DONATO |
16
36d478bde840
Implemented svg, png and hpgl compilers to pronterface
mbayer
parents:
15
diff
changeset
|
4 | # Embed Lasercut functions from laser.py |
31 | 5 | from . import laser |
29 | 6 | try: |
31 | 7 | from . import module_watcher |
8 | mw = module_watcher.ModuleWatcher() | |
29 | 9 | mw.watch_module('laser') |
10 | mw.start_watching() | |
31 | 11 | except Exception, e: |
12 | print e | |
13 | print "ModuleWatcher not loaded, skipping autoreloading of changed modules" | |
16
36d478bde840
Implemented svg, png and hpgl compilers to pronterface
mbayer
parents:
15
diff
changeset
|
14 | |
15 | 15 | # This file is part of the Printrun suite. |
16 | # | |
17 | # Printrun is free software: you can redistribute it and/or modify | |
18 | # it under the terms of the GNU General Public License as published by | |
19 | # the Free Software Foundation, either version 3 of the License, or | |
20 | # (at your option) any later version. | |
21 | # | |
22 | # Printrun is distributed in the hope that it will be useful, | |
23 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
24 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
25 | # GNU General Public License for more details. | |
26 | # | |
27 | # You should have received a copy of the GNU General Public License | |
28 | # along with Printrun. If not, see <http://www.gnu.org/licenses/>. | |
29 | ||
30 | import os | |
31 | import Queue | |
32 | import sys | |
33 | import time | |
34 | import threading | |
35 | import traceback | |
36 | import cStringIO as StringIO | |
37 | import subprocess | |
38 | import glob | |
39 | import logging | |
40 | ||
41 | try: import simplejson as json | |
42 | except ImportError: import json | |
43 | ||
44 | from . import pronsole | |
45 | from . import printcore | |
46 | ||
47 | from .utils import install_locale, setup_logging, dosify, \ | |
48 | iconfile, configfile, format_time, format_duration, \ | |
49 | hexcolor_to_float, parse_temperature_report, \ | |
50 | prepare_command, check_rgb_color, check_rgba_color | |
51 | install_locale('pronterface') | |
52 | ||
53 | try: | |
54 | import wx | |
55 | except: | |
56 | logging.error(_("WX is not installed. This program requires WX to run.")) | |
57 | raise | |
58 | ||
59 | from .gui.widgets import SpecialButton, MacroEditor, PronterOptions, ButtonEdit | |
60 | ||
61 | winsize = (800, 500) | |
62 | layerindex = 0 | |
63 | if os.name == "nt": | |
64 | winsize = (800, 530) | |
65 | ||
66 | pronterface_quitting = False | |
67 | ||
68 | class PronterfaceQuitException(Exception): | |
69 | pass | |
70 | ||
71 | from .gui import MainWindow | |
72 | from .settings import wxSetting, HiddenSetting, StringSetting, SpinSetting, \ | |
73 | FloatSpinSetting, BooleanSetting, StaticTextSetting | |
74 | from printrun import gcoder | |
75 | from .pronsole import REPORT_NONE, REPORT_POS, REPORT_TEMP, REPORT_MANUAL | |
76 | ||
77 | class ConsoleOutputHandler(object): | |
78 | """Handle console output. All messages go through the logging submodule. We setup a logging handler to get logged messages and write them to both stdout (unless a log file path is specified, in which case we add another logging handler to write to this file) and the log panel. | |
79 | We also redirect stdout and stderr to ourself to catch print messages and al.""" | |
80 | ||
81 | def __init__(self, target, log_path): | |
82 | self.stdout = sys.stdout | |
83 | self.stderr = sys.stderr | |
84 | sys.stdout = self | |
85 | sys.stderr = self | |
86 | if log_path: | |
87 | self.print_on_stdout = False | |
88 | setup_logging(self, log_path, reset_handlers = True) | |
89 | self.target = target | |
90 | else: | |
91 | self.print_on_stdout = True | |
92 | setup_logging(sys.stdout) | |
93 | self.target = target | |
94 | ||
95 | def __del__(self): | |
96 | sys.stdout = self.stdout | |
97 | sys.stderr = self.stderr | |
98 | ||
99 | def write(self, data): | |
100 | try: | |
101 | self.target(data) | |
102 | except: | |
103 | pass | |
104 | if self.print_on_stdout: | |
105 | try: | |
106 | data = data.encode("utf-8") | |
107 | except: | |
108 | pass | |
109 | self.stdout.write(data) | |
110 | ||
111 | def flush(self): | |
112 | if self.stdout: | |
113 | self.stdout.flush() | |
114 | ||
115 | class ComboSetting(wxSetting): | |
116 | ||
117 | def __init__(self, name, default, choices, label = None, help = None, group = None): | |
118 | super(ComboSetting, self).__init__(name, default, label, help, group) | |
119 | self.choices = choices | |
120 | ||
121 | def get_specific_widget(self, parent): | |
122 | import wx | |
123 | self.widget = wx.ComboBox(parent, -1, str(self.value), choices = self.choices, style = wx.CB_DROPDOWN) | |
124 | return self.widget | |
125 | ||
126 | class PronterWindow(MainWindow, pronsole.pronsole): | |
127 | ||
128 | _fgcode = None | |
129 | ||
130 | def _get_fgcode(self): | |
131 | return self._fgcode | |
132 | ||
133 | def _set_fgcode(self, value): | |
134 | self._fgcode = value | |
135 | self.excluder = None | |
136 | self.excluder_e = None | |
137 | self.excluder_z_abs = None | |
138 | self.excluder_z_rel = None | |
139 | fgcode = property(_get_fgcode, _set_fgcode) | |
140 | ||
141 | def _get_display_graph(self): | |
142 | return self.settings.tempgraph | |
143 | display_graph = property(_get_display_graph) | |
144 | ||
145 | def _get_display_gauges(self): | |
146 | return self.settings.tempgauges | |
147 | display_gauges = property(_get_display_gauges) | |
148 | ||
149 | def __init__(self, app, filename = None, size = winsize): | |
150 | pronsole.pronsole.__init__(self) | |
151 | self.app = app | |
152 | self.window_ready = False | |
153 | self.ui_ready = False | |
154 | self._add_settings(size) | |
155 | ||
156 | for field in dir(self.settings): | |
157 | if field.startswith("_gcview_color_"): | |
158 | cleanname = field[1:] | |
159 | color = hexcolor_to_float(getattr(self.settings, cleanname), 4) | |
160 | setattr(self, cleanname, list(color)) | |
161 | ||
162 | self.pauseScript = None #"pause.gcode" | |
163 | self.endScript = None #"end.gcode" | |
164 | ||
165 | self.filename = filename | |
166 | ||
167 | self.capture_skip = {} | |
168 | self.capture_skip_newline = False | |
169 | self.fgcode = None | |
170 | self.excluder = None | |
171 | self.slicep = None | |
172 | self.current_pos = [0, 0, 0] | |
173 | self.paused = False | |
174 | self.uploading = False | |
175 | self.sentglines = Queue.Queue(0) | |
176 | self.cpbuttons = { | |
177 | "motorsoff": SpecialButton(_("Motors off"), ("M84"), (250, 250, 250), _("Switch all motors off")), | |
178 | "extrude": SpecialButton(_("Extrude"), ("pront_extrude"), (225, 200, 200), _("Advance extruder by set length")), | |
179 | "reverse": SpecialButton(_("Reverse"), ("pront_reverse"), (225, 200, 200), _("Reverse extruder by set length")), | |
180 | } | |
181 | self.custombuttons = [] | |
182 | self.btndict = {} | |
183 | self.filehistory = None | |
184 | self.autoconnect = False | |
185 | self.parse_cmdline(sys.argv[1:]) | |
186 | ||
187 | # FIXME: We need to initialize the main window after loading the | |
188 | # configs to restore the size, but this might have some unforeseen | |
189 | # consequences. | |
190 | # -- Okai, it seems it breaks things like update_gviz_params >< | |
191 | os.putenv("UBUNTU_MENUPROXY", "0") | |
192 | size = (self.settings.last_window_width, self.settings.last_window_height) | |
193 | MainWindow.__init__(self, None, title = _("Pronterface"), size = size) | |
194 | if self.settings.last_window_maximized: | |
195 | self.Maximize() | |
196 | self.SetIcon(wx.Icon(iconfile("pronterface.png"), wx.BITMAP_TYPE_PNG)) | |
197 | self.Bind(wx.EVT_SIZE, self.on_resize) | |
198 | self.Bind(wx.EVT_MAXIMIZE, self.on_maximize) | |
199 | self.window_ready = True | |
200 | ||
201 | # set feedrates in printcore for pause/resume | |
202 | self.p.xy_feedrate = self.settings.xy_feedrate | |
203 | self.p.z_feedrate = self.settings.z_feedrate | |
204 | ||
205 | self.panel.SetBackgroundColour(self.bgcolor) | |
206 | customdict = {} | |
207 | try: | |
208 | execfile(configfile("custombtn.txt"), customdict) | |
209 | if len(customdict["btns"]): | |
210 | if not len(self.custombuttons): | |
211 | try: | |
212 | self.custombuttons = customdict["btns"] | |
213 | for n in xrange(len(self.custombuttons)): | |
214 | self.cbutton_save(n, self.custombuttons[n]) | |
215 | os.rename("custombtn.txt", "custombtn.old") | |
216 | rco = open("custombtn.txt", "w") | |
217 | rco.write(_("# I moved all your custom buttons into .pronsolerc.\n# Please don't add them here any more.\n# Backup of your old buttons is in custombtn.old\n")) | |
218 | rco.close() | |
219 | except IOError, x: | |
220 | logging.error(str(x)) | |
221 | else: | |
222 | logging.warning(_("Note!!! You have specified custom buttons in both custombtn.txt and .pronsolerc")) | |
223 | logging.warning(_("Ignoring custombtn.txt. Remove all current buttons to revert to custombtn.txt")) | |
224 | ||
225 | except: | |
226 | pass | |
227 | self.create_menu() | |
228 | self.update_recent_files("recentfiles", self.settings.recentfiles) | |
229 | ||
230 | self.reload_ui() | |
231 | # disable all printer controls until we connect to a printer | |
232 | self.gui_set_disconnected() | |
233 | self.statusbar = self.CreateStatusBar() | |
234 | self.statusbar.SetStatusText(_("Not connected to printer.")) | |
235 | ||
236 | self.t = ConsoleOutputHandler(self.catchprint, self.settings.log_path) | |
237 | self.stdout = sys.stdout | |
238 | self.slicing = False | |
239 | self.loading_gcode = False | |
240 | self.loading_gcode_message = "" | |
241 | self.mini = False | |
242 | self.p.sendcb = self.sentcb | |
243 | self.p.preprintsendcb = self.preprintsendcb | |
244 | self.p.printsendcb = self.printsentcb | |
245 | self.p.startcb = self.startcb | |
246 | self.p.endcb = self.endcb | |
247 | self.cur_button = None | |
248 | self.predisconnect_mainqueue = None | |
249 | self.predisconnect_queueindex = None | |
250 | self.predisconnect_layer = None | |
251 | self.hsetpoint = 0.0 | |
252 | self.bsetpoint = 0.0 | |
253 | if self.autoconnect: | |
254 | self.connect() | |
255 | if self.filename is not None: | |
256 | self.do_load(self.filename) | |
257 | if self.settings.monitor: | |
258 | self.update_monitor() | |
259 | ||
34 | 260 | self.lc_printing = False |
33
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
261 | self.pass_current = 1 |
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
262 | |
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
263 | # -------------------------------------------------------------- |
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
264 | # Lasercutter methods |
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
265 | # -------------------------------------------------------------- |
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
266 | |
34 | 267 | def on_lc_printfile(self, event): |
268 | # lc print button | |
269 | self.log("Priming Z axis to initial focus") | |
270 | line = self.precmd("G1 Z%.2f" % (self.settings.lc_z_focus + self.lc_material_thickness.GetValue())) | |
35 | 271 | wx.CallAfter(self.onecmd, line) |
34 | 272 | self.lc_printing = True |
35 | 273 | wx.CallAfter(self.printfile, None) |
33
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
274 | |
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
275 | def endcb_lasercut(self): |
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
276 | # LASERCUT: Now check if we should do another print pass? |
35 | 277 | self.log("event: endcb_lasercut") |
278 | if self.lc_printing: | |
279 | self.log(" -> checking if something to do...") | |
280 | pass_count = self.lc_pass_count.GetValue() | |
281 | if pass_count > 1: | |
282 | time.sleep(0.5) | |
283 | if self.pass_current < pass_count: | |
284 | self.pass_current += 1 | |
285 | self.log("Starting lasercut pass # %i of %i" % (self.pass_current, pass_count)) | |
286 | if self.lc_pass_zdiff.GetValue() != 0: | |
287 | # move Z focus | |
288 | new_z = self.settings.lc_z_focus + self.lc_material_thickness.GetValue() + ( | |
289 | self.lc_pass_zdiff.GetValue() * (self.pass_current - 1)) | |
290 | self.log("Re-Positioning laser focus by %.1f mm to %.1f" % (self.lc_pass_zdiff.GetValue(), new_z)) | |
291 | line = self.precmd("G1 Z%.2f" % (new_z)) | |
292 | self.onecmd(line) | |
293 | time.sleep(0.5) | |
34 | 294 | |
35 | 295 | # "click" print button again |
296 | tmp = self.pass_current | |
297 | self.printfile(None) | |
298 | self.pass_current = tmp | |
299 | else: | |
300 | self.lc_printing = False | |
301 | wx.CallAfter(self.lc_printbtn.Enable) | |
302 | wx.CallAfter(self.lc_printbtn.SetLabel, _("Start cutting")) | |
303 | ||
304 | self.log("Resetting Z axis to initial focus") | |
305 | line = self.precmd("G1 Z%.2f" % (self.settings.lc_z_focus + self.lc_material_thickness.GetValue())) | |
33
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
306 | self.onecmd(line) |
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
307 | else: |
34 | 308 | self.lc_printing = False |
309 | wx.CallAfter(self.lc_printbtn.Enable) | |
310 | wx.CallAfter(self.lc_printbtn.SetLabel, _("Start cutting")) | |
35 | 311 | |
33
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
312 | |
34 | 313 | def update_lc_settings(self, key, value): |
314 | return True | |
315 | ||
316 | def _lc_add_settings(self, size): | |
317 | # first add the lasercutter options | |
318 | self.settings._add(StaticTextSetting("separator_lc_general", "General laser settings", "", group = "Laser")) | |
43 | 319 | self.settings._add(BooleanSetting("lc_melzi_hack", False, "Use Melzi M571 Hack instead M3/M5", "no description :)", "Laser"), self.update_lc_settings) |
34 | 320 | self.settings._add(SpinSetting("lc_travel_speed", 120, 1, 300, "Travel speed in mm/s", "", "Laser"), self.update_lc_settings) |
321 | self.settings._add(SpinSetting("lc_engrave_speed", 10, 1, 300, "Engrave speed in mm/s", "", "Laser"), self.update_lc_settings) | |
322 | self.settings._add(SpinSetting("lc_z_focus", 16, -80, 80, "Laser Z focus position", "", "Laser"), self.update_lc_settings) | |
323 | self.settings._add(SpinSetting("lc_pass_count", 1, 0, 20, "Default Number of cutting passes", "", "Laser"), self.reload_ui) | |
324 | self.settings._add(FloatSpinSetting("lc_pass_zdiff", -0.25, -2.0, 2.0, "Default Z movement after each cut", "", "Laser"), self.reload_ui) | |
325 | self.settings._add(FloatSpinSetting("lc_material_thickness", 4.0, 0.0, 80.0, "Default Material Thickness", "", "Laser"), self.reload_ui) | |
326 | ||
327 | self.settings._add(StaticTextSetting("separator_lc_bitmap", "PNG Bitmap processing", "", group = "Laser")) | |
328 | self.settings._add(FloatSpinSetting("lc_bitmap_speed_factor", 1.0, 0.1, 2.0, "Engrave speed factor", "", "Laser"), self.update_lc_settings) | |
43 | 329 | self.settings._add(SpinSetting("lc_dpi", 300, 25, 600, "Image DPI", "Image resolution for scaling", "Laser"), self.update_lc_settings) |
34 | 330 | self.settings._add(SpinSetting("lc_grey_threshold", 0, 0, 255, "Grey threshold value for RGB", "", "Laser"), self.update_lc_settings) |
331 | self.settings._add(BooleanSetting("lc_invert_cut", True, "PNG: Invert grey threshold", "Invert laser on/off logic", "Laser"), self.update_lc_settings) | |
332 | self.settings._add(BooleanSetting("lc_change_dir", True, "PNG: Change direction", "Engrave in both directions on Y Axis", "Laser"), self.update_lc_settings) | |
333 | ||
334 | self.settings._add(StaticTextSetting("separator_lc_hpgl", "HPGL processing", "", group = "Laser")) | |
335 | self.settings._add(FloatSpinSetting("lc_hpgl_speed_factor", 1.0, 0.1, 2.0, "Engrave speed factor", "", "Laser"), self.update_lc_settings) | |
336 | ||
337 | self.settings._add(StaticTextSetting("separator_lc_svg", "SVG processing", "", group = "Laser")) | |
338 | self.settings._add(FloatSpinSetting("lc_svg_speed_factor", 1.0, 0.1, 2.0, "Engrave speed factor", "", "Laser"), self.update_lc_settings) | |
339 | self.settings._add(FloatSpinSetting("lc_svg_smoothness", 0.2, 0.1, 10.0, "Smoothness", "Smoothness of curves (smaller value = smoother curve)", "Laser"), self.update_lc_settings) | |
43 | 340 | self.settings._add(SpinSetting("lc_svg_width", 50, 1, 9999, "Width (mm)", "Image width", "Laser"), self.update_lc_settings) |
341 | self.settings._add(SpinSetting("lc_svg_height", 50, 1, 9999, "Height (mm)", "Image height", "Laser"), self.update_lc_settings) | |
36 | 342 | self.settings._add(ComboSetting("lc_svg_scalemode", "original", ["original", "scale", "stretch"], "Scaling mode", "scale/stretch to above dimensions", "Laser"), self.update_lc_settings) |
343 | self.settings._add(BooleanSetting("lc_svg_offset", True, "Calculate offset to X=0, Y=0", "If enabled, move image to origin position", "Laser"), self.update_lc_settings) | |
33
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
344 | |
15 | 345 | # -------------------------------------------------------------- |
346 | # Main interface handling | |
347 | # -------------------------------------------------------------- | |
348 | ||
349 | def reset_ui(self): | |
350 | MainWindow.reset_ui(self) | |
351 | self.custombuttons_widgets = [] | |
352 | ||
353 | def reload_ui(self, *args): | |
354 | if not self.window_ready: return | |
355 | self.Freeze() | |
356 | ||
357 | # If UI is being recreated, delete current one | |
358 | if self.ui_ready: | |
359 | # Store log console content | |
360 | logcontent = self.logbox.GetValue() | |
361 | # Create a temporary panel to reparent widgets with state we want | |
362 | # to retain across UI changes | |
363 | temppanel = wx.Panel(self) | |
364 | # TODO: add viz widgets to statefulControls | |
365 | for control in self.statefulControls: | |
366 | control.GetContainingSizer().Detach(control) | |
367 | control.Reparent(temppanel) | |
368 | self.panel.DestroyChildren() | |
369 | self.gwindow.Destroy() | |
370 | self.reset_ui() | |
371 | ||
372 | # Create UI | |
373 | if self.settings.uimode in (_("Tabbed"), _("Tabbed with platers")): | |
374 | self.createTabbedGui() | |
375 | else: | |
376 | self.createGui(self.settings.uimode == _("Compact"), | |
377 | self.settings.controlsmode == "Mini") | |
378 | ||
379 | if hasattr(self, "splitterwindow"): | |
380 | self.splitterwindow.SetSashPosition(self.settings.last_sash_position) | |
381 | ||
382 | def splitter_resize(event): | |
383 | self.splitterwindow.UpdateSize() | |
384 | self.splitterwindow.Bind(wx.EVT_SIZE, splitter_resize) | |
385 | ||
386 | def sash_position_changed(event): | |
387 | self.set("last_sash_position", self.splitterwindow.GetSashPosition()) | |
388 | self.splitterwindow.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, sash_position_changed) | |
389 | ||
390 | # Set gcview parameters here as they don't get set when viewers are | |
391 | # created | |
392 | self.update_gcview_params() | |
393 | ||
394 | # Finalize | |
395 | if self.online: | |
396 | self.gui_set_connected() | |
397 | if self.ui_ready: | |
398 | self.logbox.SetValue(logcontent) | |
399 | temppanel.Destroy() | |
400 | self.panel.Layout() | |
401 | if self.fgcode: | |
402 | self.start_viz_thread() | |
403 | if self.settings.monitor: | |
404 | self.update_monitor() | |
405 | self.ui_ready = True | |
406 | self.Thaw() | |
407 | ||
408 | def on_resize(self, event): | |
409 | wx.CallAfter(self.on_resize_real) | |
410 | event.Skip() | |
411 | ||
412 | def on_resize_real(self): | |
413 | maximized = self.IsMaximized() | |
414 | self.set("last_window_maximized", maximized) | |
415 | if not maximized and not self.IsIconized(): | |
416 | size = self.GetSize() | |
417 | self.set("last_window_width", size[0]) | |
418 | self.set("last_window_height", size[1]) | |
419 | ||
420 | def on_maximize(self, event): | |
421 | self.set("last_window_maximized", self.IsMaximized()) | |
422 | event.Skip() | |
423 | ||
424 | def on_exit(self, event): | |
425 | self.Close() | |
426 | ||
427 | def kill(self, e): | |
428 | if self.p.printing or self.p.paused: | |
429 | dlg = wx.MessageDialog(self, _("Print in progress ! Are you really sure you want to quit ?"), _("Exit"), wx.YES_NO | wx.ICON_WARNING) | |
430 | if dlg.ShowModal() == wx.ID_NO: | |
431 | return | |
432 | pronsole.pronsole.kill(self) | |
433 | global pronterface_quitting | |
434 | pronterface_quitting = True | |
435 | self.p.recvcb = None | |
436 | self.p.disconnect() | |
437 | if hasattr(self, "feedrates_changed"): | |
438 | self.save_in_rc("set xy_feedrate", "set xy_feedrate %d" % self.settings.xy_feedrate) | |
439 | self.save_in_rc("set z_feedrate", "set z_feedrate %d" % self.settings.z_feedrate) | |
440 | self.save_in_rc("set e_feedrate", "set e_feedrate %d" % self.settings.e_feedrate) | |
441 | if self.settings.last_extrusion != self.settings.default_extrusion: | |
442 | self.save_in_rc("set last_extrusion", "set last_extrusion %d" % self.settings.last_extrusion) | |
443 | if self.excluder: | |
444 | self.excluder.close_window() | |
445 | wx.CallAfter(self.gwindow.Destroy) | |
446 | wx.CallAfter(self.Destroy) | |
447 | ||
448 | def _get_bgcolor(self): | |
449 | if self.settings.bgcolor != "auto": | |
450 | return self.settings.bgcolor | |
451 | else: | |
452 | return wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWFRAME) | |
453 | bgcolor = property(_get_bgcolor) | |
454 | ||
455 | # -------------------------------------------------------------- | |
456 | # Main interface actions | |
457 | # -------------------------------------------------------------- | |
458 | ||
459 | def do_monitor(self, l = ""): | |
460 | if l.strip() == "": | |
461 | self.set("monitor", not self.settings.monitor) | |
462 | elif l.strip() == "off": | |
463 | self.set("monitor", False) | |
464 | else: | |
465 | try: | |
466 | self.monitor_interval = float(l) | |
467 | self.set("monitor", self.monitor_interval > 0) | |
468 | except: | |
469 | self.log(_("Invalid period given.")) | |
470 | if self.settings.monitor: | |
471 | self.log(_("Monitoring printer.")) | |
472 | else: | |
473 | self.log(_("Done monitoring.")) | |
474 | ||
475 | def do_pront_extrude(self, l = ""): | |
476 | feed = self.settings.e_feedrate | |
477 | self.do_extrude_final(self.edist.GetValue(), feed) | |
478 | ||
479 | def do_pront_reverse(self, l = ""): | |
480 | feed = self.settings.e_feedrate | |
481 | self.do_extrude_final(- self.edist.GetValue(), feed) | |
482 | ||
483 | def do_settemp(self, l = ""): | |
484 | try: | |
485 | if l.__class__ not in (str, unicode) or not len(l): | |
486 | l = str(self.htemp.GetValue().split()[0]) | |
487 | l = l.lower().replace(", ", ".") | |
488 | for i in self.temps.keys(): | |
489 | l = l.replace(i, self.temps[i]) | |
490 | f = float(l) | |
491 | if f >= 0: | |
492 | if self.p.online: | |
493 | self.p.send_now("M104 S" + l) | |
494 | self.log(_("Setting hotend temperature to %f degrees Celsius.") % f) | |
495 | self.sethotendgui(f) | |
496 | else: | |
497 | self.logError(_("Printer is not online.")) | |
498 | else: | |
499 | self.logError(_("You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0.")) | |
500 | except Exception, x: | |
501 | self.logError(_("You must enter a temperature. (%s)") % (repr(x),)) | |
502 | ||
503 | def do_bedtemp(self, l = ""): | |
504 | try: | |
505 | if l.__class__ not in (str, unicode) or not len(l): | |
506 | l = str(self.btemp.GetValue().split()[0]) | |
507 | l = l.lower().replace(", ", ".") | |
508 | for i in self.bedtemps.keys(): | |
509 | l = l.replace(i, self.bedtemps[i]) | |
510 | f = float(l) | |
511 | if f >= 0: | |
512 | if self.p.online: | |
513 | self.p.send_now("M140 S" + l) | |
514 | self.log(_("Setting bed temperature to %f degrees Celsius.") % f) | |
515 | self.setbedgui(f) | |
516 | else: | |
517 | self.logError(_("Printer is not online.")) | |
518 | else: | |
519 | self.logError(_("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.")) | |
520 | except Exception, x: | |
521 | self.logError(_("You must enter a temperature. (%s)") % (repr(x),)) | |
522 | ||
523 | def do_setspeed(self, l = ""): | |
524 | try: | |
525 | if l.__class__ not in (str, unicode) or not len(l): | |
526 | l = str(self.speed_slider.GetValue()) | |
527 | else: | |
528 | l = l.lower() | |
529 | speed = int(l) | |
530 | if self.p.online: | |
531 | self.p.send_now("M220 S" + l) | |
532 | self.log(_("Setting print speed factor to %d%%.") % speed) | |
533 | else: | |
534 | self.logError(_("Printer is not online.")) | |
535 | except Exception, x: | |
536 | self.logError(_("You must enter a speed. (%s)") % (repr(x),)) | |
537 | ||
538 | def do_setflow(self, l = ""): | |
539 | try: | |
540 | if l.__class__ not in (str, unicode) or not len(l): | |
541 | l = str(self.flow_slider.GetValue()) | |
542 | else: | |
543 | l = l.lower() | |
544 | flow = int(l) | |
545 | if self.p.online: | |
546 | self.p.send_now("M221 S" + l) | |
547 | self.log(_("Setting print flow factor to %d%%.") % flow) | |
548 | else: | |
549 | self.logError(_("Printer is not online.")) | |
550 | except Exception, x: | |
551 | self.logError(_("You must enter a flow. (%s)") % (repr(x),)) | |
552 | ||
553 | def setbedgui(self, f): | |
554 | self.bsetpoint = f | |
555 | if self.display_gauges: self.bedtgauge.SetTarget(int(f)) | |
556 | if self.display_graph: wx.CallAfter(self.graph.SetBedTargetTemperature, int(f)) | |
557 | if f > 0: | |
558 | wx.CallAfter(self.btemp.SetValue, str(f)) | |
559 | self.set("last_bed_temperature", str(f)) | |
560 | wx.CallAfter(self.setboff.SetBackgroundColour, None) | |
561 | wx.CallAfter(self.setboff.SetForegroundColour, None) | |
562 | wx.CallAfter(self.setbbtn.SetBackgroundColour, "#FFAA66") | |
563 | wx.CallAfter(self.setbbtn.SetForegroundColour, "#660000") | |
564 | wx.CallAfter(self.btemp.SetBackgroundColour, "#FFDABB") | |
565 | else: | |
566 | wx.CallAfter(self.setboff.SetBackgroundColour, "#0044CC") | |
567 | wx.CallAfter(self.setboff.SetForegroundColour, "white") | |
568 | wx.CallAfter(self.setbbtn.SetBackgroundColour, None) | |
569 | wx.CallAfter(self.setbbtn.SetForegroundColour, None) | |
570 | wx.CallAfter(self.btemp.SetBackgroundColour, "white") | |
571 | wx.CallAfter(self.btemp.Refresh) | |
572 | ||
573 | def sethotendgui(self, f): | |
574 | self.hsetpoint = f | |
575 | if self.display_gauges: self.hottgauge.SetTarget(int(f)) | |
576 | if self.display_graph: wx.CallAfter(self.graph.SetExtruder0TargetTemperature, int(f)) | |
577 | if f > 0: | |
578 | wx.CallAfter(self.htemp.SetValue, str(f)) | |
579 | self.set("last_temperature", str(f)) | |
580 | wx.CallAfter(self.settoff.SetBackgroundColour, None) | |
581 | wx.CallAfter(self.settoff.SetForegroundColour, None) | |
582 | wx.CallAfter(self.settbtn.SetBackgroundColour, "#FFAA66") | |
583 | wx.CallAfter(self.settbtn.SetForegroundColour, "#660000") | |
584 | wx.CallAfter(self.htemp.SetBackgroundColour, "#FFDABB") | |
585 | else: | |
586 | wx.CallAfter(self.settoff.SetBackgroundColour, "#0044CC") | |
587 | wx.CallAfter(self.settoff.SetForegroundColour, "white") | |
588 | wx.CallAfter(self.settbtn.SetBackgroundColour, None) | |
589 | wx.CallAfter(self.settbtn.SetForegroundColour, None) | |
590 | wx.CallAfter(self.htemp.SetBackgroundColour, "white") | |
591 | wx.CallAfter(self.htemp.Refresh) | |
592 | ||
593 | def rescanports(self, event = None): | |
594 | scanned = self.scanserial() | |
595 | portslist = list(scanned) | |
596 | if self.settings.port != "" and self.settings.port not in portslist: | |
597 | portslist.append(self.settings.port) | |
598 | self.serialport.Clear() | |
599 | self.serialport.AppendItems(portslist) | |
600 | if os.path.exists(self.settings.port) or self.settings.port in scanned: | |
601 | self.serialport.SetValue(self.settings.port) | |
602 | elif portslist: | |
603 | self.serialport.SetValue(portslist[0]) | |
604 | ||
605 | def cbkey(self, e): | |
606 | if e.GetKeyCode() == wx.WXK_UP: | |
607 | if self.commandbox.histindex == len(self.commandbox.history): | |
608 | self.commandbox.history.append(self.commandbox.GetValue()) # save current command | |
609 | if len(self.commandbox.history): | |
610 | self.commandbox.histindex = (self.commandbox.histindex - 1) % len(self.commandbox.history) | |
611 | self.commandbox.SetValue(self.commandbox.history[self.commandbox.histindex]) | |
612 | self.commandbox.SetSelection(0, len(self.commandbox.history[self.commandbox.histindex])) | |
613 | elif e.GetKeyCode() == wx.WXK_DOWN: | |
614 | if self.commandbox.histindex == len(self.commandbox.history): | |
615 | self.commandbox.history.append(self.commandbox.GetValue()) # save current command | |
616 | if len(self.commandbox.history): | |
617 | self.commandbox.histindex = (self.commandbox.histindex + 1) % len(self.commandbox.history) | |
618 | self.commandbox.SetValue(self.commandbox.history[self.commandbox.histindex]) | |
619 | self.commandbox.SetSelection(0, len(self.commandbox.history[self.commandbox.histindex])) | |
620 | else: | |
621 | e.Skip() | |
622 | ||
623 | def plate(self, e): | |
624 | from . import stlplater as plater | |
625 | self.log(_("Plate function activated")) | |
626 | plater.StlPlater(size = (800, 580), callback = self.platecb, | |
627 | parent = self, | |
628 | build_dimensions = self.build_dimensions_list, | |
629 | circular_platform = self.settings.circular_bed, | |
630 | simarrange_path = self.settings.simarrange_path, | |
631 | antialias_samples = int(self.settings.antialias3dsamples)).Show() | |
632 | ||
633 | def plate_gcode(self, e): | |
634 | from . import gcodeplater as plater | |
635 | self.log(_("G-Code plate function activated")) | |
636 | plater.GcodePlater(size = (800, 580), callback = self.platecb, | |
637 | parent = self, | |
638 | build_dimensions = self.build_dimensions_list, | |
639 | circular_platform = self.settings.circular_bed, | |
640 | antialias_samples = int(self.settings.antialias3dsamples)).Show() | |
641 | ||
642 | def platecb(self, name): | |
643 | self.log(_("Plated %s") % name) | |
644 | self.loadfile(None, name) | |
645 | if self.settings.uimode in (_("Tabbed"), _("Tabbed with platers")): | |
646 | # Switch to page 1 (Status tab) | |
647 | self.notebook.SetSelection(1) | |
648 | ||
649 | def do_editgcode(self, e = None): | |
650 | if self.filename is not None: | |
651 | MacroEditor(self.filename, [line.raw for line in self.fgcode], self.doneediting, True) | |
652 | ||
653 | def doneediting(self, gcode): | |
654 | open(self.filename, "w").write("\n".join(gcode)) | |
655 | wx.CallAfter(self.loadfile, None, self.filename) | |
656 | ||
657 | def sdmenu(self, e): | |
658 | obj = e.GetEventObject() | |
659 | popupmenu = wx.Menu() | |
660 | item = popupmenu.Append(-1, _("SD Upload")) | |
661 | if not self.fgcode: | |
662 | item.Enable(False) | |
663 | self.Bind(wx.EVT_MENU, self.upload, id = item.GetId()) | |
664 | item = popupmenu.Append(-1, _("SD Print")) | |
665 | self.Bind(wx.EVT_MENU, self.sdprintfile, id = item.GetId()) | |
666 | self.panel.PopupMenu(popupmenu, obj.GetPosition()) | |
667 | ||
668 | def htemp_change(self, event): | |
669 | if self.hsetpoint > 0: | |
670 | self.do_settemp("") | |
671 | wx.CallAfter(self.htemp.SetInsertionPoint, 0) | |
672 | ||
673 | def btemp_change(self, event): | |
674 | if self.bsetpoint > 0: | |
675 | self.do_bedtemp("") | |
676 | wx.CallAfter(self.btemp.SetInsertionPoint, 0) | |
677 | ||
678 | def tool_change(self, event): | |
679 | self.do_tool(self.extrudersel.GetValue()) | |
680 | ||
681 | def show_viz_window(self, event): | |
682 | if self.fgcode: | |
683 | self.gwindow.Show(True) | |
684 | self.gwindow.SetToolTip(wx.ToolTip("Mousewheel zooms the display\nShift / Mousewheel scrolls layers")) | |
685 | self.gwindow.Raise() | |
686 | ||
687 | def setfeeds(self, e): | |
688 | self.feedrates_changed = True | |
689 | try: | |
690 | if self.efeedc is not None: | |
691 | self.settings._set("e_feedrate", self.efeedc.GetValue()) | |
692 | except: | |
693 | pass | |
694 | try: | |
695 | self.settings._set("z_feedrate", self.zfeedc.GetValue()) | |
696 | except: | |
697 | pass | |
698 | try: | |
699 | self.settings._set("xy_feedrate", self.xyfeedc.GetValue()) | |
700 | except: | |
701 | pass | |
702 | try: | |
703 | self.settings._set("last_extrusion", self.edist.GetValue()) | |
704 | except: | |
705 | pass | |
706 | ||
707 | def homeButtonClicked(self, axis): | |
708 | # When user clicks on the XY control, the Z control no longer gets spacebar/repeat signals | |
709 | self.zb.clearRepeat() | |
710 | if axis == "x": | |
711 | self.onecmd('home X') | |
712 | elif axis == "y": # upper-right | |
713 | self.onecmd('home Y') | |
714 | elif axis == "z": | |
715 | self.onecmd('home Z') | |
716 | elif axis == "all": | |
717 | self.onecmd('home') | |
718 | elif axis == "center": | |
719 | center_x = self.build_dimensions_list[0] / 2 + self.build_dimensions_list[3] | |
720 | center_y = self.build_dimensions_list[1] / 2 + self.build_dimensions_list[4] | |
721 | feed = self.settings.xy_feedrate | |
722 | self.onecmd('G0 X%s Y%s F%s' % (center_x, center_y, feed)) | |
723 | else: | |
724 | return | |
725 | self.p.send_now('M114') | |
726 | ||
727 | def clamped_move_message(self): | |
728 | self.log(_("Manual move outside of the build volume prevented (see the \"Clamp manual moves\" option).")) | |
729 | ||
730 | def moveXY(self, x, y): | |
731 | # When user clicks on the XY control, the Z control no longer gets spacebar/repeat signals | |
732 | self.zb.clearRepeat() | |
733 | if x != 0: | |
734 | if self.settings.clamp_jogging: | |
735 | new_x = self.current_pos[0] + x | |
736 | if new_x < self.build_dimensions_list[3] or new_x > self.build_dimensions_list[0] + self.build_dimensions_list[3]: | |
737 | self.clamped_move_message() | |
738 | return | |
739 | self.onecmd('move X %s' % x) | |
740 | elif y != 0: | |
741 | if self.settings.clamp_jogging: | |
742 | new_y = self.current_pos[1] + y | |
743 | if new_y < self.build_dimensions_list[4] or new_y > self.build_dimensions_list[1] + self.build_dimensions_list[4]: | |
744 | self.clamped_move_message() | |
745 | return | |
746 | self.onecmd('move Y %s' % y) | |
747 | else: | |
748 | return | |
749 | self.p.send_now('M114') | |
750 | ||
751 | def moveZ(self, z): | |
752 | if z != 0: | |
753 | if self.settings.clamp_jogging: | |
754 | new_z = self.current_pos[2] + z | |
755 | if new_z < self.build_dimensions_list[5] or new_z > self.build_dimensions_list[2] + self.build_dimensions_list[5]: | |
756 | self.clamped_move_message() | |
757 | return | |
758 | self.onecmd('move Z %s' % z) | |
759 | self.p.send_now('M114') | |
760 | # When user clicks on the Z control, the XY control no longer gets spacebar/repeat signals | |
761 | self.xyb.clearRepeat() | |
762 | ||
763 | def spacebarAction(self): | |
764 | self.zb.repeatLast() | |
765 | self.xyb.repeatLast() | |
766 | ||
767 | # -------------------------------------------------------------- | |
768 | # Console handling | |
769 | # -------------------------------------------------------------- | |
770 | ||
771 | def catchprint(self, l): | |
772 | """Called by the Tee operator to write to the log box""" | |
773 | if not self.IsFrozen(): | |
774 | wx.CallAfter(self.addtexttolog, l) | |
775 | ||
776 | def addtexttolog(self, text): | |
777 | try: | |
778 | self.logbox.AppendText(text) | |
779 | max_length = 20000 | |
780 | current_length = self.logbox.GetLastPosition() | |
781 | if current_length > max_length: | |
782 | self.logbox.Remove(0, current_length / 10) | |
783 | except: | |
784 | self.log(_("Attempted to write invalid text to console, which could be due to an invalid baudrate")) | |
785 | ||
786 | def clear_log(self, e): | |
787 | self.logbox.Clear() | |
788 | ||
789 | def set_verbose_communications(self, e): | |
790 | self.p.loud = e.IsChecked() | |
791 | ||
792 | def sendline(self, e): | |
793 | command = self.commandbox.GetValue() | |
794 | if not len(command): | |
795 | return | |
796 | wx.CallAfter(self.addtexttolog, ">>> " + command + "\n") | |
797 | line = self.precmd(str(command)) | |
798 | self.onecmd(line) | |
799 | self.commandbox.SetSelection(0, len(command)) | |
800 | self.commandbox.history.append(command) | |
801 | self.commandbox.histindex = len(self.commandbox.history) | |
802 | ||
803 | # -------------------------------------------------------------- | |
804 | # Main menu handling & actions | |
805 | # -------------------------------------------------------------- | |
806 | ||
807 | def create_menu(self): | |
808 | """Create main menu""" | |
809 | self.menustrip = wx.MenuBar() | |
810 | # File menu | |
811 | m = wx.Menu() | |
812 | self.Bind(wx.EVT_MENU, self.loadfile, m.Append(-1, _("&Open..."), _(" Open file"))) | |
813 | self.savebtn = m.Append(-1, _("&Save..."), _(" Save file")) | |
814 | self.savebtn.Enable(False) | |
815 | self.Bind(wx.EVT_MENU, self.savefile, self.savebtn) | |
816 | ||
817 | self.filehistory = wx.FileHistory(maxFiles = 8, idBase = wx.ID_FILE1) | |
818 | recent = wx.Menu() | |
819 | self.filehistory.UseMenu(recent) | |
820 | self.Bind(wx.EVT_MENU_RANGE, self.load_recent_file, | |
821 | id = wx.ID_FILE1, id2 = wx.ID_FILE9) | |
822 | m.AppendMenu(wx.ID_ANY, _("&Recent Files"), recent) | |
823 | self.Bind(wx.EVT_MENU, self.clear_log, m.Append(-1, _("Clear console"), _(" Clear output console"))) | |
824 | self.Bind(wx.EVT_MENU, self.on_exit, m.Append(wx.ID_EXIT, _("E&xit"), _(" Closes the Window"))) | |
825 | self.menustrip.Append(m, _("&File")) | |
826 | ||
827 | m = wx.Menu() | |
828 | self.Bind(wx.EVT_MENU, self.do_editgcode, m.Append(-1, _("&Edit..."), _(" Edit open file"))) | |
829 | self.Bind(wx.EVT_MENU, self.plate, m.Append(-1, _("Plater"), _(" Compose 3D models into a single plate"))) | |
830 | self.Bind(wx.EVT_MENU, self.plate_gcode, m.Append(-1, _("G-Code Plater"), _(" Compose G-Codes into a single plate"))) | |
831 | self.Bind(wx.EVT_MENU, self.exclude, m.Append(-1, _("Excluder"), _(" Exclude parts of the bed from being printed"))) | |
832 | self.Bind(wx.EVT_MENU, self.project, m.Append(-1, _("Projector"), _(" Project slices"))) | |
833 | self.menustrip.Append(m, _("&Tools")) | |
834 | ||
835 | m = wx.Menu() | |
836 | self.recoverbtn = m.Append(-1, _("Recover"), _(" Recover previous print after a disconnect (homes X, Y, restores Z and E status)")) | |
837 | self.recoverbtn.Disable = lambda *a: self.recoverbtn.Enable(False) | |
838 | self.Bind(wx.EVT_MENU, self.recover, self.recoverbtn) | |
839 | self.menustrip.Append(m, _("&Advanced")) | |
840 | ||
841 | if self.settings.slic3rintegration: | |
842 | m = wx.Menu() | |
843 | print_menu = wx.Menu() | |
844 | filament_menu = wx.Menu() | |
845 | printer_menu = wx.Menu() | |
846 | m.AppendSubMenu(print_menu, _("Print &settings")) | |
847 | m.AppendSubMenu(filament_menu, _("&Filament")) | |
848 | m.AppendSubMenu(printer_menu, _("&Printer")) | |
849 | menus = {"print": print_menu, | |
850 | "filament": filament_menu, | |
851 | "printer": printer_menu} | |
852 | try: | |
853 | self.load_slic3r_configs(menus) | |
854 | self.menustrip.Append(m, _("&Slic3r")) | |
855 | except IOError: | |
856 | self.logError(_("Failed to load Slic3r configuration:") + | |
857 | "\n" + traceback.format_exc()) | |
858 | ||
859 | # Settings menu | |
860 | m = wx.Menu() | |
861 | self.macros_menu = wx.Menu() | |
862 | m.AppendSubMenu(self.macros_menu, _("&Macros")) | |
863 | self.Bind(wx.EVT_MENU, self.new_macro, self.macros_menu.Append(-1, _("<&New...>"))) | |
864 | self.Bind(wx.EVT_MENU, lambda *e: PronterOptions(self), m.Append(-1, _("&Options"), _(" Options dialog"))) | |
865 | ||
866 | self.Bind(wx.EVT_MENU, lambda x: threading.Thread(target = lambda: self.do_slice("set")).start(), m.Append(-1, _("Slicing settings"), _(" Adjust slicing settings"))) | |
867 | ||
868 | mItem = m.AppendCheckItem(-1, _("Debug communications"), | |
869 | _("Print all G-code sent to and received from the printer.")) | |
870 | m.Check(mItem.GetId(), self.p.loud) | |
871 | self.Bind(wx.EVT_MENU, self.set_verbose_communications, mItem) | |
872 | ||
873 | self.menustrip.Append(m, _("&Settings")) | |
874 | self.update_macros_menu() | |
875 | self.SetMenuBar(self.menustrip) | |
876 | ||
877 | m = wx.Menu() | |
878 | self.Bind(wx.EVT_MENU, self.about, | |
879 | m.Append(-1, _("&About Printrun"), _("Show about dialog"))) | |
880 | self.menustrip.Append(m, _("&Help")) | |
881 | ||
882 | def project(self, event): | |
883 | """Start Projector tool""" | |
884 | from printrun import projectlayer | |
885 | projectlayer.SettingsFrame(self, self.p).Show() | |
886 | ||
887 | def exclude(self, event): | |
888 | """Start part excluder tool""" | |
889 | if not self.fgcode: | |
890 | wx.CallAfter(self.statusbar.SetStatusText, _("No file loaded. Please use load first.")) | |
891 | return | |
892 | if not self.excluder: | |
893 | from .excluder import Excluder | |
894 | self.excluder = Excluder() | |
895 | self.excluder.pop_window(self.fgcode, bgcolor = self.bgcolor, | |
896 | build_dimensions = self.build_dimensions_list) | |
897 | ||
898 | def about(self, event): | |
899 | """Show about dialog""" | |
900 | ||
901 | info = wx.AboutDialogInfo() | |
902 | ||
903 | info.SetIcon(wx.Icon(iconfile("pronterface.png"), wx.BITMAP_TYPE_PNG)) | |
904 | info.SetName('Printrun') | |
905 | info.SetVersion(printcore.__version__) | |
906 | ||
907 | description = _("Printrun is a pure Python 3D printing" | |
908 | " (and other types of CNC) host software.") | |
909 | ||
910 | description += "\n\n" + \ | |
911 | _("%.02fmm of filament have been extruded during prints") \ | |
912 | % self.settings.total_filament_used | |
913 | ||
914 | info.SetDescription(description) | |
915 | info.SetCopyright('(C) 2011 - 2015') | |
916 | info.SetWebSite('https://github.com/kliment/Printrun') | |
917 | ||
918 | licence = """\ | |
919 | Printrun is free software: you can redistribute it and/or modify it under the | |
920 | terms of the GNU General Public License as published by the Free Software | |
921 | Foundation, either version 3 of the License, or (at your option) any later | |
922 | version. | |
923 | ||
924 | Printrun is distributed in the hope that it will be useful, but WITHOUT ANY | |
925 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A | |
926 | PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
927 | ||
928 | You should have received a copy of the GNU General Public License along with | |
929 | Printrun. If not, see <http://www.gnu.org/licenses/>.""" | |
930 | ||
931 | info.SetLicence(licence) | |
932 | info.AddDeveloper('Kliment Yanev') | |
933 | info.AddDeveloper('Guillaume Seguin') | |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
20
diff
changeset
|
934 | info.AddDeveloper('Malte Bayer') |
15 | 935 | |
936 | wx.AboutBox(info) | |
937 | ||
938 | # -------------------------------------------------------------- | |
939 | # Settings & command line handling (including update callbacks) | |
940 | # -------------------------------------------------------------- | |
941 | def _add_settings(self, size): | |
34 | 942 | self._lc_add_settings(size) |
22
4c9bb8f93ae8
Added the Lasercut settings to the pronterface options dialog
mbayer
parents:
20
diff
changeset
|
943 | |
15 | 944 | self.settings._add(BooleanSetting("monitor", True, _("Monitor printer status"), _("Regularly monitor printer temperatures (required to have functional temperature graph or gauges)"), "Printer"), self.update_monitor) |
945 | self.settings._add(StringSetting("simarrange_path", "", _("Simarrange command"), _("Path to the simarrange binary to use in the STL plater"), "External")) | |
946 | self.settings._add(BooleanSetting("circular_bed", False, _("Circular build platform"), _("Draw a circular (or oval) build platform instead of a rectangular one"), "Printer"), self.update_bed_viz) | |
947 | self.settings._add(SpinSetting("extruders", 0, 1, 5, _("Extruders count"), _("Number of extruders"), "Printer")) | |
948 | self.settings._add(BooleanSetting("clamp_jogging", False, _("Clamp manual moves"), _("Prevent manual moves from leaving the specified build dimensions"), "Printer")) | |
949 | self.settings._add(ComboSetting("uimode", _("Standard"), [_("Standard"), _("Compact"), _("Tabbed"), _("Tabbed with platers")], _("Interface mode"), _("Standard interface is a one-page, three columns layout with controls/visualization/log\nCompact mode is a one-page, two columns layout with controls + log/visualization\nTabbed mode is a two-pages mode, where the first page shows controls and the second one shows visualization and log.\nTabbed with platers mode is the same as Tabbed, but with two extra pages for the STL and G-Code platers."), "UI"), self.reload_ui) | |
950 | self.settings._add(ComboSetting("controlsmode", "Standard", ["Standard", "Mini"], _("Controls mode"), _("Standard controls include all controls needed for printer setup and calibration, while Mini controls are limited to the ones needed for daily printing"), "UI"), self.reload_ui) | |
951 | self.settings._add(BooleanSetting("slic3rintegration", False, _("Enable Slic3r integration"), _("Add a menu to select Slic3r profiles directly from Pronterface"), "UI"), self.reload_ui) | |
952 | self.settings._add(BooleanSetting("slic3rupdate", False, _("Update Slic3r default presets"), _("When selecting a profile in Slic3r integration menu, also save it as the default Slic3r preset"), "UI")) | |
953 | self.settings._add(ComboSetting("mainviz", "3D", ["2D", "3D", "None"], _("Main visualization"), _("Select visualization for main window."), "Viewer"), self.reload_ui) | |
954 | self.settings._add(BooleanSetting("viz3d", False, _("Use 3D in GCode viewer window"), _("Use 3D mode instead of 2D layered mode in the visualization window"), "Viewer"), self.reload_ui) | |
955 | self.settings._add(StaticTextSetting("separator_3d_viewer", _("3D viewer options"), "", group = "Viewer")) | |
956 | self.settings._add(BooleanSetting("light3d", False, _("Use a lighter 3D visualization"), _("Use a lighter visualization with simple lines instead of extruded paths for 3D viewer"), "Viewer"), self.reload_ui) | |
957 | self.settings._add(ComboSetting("antialias3dsamples", "0", ["0", "2", "4", "8"], _("Number of anti-aliasing samples"), _("Amount of anti-aliasing samples used in the 3D viewer"), "Viewer"), self.reload_ui) | |
958 | self.settings._add(BooleanSetting("trackcurrentlayer3d", False, _("Track current layer in main 3D view"), _("Track the currently printing layer in the main 3D visualization"), "Viewer")) | |
959 | self.settings._add(FloatSpinSetting("gcview_path_width", 0.4, 0.01, 2, _("Extrusion width for 3D viewer"), _("Width of printed path in 3D viewer"), "Viewer", increment = 0.05), self.update_gcview_params) | |
960 | self.settings._add(FloatSpinSetting("gcview_path_height", 0.3, 0.01, 2, _("Layer height for 3D viewer"), _("Height of printed path in 3D viewer"), "Viewer", increment = 0.05), self.update_gcview_params) | |
961 | self.settings._add(BooleanSetting("tempgraph", True, _("Display temperature graph"), _("Display time-lapse temperature graph"), "UI"), self.reload_ui) | |
962 | self.settings._add(BooleanSetting("tempgauges", False, _("Display temperature gauges"), _("Display graphical gauges for temperatures visualization"), "UI"), self.reload_ui) | |
963 | self.settings._add(BooleanSetting("lockbox", False, _("Display interface lock checkbox"), _("Display a checkbox that, when check, locks most of Pronterface"), "UI"), self.reload_ui) | |
964 | self.settings._add(BooleanSetting("lockonstart", False, _("Lock interface upon print start"), _("If lock checkbox is enabled, lock the interface when starting a print"), "UI")) | |
965 | self.settings._add(BooleanSetting("refreshwhenloading", True, _("Update UI during G-Code load"), _("Regularly update visualization during the load of a G-Code file"), "UI")) | |
966 | self.settings._add(HiddenSetting("last_window_width", size[0])) | |
967 | self.settings._add(HiddenSetting("last_window_height", size[1])) | |
968 | self.settings._add(HiddenSetting("last_window_maximized", False)) | |
969 | self.settings._add(HiddenSetting("last_sash_position", -1)) | |
970 | self.settings._add(HiddenSetting("last_bed_temperature", 0.0)) | |
971 | self.settings._add(HiddenSetting("last_file_path", u"")) | |
972 | self.settings._add(HiddenSetting("last_file_filter", 0)) | |
973 | self.settings._add(HiddenSetting("last_temperature", 0.0)) | |
974 | self.settings._add(StaticTextSetting("separator_2d_viewer", _("2D viewer options"), "", group = "Viewer")) | |
975 | self.settings._add(FloatSpinSetting("preview_extrusion_width", 0.5, 0, 10, _("Preview extrusion width"), _("Width of Extrusion in Preview"), "Viewer", increment = 0.1), self.update_gviz_params) | |
976 | self.settings._add(SpinSetting("preview_grid_step1", 10., 0, 200, _("Fine grid spacing"), _("Fine Grid Spacing"), "Viewer"), self.update_gviz_params) | |
977 | self.settings._add(SpinSetting("preview_grid_step2", 50., 0, 200, _("Coarse grid spacing"), _("Coarse Grid Spacing"), "Viewer"), self.update_gviz_params) | |
978 | self.settings._add(StringSetting("bgcolor", "#FFFFFF", _("Background color"), _("Pronterface background color"), "Colors"), self.reload_ui, validate = check_rgb_color) | |
979 | self.settings._add(StringSetting("gcview_color_background", "#FAFAC7FF", _("3D view background color"), _("Color of the 3D view background"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) | |
980 | self.settings._add(StringSetting("gcview_color_travel", "#99999999", _("3D view travel moves color"), _("Color of travel moves in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) | |
981 | self.settings._add(StringSetting("gcview_color_tool0", "#FF000099", _("3D view print moves color"), _("Color of print moves with tool 0 in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) | |
982 | self.settings._add(StringSetting("gcview_color_tool1", "#AC0DFF99", _("3D view tool 1 moves color"), _("Color of print moves with tool 1 in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) | |
983 | self.settings._add(StringSetting("gcview_color_tool2", "#FFCE0099", _("3D view tool 2 moves color"), _("Color of print moves with tool 2 in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) | |
984 | self.settings._add(StringSetting("gcview_color_tool3", "#FF009F99", _("3D view tool 3 moves color"), _("Color of print moves with tool 3 in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) | |
985 | self.settings._add(StringSetting("gcview_color_tool4", "#00FF8F99", _("3D view tool 4 moves color"), _("Color of print moves with tool 4 in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) | |
986 | self.settings._add(StringSetting("gcview_color_printed", "#33BF0099", _("3D view printed moves color"), _("Color of printed moves in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) | |
987 | self.settings._add(StringSetting("gcview_color_current", "#00E5FFCC", _("3D view current layer moves color"), _("Color of moves in current layer in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) | |
988 | self.settings._add(StringSetting("gcview_color_current_printed", "#196600CC", _("3D view printed current layer moves color"), _("Color of already printed moves from current layer in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) | |
989 | self.settings._add(StaticTextSetting("note1", _("Note:"), _("Changing some of these settings might require a restart to get effect"), group = "UI")) | |
990 | recentfilessetting = StringSetting("recentfiles", "[]") | |
991 | recentfilessetting.hidden = True | |
992 | self.settings._add(recentfilessetting, self.update_recent_files) | |
993 | ||
994 | def add_cmdline_arguments(self, parser): | |
995 | pronsole.pronsole.add_cmdline_arguments(self, parser) | |
996 | parser.add_argument('-a', '--autoconnect', help = _("automatically try to connect to printer on startup"), action = "store_true") | |
997 | ||
998 | def process_cmdline_arguments(self, args): | |
999 | pronsole.pronsole.process_cmdline_arguments(self, args) | |
1000 | self.autoconnect = args.autoconnect | |
1001 | ||
1002 | def update_recent_files(self, param, value): | |
1003 | if self.filehistory is None: | |
1004 | return | |
1005 | recent_files = [] | |
1006 | try: | |
1007 | recent_files = json.loads(value) | |
1008 | except: | |
1009 | self.logError(_("Failed to load recent files list:") + | |
1010 | "\n" + traceback.format_exc()) | |
1011 | # Clear history | |
1012 | while self.filehistory.GetCount(): | |
1013 | self.filehistory.RemoveFileFromHistory(0) | |
1014 | recent_files.reverse() | |
1015 | for f in recent_files: | |
1016 | self.filehistory.AddFileToHistory(f) | |
1017 | ||
1018 | def update_gviz_params(self, param, value): | |
1019 | params_map = {"preview_extrusion_width": "extrusion_width", | |
1020 | "preview_grid_step1": "grid", | |
1021 | "preview_grid_step2": "grid"} | |
1022 | if param not in params_map: | |
1023 | return | |
1024 | if not hasattr(self, "gviz"): | |
1025 | # GUI hasn't been loaded yet, ignore this setting | |
1026 | return | |
1027 | trueparam = params_map[param] | |
1028 | if hasattr(self.gviz, trueparam): | |
1029 | gviz = self.gviz | |
1030 | elif hasattr(self.gwindow, "p") and hasattr(self.gwindow.p, trueparam): | |
1031 | gviz = self.gwindow.p | |
1032 | else: | |
1033 | return | |
1034 | if trueparam == "grid": | |
1035 | try: | |
1036 | item = int(param[-1]) # extract list item position | |
1037 | grid = list(gviz.grid) | |
1038 | grid[item - 1] = value | |
1039 | value = tuple(grid) | |
1040 | except: | |
1041 | self.logError(traceback.format_exc()) | |
1042 | if hasattr(self.gviz, trueparam): | |
1043 | self.apply_gviz_params(self.gviz, trueparam, value) | |
1044 | if hasattr(self.gwindow, "p") and hasattr(self.gwindow.p, trueparam): | |
1045 | self.apply_gviz_params(self.gwindow.p, trueparam, value) | |
1046 | ||
1047 | def apply_gviz_params(self, widget, param, value): | |
1048 | setattr(widget, param, value) | |
1049 | widget.dirty = 1 | |
1050 | wx.CallAfter(widget.Refresh) | |
1051 | ||
1052 | def update_gcview_colors(self, param, value): | |
1053 | color = hexcolor_to_float(value, 4) | |
1054 | # This is sort of a hack: we copy the color values into the preexisting | |
1055 | # color tuple so that we don't need to update the tuple used by gcview | |
1056 | target_color = getattr(self, param) | |
1057 | for i, v in enumerate(color): | |
1058 | target_color[i] = v | |
1059 | wx.CallAfter(self.Refresh) | |
1060 | ||
1061 | def update_build_dimensions(self, param, value): | |
1062 | pronsole.pronsole.update_build_dimensions(self, param, value) | |
1063 | self.update_bed_viz() | |
1064 | ||
1065 | def update_bed_viz(self, *args): | |
1066 | """Update bed visualization when size/type changed""" | |
1067 | if hasattr(self, "gviz") and hasattr(self.gviz, "recreate_platform"): | |
1068 | self.gviz.recreate_platform(self.build_dimensions_list, self.settings.circular_bed) | |
1069 | if hasattr(self, "gwindow") and hasattr(self.gwindow, "recreate_platform"): | |
1070 | self.gwindow.recreate_platform(self.build_dimensions_list, self.settings.circular_bed) | |
1071 | ||
1072 | def update_gcview_params(self, *args): | |
1073 | need_reload = False | |
1074 | if hasattr(self, "gviz") and hasattr(self.gviz, "set_gcview_params"): | |
1075 | need_reload |= self.gviz.set_gcview_params(self.settings.gcview_path_width, self.settings.gcview_path_height) | |
1076 | if hasattr(self, "gwindow") and hasattr(self.gwindow, "set_gcview_params"): | |
1077 | need_reload |= self.gwindow.set_gcview_params(self.settings.gcview_path_width, self.settings.gcview_path_height) | |
1078 | if need_reload: | |
1079 | self.start_viz_thread() | |
1080 | ||
1081 | def update_monitor(self, *args): | |
1082 | if hasattr(self, "graph") and self.display_graph: | |
1083 | if self.settings.monitor: | |
1084 | wx.CallAfter(self.graph.StartPlotting, 1000) | |
1085 | else: | |
1086 | wx.CallAfter(self.graph.StopPlotting) | |
1087 | ||
1088 | # -------------------------------------------------------------- | |
1089 | # Statusbar handling | |
1090 | # -------------------------------------------------------------- | |
1091 | ||
1092 | def statuschecker_inner(self): | |
1093 | status_string = "" | |
1094 | if self.sdprinting or self.uploading or self.p.printing: | |
1095 | secondsremain, secondsestimate, progress = self.get_eta() | |
1096 | if self.sdprinting or self.uploading: | |
1097 | if self.uploading: | |
1098 | status_string += _("SD upload: %04.2f%% |") % (100 * progress,) | |
1099 | status_string += _(" Line# %d of %d lines |") % (self.p.queueindex, len(self.p.mainqueue)) | |
1100 | else: | |
1101 | status_string += _("SD printing: %04.2f%% |") % (self.percentdone,) | |
1102 | elif self.p.printing: | |
1103 | status_string += _("Printing: %04.2f%% |") % (100 * float(self.p.queueindex) / len(self.p.mainqueue),) | |
1104 | status_string += _(" Line# %d of %d lines |") % (self.p.queueindex, len(self.p.mainqueue)) | |
1105 | if progress > 0: | |
1106 | status_string += _(" Est: %s of %s remaining | ") % (format_duration(secondsremain), | |
1107 | format_duration(secondsestimate)) | |
1108 | status_string += _(" Z: %.3f mm") % self.curlayer | |
1109 | elif self.loading_gcode: | |
1110 | status_string = self.loading_gcode_message | |
1111 | wx.CallAfter(self.statusbar.SetStatusText, status_string) | |
1112 | wx.CallAfter(self.gviz.Refresh) | |
1113 | # Call pronsole's statuschecker inner loop function to handle | |
1114 | # temperature monitoring and status loop sleep | |
1115 | pronsole.pronsole.statuschecker_inner(self, self.settings.monitor) | |
1116 | try: | |
1117 | while not self.sentglines.empty(): | |
1118 | gc = self.sentglines.get_nowait() | |
1119 | wx.CallAfter(self.gviz.addgcodehighlight, gc) | |
1120 | self.sentglines.task_done() | |
1121 | except Queue.Empty: | |
1122 | pass | |
1123 | ||
1124 | def statuschecker(self): | |
1125 | pronsole.pronsole.statuschecker(self) | |
1126 | wx.CallAfter(self.statusbar.SetStatusText, _("Not connected to printer.")) | |
1127 | ||
1128 | # -------------------------------------------------------------- | |
1129 | # Interface lock handling | |
1130 | # -------------------------------------------------------------- | |
1131 | ||
1132 | def lock(self, event = None, force = None): | |
1133 | if force is not None: | |
1134 | self.locker.SetValue(force) | |
1135 | if self.locker.GetValue(): | |
1136 | self.log(_("Locking interface.")) | |
1137 | for panel in self.panels: | |
1138 | panel.Disable() | |
1139 | else: | |
1140 | self.log(_("Unlocking interface.")) | |
1141 | for panel in self.panels: | |
1142 | panel.Enable() | |
1143 | ||
1144 | # -------------------------------------------------------------- | |
1145 | # Printer connection handling | |
1146 | # -------------------------------------------------------------- | |
1147 | ||
1148 | def connect(self, event = None): | |
1149 | self.log(_("Connecting...")) | |
1150 | port = None | |
1151 | if self.serialport.GetValue(): | |
1152 | port = str(self.serialport.GetValue()) | |
1153 | else: | |
1154 | scanned = self.scanserial() | |
1155 | if scanned: | |
1156 | port = scanned[0] | |
1157 | baud = 115200 | |
1158 | try: | |
1159 | baud = int(self.baud.GetValue()) | |
1160 | except: | |
1161 | self.logError(_("Could not parse baud rate: ") | |
1162 | + "\n" + traceback.format_exc()) | |
1163 | if self.paused: | |
1164 | self.p.paused = 0 | |
1165 | self.p.printing = 0 | |
1166 | wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) | |
1167 | wx.CallAfter(self.printbtn.SetLabel, _("Print")) | |
1168 | wx.CallAfter(self.toolbarsizer.Layout) | |
1169 | self.paused = 0 | |
1170 | if self.sdprinting: | |
1171 | self.p.send_now("M26 S0") | |
1172 | if not self.connect_to_printer(port, baud, self.settings.dtr): | |
1173 | return | |
1174 | if port != self.settings.port: | |
1175 | self.set("port", port) | |
1176 | if baud != self.settings.baudrate: | |
1177 | self.set("baudrate", str(baud)) | |
1178 | if self.predisconnect_mainqueue: | |
1179 | self.recoverbtn.Enable() | |
1180 | ||
1181 | def store_predisconnect_state(self): | |
1182 | self.predisconnect_mainqueue = self.p.mainqueue | |
1183 | self.predisconnect_queueindex = self.p.queueindex | |
1184 | self.predisconnect_layer = self.curlayer | |
1185 | ||
1186 | def disconnect(self, event = None): | |
1187 | self.log(_("Disconnected.")) | |
1188 | if self.p.printing or self.p.paused or self.paused: | |
1189 | self.store_predisconnect_state() | |
1190 | self.p.disconnect() | |
1191 | self.statuscheck = False | |
1192 | if self.status_thread: | |
1193 | self.status_thread.join() | |
1194 | self.status_thread = None | |
1195 | ||
1196 | wx.CallAfter(self.connectbtn.SetLabel, _("Connect")) | |
1197 | wx.CallAfter(self.connectbtn.SetToolTip, wx.ToolTip(_("Connect to the printer"))) | |
1198 | wx.CallAfter(self.connectbtn.Bind, wx.EVT_BUTTON, self.connect) | |
1199 | ||
1200 | wx.CallAfter(self.gui_set_disconnected) | |
1201 | ||
1202 | if self.paused: | |
1203 | self.p.paused = 0 | |
1204 | self.p.printing = 0 | |
1205 | wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) | |
1206 | wx.CallAfter(self.printbtn.SetLabel, _("Print")) | |
1207 | self.paused = 0 | |
1208 | if self.sdprinting: | |
1209 | self.p.send_now("M26 S0") | |
1210 | ||
1211 | # Relayout the toolbar to handle new buttons size | |
1212 | wx.CallAfter(self.toolbarsizer.Layout) | |
1213 | ||
1214 | def reset(self, event): | |
1215 | self.log(_("Reset.")) | |
1216 | dlg = wx.MessageDialog(self, _("Are you sure you want to reset the printer?"), _("Reset?"), wx.YES | wx.NO) | |
1217 | if dlg.ShowModal() == wx.ID_YES: | |
1218 | self.p.reset() | |
1219 | self.sethotendgui(0) | |
1220 | self.setbedgui(0) | |
1221 | self.p.printing = 0 | |
1222 | wx.CallAfter(self.printbtn.SetLabel, _("Print")) | |
1223 | if self.paused: | |
1224 | self.p.paused = 0 | |
1225 | wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) | |
1226 | self.paused = 0 | |
1227 | wx.CallAfter(self.toolbarsizer.Layout) | |
1228 | dlg.Destroy() | |
1229 | ||
1230 | # -------------------------------------------------------------- | |
1231 | # Print/upload handling | |
1232 | # -------------------------------------------------------------- | |
1233 | ||
1234 | def on_startprint(self): | |
1235 | wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) | |
1236 | wx.CallAfter(self.pausebtn.Enable) | |
1237 | wx.CallAfter(self.printbtn.SetLabel, _("Restart")) | |
1238 | wx.CallAfter(self.toolbarsizer.Layout) | |
1239 | ||
34 | 1240 | wx.CallAfter(self.lc_printbtn.Disable) |
1241 | wx.CallAfter(self.lc_printbtn.SetLabel, _("Cut in progress")) | |
1242 | ||
15 | 1243 | def printfile(self, event): |
1244 | self.extra_print_time = 0 | |
1245 | if self.paused: | |
1246 | self.p.paused = 0 | |
1247 | self.paused = 0 | |
1248 | if self.sdprinting: | |
1249 | self.on_startprint() | |
1250 | self.p.send_now("M26 S0") | |
1251 | self.p.send_now("M24") | |
1252 | return | |
1253 | ||
1254 | if not self.fgcode: | |
1255 | wx.CallAfter(self.statusbar.SetStatusText, _("No file loaded. Please use load first.")) | |
1256 | return | |
1257 | if not self.p.online: | |
1258 | wx.CallAfter(self.statusbar.SetStatusText, _("Not connected to printer.")) | |
1259 | return | |
1260 | self.sdprinting = False | |
1261 | self.on_startprint() | |
1262 | self.p.startprint(self.fgcode) | |
33
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
1263 | self.pass_current = 1 |
15 | 1264 | |
1265 | def sdprintfile(self, event): | |
1266 | self.extra_print_time = 0 | |
1267 | self.on_startprint() | |
1268 | threading.Thread(target = self.getfiles).start() | |
1269 | ||
1270 | def upload(self, event): | |
1271 | if not self.fgcode: | |
1272 | return | |
1273 | if not self.p.online: | |
1274 | return | |
1275 | dlg = wx.TextEntryDialog(self, ("Enter a target filename in 8.3 format:"), _("Pick SD filename"), dosify(self.filename)) | |
1276 | if dlg.ShowModal() == wx.ID_OK: | |
1277 | self.p.send_now("M21") | |
1278 | self.p.send_now("M28 " + str(dlg.GetValue())) | |
1279 | self.recvlisteners.append(self.uploadtrigger) | |
1280 | dlg.Destroy() | |
1281 | ||
1282 | def uploadtrigger(self, l): | |
1283 | if "Writing to file" in l: | |
1284 | self.uploading = True | |
1285 | self.p.startprint(self.fgcode) | |
1286 | self.p.endcb = self.endupload | |
1287 | self.recvlisteners.remove(self.uploadtrigger) | |
1288 | elif "open failed, File" in l: | |
1289 | self.recvlisteners.remove(self.uploadtrigger) | |
1290 | ||
1291 | def endupload(self): | |
1292 | self.p.send_now("M29 ") | |
1293 | wx.CallAfter(self.statusbar.SetStatusText, _("File upload complete")) | |
1294 | time.sleep(0.5) | |
1295 | self.p.clear = True | |
1296 | self.uploading = False | |
1297 | ||
1298 | def pause(self, event = None): | |
1299 | if not self.paused: | |
1300 | self.log(_("Print paused at: %s") % format_time(time.time())) | |
1301 | if self.sdprinting: | |
1302 | self.p.send_now("M25") | |
1303 | else: | |
1304 | if not self.p.printing: | |
1305 | return | |
1306 | self.p.pause() | |
1307 | self.p.runSmallScript(self.pauseScript) | |
1308 | self.paused = True | |
1309 | # self.p.runSmallScript(self.pauseScript) | |
1310 | self.extra_print_time += int(time.time() - self.starttime) | |
1311 | wx.CallAfter(self.pausebtn.SetLabel, _("Resume")) | |
1312 | wx.CallAfter(self.toolbarsizer.Layout) | |
1313 | else: | |
1314 | self.log(_("Resuming.")) | |
1315 | self.paused = False | |
1316 | if self.sdprinting: | |
1317 | self.p.send_now("M24") | |
1318 | else: | |
1319 | self.p.resume() | |
1320 | wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) | |
1321 | wx.CallAfter(self.toolbarsizer.Layout) | |
1322 | ||
1323 | def recover(self, event): | |
1324 | self.extra_print_time = 0 | |
1325 | if not self.p.online: | |
1326 | wx.CallAfter(self.statusbar.SetStatusText, _("Not connected to printer.")) | |
1327 | return | |
1328 | # Reset Z | |
1329 | self.p.send_now("G92 Z%f" % self.predisconnect_layer) | |
1330 | # Home X and Y | |
1331 | self.p.send_now("G28 X Y") | |
1332 | self.on_startprint() | |
1333 | self.p.startprint(self.predisconnect_mainqueue, self.p.queueindex) | |
1334 | ||
1335 | # -------------------------------------------------------------- | |
1336 | # File loading handling | |
1337 | # -------------------------------------------------------------- | |
1338 | ||
1339 | def filesloaded(self): | |
1340 | dlg = wx.SingleChoiceDialog(self, _("Select the file to print"), _("Pick SD file"), self.sdfiles) | |
1341 | if dlg.ShowModal() == wx.ID_OK: | |
1342 | target = dlg.GetStringSelection() | |
1343 | if len(target): | |
1344 | self.recvlisteners.append(self.waitforsdresponse) | |
1345 | self.p.send_now("M23 " + target.lower()) | |
1346 | dlg.Destroy() | |
1347 | ||
1348 | def getfiles(self): | |
1349 | if not self.p.online: | |
1350 | self.sdfiles = [] | |
1351 | return | |
1352 | self.sdlisting = 0 | |
1353 | self.sdfiles = [] | |
1354 | self.recvlisteners.append(self.listfiles) | |
1355 | self.p.send_now("M21") | |
1356 | self.p.send_now("M20") | |
1357 | ||
1358 | def model_to_gcode_filename(self, filename): | |
1359 | suffix = "_export.gcode" | |
1360 | for ext in [".stl", ".obj"]: | |
1361 | filename = filename.replace(ext, suffix) | |
1362 | filename = filename.replace(ext.upper(), suffix) | |
1363 | return filename | |
1364 | ||
1365 | def slice_func(self): | |
1366 | try: | |
1367 | output_filename = self.model_to_gcode_filename(self.filename) | |
1368 | pararray = prepare_command(self.settings.slicecommand, | |
1369 | {"$s": self.filename, "$o": output_filename}) | |
1370 | if self.settings.slic3rintegration: | |
1371 | for cat, config in self.slic3r_configs.items(): | |
1372 | if config: | |
1373 | fpath = os.path.join(self.slic3r_configpath, cat, config) | |
1374 | pararray += ["--load", fpath] | |
1375 | self.log(_("Running ") + " ".join(pararray)) | |
1376 | self.slicep = subprocess.Popen(pararray, stderr = subprocess.STDOUT, stdout = subprocess.PIPE) | |
1377 | while True: | |
1378 | o = self.slicep.stdout.read(1) | |
1379 | if o == '' and self.slicep.poll() is not None: break | |
1380 | sys.stdout.write(o) | |
1381 | self.slicep.wait() | |
1382 | self.stopsf = 1 | |
1383 | except: | |
1384 | self.logError(_("Failed to execute slicing software: ") | |
1385 | + "\n" + traceback.format_exc()) | |
1386 | self.stopsf = 1 | |
1387 | ||
1388 | def slice_monitor(self): | |
1389 | while not self.stopsf: | |
1390 | try: | |
1391 | wx.CallAfter(self.statusbar.SetStatusText, _("Slicing...")) # +self.cout.getvalue().split("\n")[-1]) | |
1392 | except: | |
1393 | pass | |
1394 | time.sleep(0.1) | |
1395 | fn = self.filename | |
1396 | try: | |
1397 | self.load_gcode_async(self.model_to_gcode_filename(self.filename)) | |
1398 | except: | |
1399 | self.filename = fn | |
1400 | self.slicing = False | |
1401 | self.slicep = None | |
1402 | ||
1403 | def slice(self, filename): | |
1404 | wx.CallAfter(self.loadbtn.SetLabel, _("Cancel")) | |
1405 | wx.CallAfter(self.toolbarsizer.Layout) | |
1406 | self.log(_("Slicing ") + filename) | |
1407 | self.cout = StringIO.StringIO() | |
1408 | self.filename = filename | |
1409 | self.stopsf = 0 | |
1410 | self.slicing = True | |
1411 | threading.Thread(target = self.slice_func).start() | |
1412 | threading.Thread(target = self.slice_monitor).start() | |
1413 | ||
1414 | def cmdline_filename_callback(self, filename): | |
1415 | # Do nothing when processing a filename from command line, as we'll | |
1416 | # handle it when everything has been prepared | |
1417 | self.filename = filename | |
1418 | ||
1419 | def do_load(self, l): | |
1420 | if hasattr(self, 'slicing'): | |
1421 | self.loadfile(None, l) | |
1422 | else: | |
1423 | self._do_load(l) | |
1424 | ||
1425 | def load_recent_file(self, event): | |
1426 | fileid = event.GetId() - wx.ID_FILE1 | |
1427 | path = self.filehistory.GetHistoryFile(fileid) | |
1428 | self.loadfile(None, filename = path) | |
1429 | ||
1430 | def loadfile(self, event, filename = None): | |
1431 | if self.slicing and self.slicep is not None: | |
1432 | self.slicep.terminate() | |
1433 | return | |
1434 | basedir = self.settings.last_file_path | |
1435 | if not os.path.exists(basedir): | |
1436 | basedir = "." | |
1437 | try: | |
1438 | basedir = os.path.split(self.filename)[0] | |
1439 | except: | |
1440 | pass | |
1441 | dlg = None | |
1442 | if filename is None: | |
1443 | dlg = wx.FileDialog(self, _("Open file to print"), basedir, style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) | |
16
36d478bde840
Implemented svg, png and hpgl compilers to pronterface
mbayer
parents:
15
diff
changeset
|
1444 | # add image files to GCODE file list |
36d478bde840
Implemented svg, png and hpgl compilers to pronterface
mbayer
parents:
15
diff
changeset
|
1445 | dlg.SetWildcard(_("GCODE and Image files|*.gcode;*.gco;*.g;*.png;*.svg;*.hpgl;*.plt|OBJ, STL, and GCODE files (*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ)|*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ|GCODE files (*.gcode;*.gco;*.g)|*.gcode;*.gco;*.g|OBJ, STL files (*.stl;*.STL;*.obj;*.OBJ)|*.stl;*.STL;*.obj;*.OBJ|All Files (*.*)|*.*")) |
15 | 1446 | try: |
1447 | dlg.SetFilterIndex(self.settings.last_file_filter) | |
1448 | except: | |
1449 | pass | |
1450 | if filename or dlg.ShowModal() == wx.ID_OK: | |
1451 | if filename: | |
1452 | name = filename | |
1453 | else: | |
1454 | name = dlg.GetPath() | |
1455 | self.set("last_file_filter", dlg.GetFilterIndex()) | |
1456 | dlg.Destroy() | |
1457 | if not os.path.exists(name): | |
1458 | self.statusbar.SetStatusText(_("File not found!")) | |
1459 | return | |
1460 | path = os.path.split(name)[0] | |
1461 | if path != self.settings.last_file_path: | |
1462 | self.set("last_file_path", path) | |
1463 | try: | |
1464 | abspath = os.path.abspath(name) | |
1465 | recent_files = [] | |
1466 | try: | |
1467 | recent_files = json.loads(self.settings.recentfiles) | |
1468 | except: | |
1469 | self.logError(_("Failed to load recent files list:") + | |
1470 | "\n" + traceback.format_exc()) | |
1471 | if abspath in recent_files: | |
1472 | recent_files.remove(abspath) | |
1473 | recent_files.insert(0, abspath) | |
1474 | if len(recent_files) > 5: | |
1475 | recent_files = recent_files[:5] | |
1476 | self.set("recentfiles", json.dumps(recent_files)) | |
1477 | except: | |
1478 | self.logError(_("Could not update recent files list:") + | |
1479 | "\n" + traceback.format_exc()) | |
19 | 1480 | |
20 | 1481 | # reload the library local so we dont have to restart the whole app when making code changes |
1482 | reload(laser) | |
19 | 1483 | |
15 | 1484 | if name.lower().endswith(".stl") or name.lower().endswith(".obj"): |
1485 | self.slice(name) | |
16
36d478bde840
Implemented svg, png and hpgl compilers to pronterface
mbayer
parents:
15
diff
changeset
|
1486 | elif name.lower().endswith(".png") or name.lower().endswith(".jpg") or name.lower().endswith(".gif"): |
36d478bde840
Implemented svg, png and hpgl compilers to pronterface
mbayer
parents:
15
diff
changeset
|
1487 | # Generate GCODE from IMAGE |
20 | 1488 | lc = laser.Lasercutter(pronterwindow = self) |
16
36d478bde840
Implemented svg, png and hpgl compilers to pronterface
mbayer
parents:
15
diff
changeset
|
1489 | lc.image2gcode(name) |
42
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
36
diff
changeset
|
1490 | wx.CallAfter(self.endcb_lasercut) |
16
36d478bde840
Implemented svg, png and hpgl compilers to pronterface
mbayer
parents:
15
diff
changeset
|
1491 | elif name.lower().endswith(".svg"): |
36d478bde840
Implemented svg, png and hpgl compilers to pronterface
mbayer
parents:
15
diff
changeset
|
1492 | # Generate GCODE from SVG |
20 | 1493 | lc = laser.Lasercutter(pronterwindow = self) |
16
36d478bde840
Implemented svg, png and hpgl compilers to pronterface
mbayer
parents:
15
diff
changeset
|
1494 | lc.svg2gcode(name) |
42
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
36
diff
changeset
|
1495 | wx.CallAfter(self.endcb_lasercut) |
16
36d478bde840
Implemented svg, png and hpgl compilers to pronterface
mbayer
parents:
15
diff
changeset
|
1496 | elif name.lower().endswith(".hpgl") or name.lower().endswith(".plt"): |
36d478bde840
Implemented svg, png and hpgl compilers to pronterface
mbayer
parents:
15
diff
changeset
|
1497 | # Generate GCODE from HPGL |
20 | 1498 | lc = laser.Lasercutter(pronterwindow = self) |
16
36d478bde840
Implemented svg, png and hpgl compilers to pronterface
mbayer
parents:
15
diff
changeset
|
1499 | lc.hpgl2gcode(name) |
42
ea4c43494a19
bugfixes on rastered image column endings, force laser off with G0 + little offset
mdd
parents:
36
diff
changeset
|
1500 | wx.CallAfter(self.endcb_lasercut) |
15 | 1501 | else: |
1502 | self.load_gcode_async(name) | |
1503 | else: | |
1504 | dlg.Destroy() | |
1505 | ||
1506 | def load_gcode_async(self, filename): | |
1507 | self.filename = filename | |
1508 | gcode = self.pre_gcode_load() | |
1509 | self.log(_("Loading file: %s") % filename) | |
1510 | threading.Thread(target = self.load_gcode_async_thread, args = (gcode,)).start() | |
1511 | ||
1512 | def load_gcode_async_thread(self, gcode): | |
1513 | try: | |
1514 | self.load_gcode(self.filename, | |
1515 | layer_callback = self.layer_ready_cb, | |
1516 | gcode = gcode) | |
1517 | except PronterfaceQuitException: | |
1518 | return | |
1519 | wx.CallAfter(self.post_gcode_load) | |
1520 | ||
1521 | def layer_ready_cb(self, gcode, layer): | |
1522 | global pronterface_quitting | |
1523 | if pronterface_quitting: | |
1524 | raise PronterfaceQuitException | |
1525 | if not self.settings.refreshwhenloading: | |
1526 | return | |
1527 | self.viz_last_layer = layer | |
1528 | if time.time() - self.viz_last_yield > 1.0: | |
1529 | time.sleep(0.2) | |
1530 | self.loading_gcode_message = _("Loading %s: %d layers loaded (%d lines)") % (self.filename, layer + 1, len(gcode)) | |
1531 | self.viz_last_yield = time.time() | |
1532 | wx.CallAfter(self.statusbar.SetStatusText, self.loading_gcode_message) | |
1533 | ||
1534 | def start_viz_thread(self, gcode = None): | |
1535 | threading.Thread(target = self.loadviz, args = (gcode,)).start() | |
1536 | ||
1537 | def pre_gcode_load(self): | |
1538 | self.loading_gcode = True | |
1539 | self.loading_gcode_message = _("Loading %s...") % self.filename | |
1540 | if self.settings.mainviz == "None": | |
1541 | gcode = gcoder.LightGCode(deferred = True) | |
1542 | else: | |
1543 | gcode = gcoder.GCode(deferred = True) | |
1544 | self.viz_last_yield = 0 | |
1545 | self.viz_last_layer = -1 | |
1546 | self.start_viz_thread(gcode) | |
1547 | return gcode | |
1548 | ||
1549 | def post_gcode_load(self, print_stats = True): | |
1550 | # Must be called in wx.CallAfter for safety | |
1551 | self.loading_gcode = False | |
1552 | self.SetTitle(_(u"Pronterface - %s") % self.filename) | |
1553 | message = _("Loaded %s, %d lines") % (self.filename, len(self.fgcode),) | |
1554 | self.log(message) | |
1555 | self.statusbar.SetStatusText(message) | |
1556 | self.savebtn.Enable(True) | |
1557 | self.loadbtn.SetLabel(_("Load File")) | |
1558 | self.printbtn.SetLabel(_("Print")) | |
1559 | self.pausebtn.SetLabel(_("Pause")) | |
1560 | self.pausebtn.Disable() | |
1561 | self.recoverbtn.Disable() | |
1562 | if self.p.online: | |
1563 | self.printbtn.Enable() | |
1564 | self.toolbarsizer.Layout() | |
1565 | self.viz_last_layer = None | |
1566 | if print_stats: | |
1567 | self.output_gcode_stats() | |
1568 | ||
1569 | def output_gcode_stats(self): | |
1570 | gcode = self.fgcode | |
1571 | self.log(_("%.2fmm of filament used in this print") % gcode.filament_length) | |
1572 | if(len(gcode.filament_length_multi)>1): | |
1573 | for i in enumerate(gcode.filament_length_multi): | |
1574 | print "Extruder %d: %0.02fmm" % (i[0],i[1]) | |
1575 | self.log(_("The print goes:")) | |
1576 | self.log(_("- from %.2f mm to %.2f mm in X and is %.2f mm wide") % (gcode.xmin, gcode.xmax, gcode.width)) | |
1577 | self.log(_("- from %.2f mm to %.2f mm in Y and is %.2f mm deep") % (gcode.ymin, gcode.ymax, gcode.depth)) | |
1578 | self.log(_("- from %.2f mm to %.2f mm in Z and is %.2f mm high") % (gcode.zmin, gcode.zmax, gcode.height)) | |
1579 | self.log(_("Estimated duration: %d layers, %s") % gcode.estimate_duration()) | |
1580 | ||
1581 | def loadviz(self, gcode = None): | |
1582 | self.gviz.clear() | |
1583 | self.gwindow.p.clear() | |
1584 | if gcode is not None: | |
1585 | generator = self.gviz.addfile_perlayer(gcode, True) | |
1586 | next_layer = 0 | |
1587 | # Progressive loading of visualization | |
1588 | # We load layers up to the last one which has been processed in GCoder | |
1589 | # (self.viz_last_layer) | |
1590 | # Once the GCode has been entirely loaded, this variable becomes None, | |
1591 | # indicating that we can do the last generator call to finish the | |
1592 | # loading of the visualization, which will itself return None. | |
1593 | # During preloading we verify that the layer we added is the one we | |
1594 | # expected through the assert call. | |
1595 | while True: | |
1596 | global pronterface_quitting | |
1597 | if pronterface_quitting: | |
1598 | return | |
1599 | max_layer = self.viz_last_layer | |
1600 | if max_layer is None: | |
1601 | break | |
1602 | while next_layer <= max_layer: | |
1603 | assert(generator.next() == next_layer) | |
1604 | next_layer += 1 | |
1605 | time.sleep(0.1) | |
1606 | generator_output = generator.next() | |
1607 | while generator_output is not None: | |
1608 | assert(generator_output in (None, next_layer)) | |
1609 | next_layer += 1 | |
1610 | generator_output = generator.next() | |
1611 | else: | |
1612 | # If GCode is not being loaded asynchroneously, it is already | |
1613 | # loaded, so let's make visualization sequentially | |
1614 | gcode = self.fgcode | |
1615 | self.gviz.addfile(gcode) | |
1616 | wx.CallAfter(self.gviz.Refresh) | |
1617 | # Load external window sequentially now that everything is ready. | |
1618 | # We can't really do any better as the 3D viewer might clone the | |
1619 | # finalized model from the main visualization | |
1620 | self.gwindow.p.addfile(gcode) | |
1621 | ||
1622 | # -------------------------------------------------------------- | |
1623 | # File saving handling | |
1624 | # -------------------------------------------------------------- | |
1625 | ||
1626 | def savefile(self, event): | |
1627 | basedir = self.settings.last_file_path | |
1628 | if not os.path.exists(basedir): | |
1629 | basedir = "." | |
1630 | try: | |
1631 | basedir = os.path.split(self.filename)[0] | |
1632 | except: | |
1633 | pass | |
1634 | dlg = wx.FileDialog(self, _("Save as"), basedir, style = wx.FD_SAVE) | |
1635 | dlg.SetWildcard(_("GCODE files (*.gcode;*.gco;*.g)|*.gcode;*.gco;*.g|All Files (*.*)|*.*")) | |
1636 | if dlg.ShowModal() == wx.ID_OK: | |
1637 | name = dlg.GetPath() | |
1638 | open(name, "w").write("\n".join((line.raw for line in self.fgcode))) | |
1639 | self.log(_("G-Code succesfully saved to %s") % name) | |
1640 | dlg.Destroy() | |
1641 | ||
1642 | # -------------------------------------------------------------- | |
1643 | # Printcore callbacks | |
1644 | # -------------------------------------------------------------- | |
1645 | ||
1646 | def process_host_command(self, command): | |
1647 | """Override host command handling""" | |
1648 | command = command.lstrip() | |
1649 | if command.startswith(";@pause"): | |
1650 | self.pause(None) | |
1651 | else: | |
1652 | pronsole.pronsole.process_host_command(self, command) | |
1653 | ||
1654 | def startcb(self, resuming = False): | |
1655 | """Callback on print start""" | |
1656 | pronsole.pronsole.startcb(self, resuming) | |
1657 | if self.settings.lockbox and self.settings.lockonstart: | |
1658 | wx.CallAfter(self.lock, force = True) | |
1659 | ||
1660 | def endcb(self): | |
1661 | """Callback on print end/pause""" | |
1662 | pronsole.pronsole.endcb(self) | |
1663 | if self.p.queueindex == 0: | |
1664 | self.p.runSmallScript(self.endScript) | |
1665 | wx.CallAfter(self.pausebtn.Disable) | |
1666 | wx.CallAfter(self.printbtn.SetLabel, _("Print")) | |
1667 | wx.CallAfter(self.toolbarsizer.Layout) | |
33
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
1668 | wx.CallAfter(self.endcb_lasercut) |
eee51ca7cbe7
Added support for multiple cutting passes with automatic Z refocusing
mdd
parents:
31
diff
changeset
|
1669 | |
15 | 1670 | def online(self): |
1671 | """Callback when printer goes online""" | |
1672 | self.log(_("Printer is now online.")) | |
1673 | wx.CallAfter(self.online_gui) | |
1674 | ||
1675 | def online_gui(self): | |
1676 | """Callback when printer goes online (graphical bits)""" | |
1677 | self.connectbtn.SetLabel(_("Disconnect")) | |
1678 | self.connectbtn.SetToolTip(wx.ToolTip("Disconnect from the printer")) | |
1679 | self.connectbtn.Bind(wx.EVT_BUTTON, self.disconnect) | |
1680 | ||
1681 | if hasattr(self, "extrudersel"): | |
1682 | self.do_tool(self.extrudersel.GetValue()) | |
1683 | ||
1684 | self.gui_set_connected() | |
1685 | ||
1686 | if self.filename: | |
1687 | self.printbtn.Enable() | |
1688 | ||
1689 | wx.CallAfter(self.toolbarsizer.Layout) | |
1690 | ||
1691 | def sentcb(self, line, gline): | |
1692 | """Callback when a printer gcode has been sent""" | |
1693 | if not gline: | |
1694 | pass | |
1695 | elif gline.command in ["M104", "M109"]: | |
1696 | gline_s = gcoder.S(gline) | |
1697 | if gline_s is not None: | |
1698 | temp = gline_s | |
1699 | if self.display_gauges: wx.CallAfter(self.hottgauge.SetTarget, temp) | |
1700 | if self.display_graph: wx.CallAfter(self.graph.SetExtruder0TargetTemperature, temp) | |
1701 | elif gline.command in ["M140", "M190"]: | |
1702 | gline_s = gcoder.S(gline) | |
1703 | if gline_s is not None: | |
1704 | temp = gline_s | |
1705 | if self.display_gauges: wx.CallAfter(self.bedtgauge.SetTarget, temp) | |
1706 | if self.display_graph: wx.CallAfter(self.graph.SetBedTargetTemperature, temp) | |
1707 | elif gline.command in ["M106"]: | |
1708 | gline_s=gcoder.S(gline) | |
1709 | fanpow=255 | |
1710 | if gline_s is not None: | |
1711 | fanpow=gline_s | |
1712 | if self.display_graph: wx.CallAfter(self.graph.SetFanPower, fanpow) | |
1713 | elif gline.command in ["M107"]: | |
1714 | if self.display_graph: wx.CallAfter(self.graph.SetFanPower, 0) | |
1715 | elif gline.command.startswith("T"): | |
1716 | tool = gline.command[1:] | |
1717 | if hasattr(self, "extrudersel"): wx.CallAfter(self.extrudersel.SetValue, tool) | |
1718 | if gline.is_move: | |
1719 | self.sentglines.put_nowait(gline) | |
1720 | ||
1721 | def is_excluded_move(self, gline): | |
1722 | """Check whether the given moves ends at a position specified as | |
1723 | excluded in the part excluder""" | |
1724 | if not gline.is_move or not self.excluder or not self.excluder.rectangles: | |
1725 | return False | |
1726 | for (x0, y0, x1, y1) in self.excluder.rectangles: | |
1727 | if x0 <= gline.current_x <= x1 and y0 <= gline.current_y <= y1: | |
1728 | return True | |
1729 | return False | |
1730 | ||
1731 | def preprintsendcb(self, gline, next_gline): | |
1732 | """Callback when a printer gcode is about to be sent. We use it to | |
1733 | exclude moves defined by the part excluder tool""" | |
1734 | if not self.is_excluded_move(gline): | |
1735 | return gline | |
1736 | else: | |
1737 | if gline.z is not None: | |
1738 | if gline.relative: | |
1739 | if self.excluder_z_abs is not None: | |
1740 | self.excluder_z_abs += gline.z | |
1741 | elif self.excluder_z_rel is not None: | |
1742 | self.excluder_z_rel += gline.z | |
1743 | else: | |
1744 | self.excluder_z_rel = gline.z | |
1745 | else: | |
1746 | self.excluder_z_rel = None | |
1747 | self.excluder_z_abs = gline.z | |
1748 | if gline.e is not None and not gline.relative_e: | |
1749 | self.excluder_e = gline.e | |
1750 | # If next move won't be excluded, push the changes we have to do | |
1751 | if next_gline is not None and not self.is_excluded_move(next_gline): | |
1752 | if self.excluder_e is not None: | |
1753 | self.p.send_now("G92 E%.5f" % self.excluder_e) | |
1754 | self.excluder_e = None | |
1755 | if self.excluder_z_abs is not None: | |
1756 | if gline.relative: | |
1757 | self.p.send_now("G90") | |
1758 | self.p.send_now("G1 Z%.5f" % self.excluder_z_abs) | |
1759 | self.excluder_z_abs = None | |
1760 | if gline.relative: | |
1761 | self.p.send_now("G91") | |
1762 | if self.excluder_z_rel is not None: | |
1763 | if not gline.relative: | |
1764 | self.p.send_now("G91") | |
1765 | self.p.send_now("G1 Z%.5f" % self.excluder_z_rel) | |
1766 | self.excluder_z_rel = None | |
1767 | if not gline.relative: | |
1768 | self.p.send_now("G90") | |
1769 | return None | |
1770 | ||
1771 | def printsentcb(self, gline): | |
1772 | """Callback when a print gcode has been sent""" | |
1773 | if gline.is_move: | |
1774 | if hasattr(self.gwindow, "set_current_gline"): | |
1775 | wx.CallAfter(self.gwindow.set_current_gline, gline) | |
1776 | if hasattr(self.gviz, "set_current_gline"): | |
1777 | wx.CallAfter(self.gviz.set_current_gline, gline) | |
1778 | ||
1779 | def layer_change_cb(self, newlayer): | |
1780 | """Callback when the printed layer changed""" | |
1781 | pronsole.pronsole.layer_change_cb(self, newlayer) | |
1782 | if self.settings.mainviz != "3D" or self.settings.trackcurrentlayer3d: | |
1783 | wx.CallAfter(self.gviz.setlayer, newlayer) | |
1784 | ||
1785 | def update_tempdisplay(self): | |
1786 | try: | |
1787 | temps = parse_temperature_report(self.tempreadings) | |
1788 | if "T0" in temps and temps["T0"][0]: | |
1789 | hotend_temp = float(temps["T0"][0]) | |
1790 | elif "T" in temps and temps["T"][0]: | |
1791 | hotend_temp = float(temps["T"][0]) | |
1792 | else: | |
1793 | hotend_temp = None | |
1794 | if hotend_temp is not None: | |
1795 | if self.display_graph: wx.CallAfter(self.graph.SetExtruder0Temperature, hotend_temp) | |
1796 | if self.display_gauges: wx.CallAfter(self.hottgauge.SetValue, hotend_temp) | |
1797 | setpoint = None | |
1798 | if "T0" in temps and temps["T0"][1]: setpoint = float(temps["T0"][1]) | |
1799 | elif temps["T"][1]: setpoint = float(temps["T"][1]) | |
1800 | if setpoint is not None: | |
1801 | if self.display_graph: wx.CallAfter(self.graph.SetExtruder0TargetTemperature, setpoint) | |
1802 | if self.display_gauges: wx.CallAfter(self.hottgauge.SetTarget, setpoint) | |
1803 | if "T1" in temps: | |
1804 | hotend_temp = float(temps["T1"][0]) | |
1805 | if self.display_graph: wx.CallAfter(self.graph.SetExtruder1Temperature, hotend_temp) | |
1806 | setpoint = temps["T1"][1] | |
1807 | if setpoint and self.display_graph: | |
1808 | wx.CallAfter(self.graph.SetExtruder1TargetTemperature, float(setpoint)) | |
1809 | bed_temp = float(temps["B"][0]) if "B" in temps and temps["B"][0] else None | |
1810 | if bed_temp is not None: | |
1811 | if self.display_graph: wx.CallAfter(self.graph.SetBedTemperature, bed_temp) | |
1812 | if self.display_gauges: wx.CallAfter(self.bedtgauge.SetValue, bed_temp) | |
1813 | setpoint = temps["B"][1] | |
1814 | if setpoint: | |
1815 | setpoint = float(setpoint) | |
1816 | if self.display_graph: wx.CallAfter(self.graph.SetBedTargetTemperature, setpoint) | |
1817 | if self.display_gauges: wx.CallAfter(self.bedtgauge.SetTarget, setpoint) | |
1818 | except: | |
1819 | self.logError(traceback.format_exc()) | |
1820 | ||
1821 | def update_pos(self): | |
1822 | bits = gcoder.m114_exp.findall(self.posreport) | |
1823 | x = None | |
1824 | y = None | |
1825 | z = None | |
1826 | for bit in bits: | |
1827 | if not bit[0]: continue | |
1828 | if x is None and bit[0] == "X": | |
1829 | x = float(bit[1]) | |
1830 | elif y is None and bit[0] == "Y": | |
1831 | y = float(bit[1]) | |
1832 | elif z is None and bit[0] == "Z": | |
1833 | z = float(bit[1]) | |
1834 | if x is not None: self.current_pos[0] = x | |
1835 | if y is not None: self.current_pos[1] = y | |
1836 | if z is not None: self.current_pos[2] = z | |
1837 | ||
1838 | def recvcb_actions(self, l): | |
1839 | if l.startswith("!!"): | |
1840 | if not self.paused: | |
1841 | wx.CallAfter(self.pause) | |
1842 | msg = l.split(" ", 1) | |
1843 | if len(msg) > 1 and not self.p.loud: | |
1844 | wx.CallAfter(self.addtexttolog, msg[1] + "\n") | |
1845 | return True | |
1846 | elif l.startswith("//"): | |
1847 | command = l.split(" ", 1) | |
1848 | if len(command) > 1: | |
1849 | command = command[1] | |
1850 | command = command.split(":") | |
1851 | if len(command) == 2 and command[0] == "action": | |
1852 | command = command[1] | |
1853 | self.log(_("Received command %s") % command) | |
1854 | if command == "pause": | |
1855 | if not self.paused: | |
1856 | wx.CallAfter(self.pause) | |
1857 | return True | |
1858 | elif command == "resume": | |
1859 | if self.paused: | |
1860 | wx.CallAfter(self.pause) | |
1861 | return True | |
1862 | elif command == "disconnect": | |
1863 | wx.CallAfter(self.disconnect) | |
1864 | return True | |
1865 | return False | |
1866 | ||
1867 | def recvcb(self, l): | |
1868 | l = l.rstrip() | |
1869 | if not self.recvcb_actions(l): | |
1870 | report_type = self.recvcb_report(l) | |
1871 | isreport = report_type != REPORT_NONE | |
1872 | if report_type & REPORT_POS: | |
1873 | self.update_pos() | |
1874 | elif report_type & REPORT_TEMP: | |
1875 | wx.CallAfter(self.tempdisp.SetLabel, self.tempreadings.strip().replace("ok ", "")) | |
1876 | self.update_tempdisplay() | |
1877 | if not self.p.loud and (l not in ["ok", "wait"] and (not isreport or report_type & REPORT_MANUAL)): | |
1878 | wx.CallAfter(self.addtexttolog, l + "\n") | |
1879 | for listener in self.recvlisteners: | |
1880 | listener(l) | |
1881 | ||
1882 | def listfiles(self, line, ignored = False): | |
1883 | if "Begin file list" in line: | |
1884 | self.sdlisting = True | |
1885 | elif "End file list" in line: | |
1886 | self.sdlisting = False | |
1887 | self.recvlisteners.remove(self.listfiles) | |
1888 | wx.CallAfter(self.filesloaded) | |
1889 | elif self.sdlisting: | |
1890 | self.sdfiles.append(line.strip().lower()) | |
1891 | ||
1892 | def waitforsdresponse(self, l): | |
1893 | if "file.open failed" in l: | |
1894 | wx.CallAfter(self.statusbar.SetStatusText, _("Opening file failed.")) | |
1895 | self.recvlisteners.remove(self.waitforsdresponse) | |
1896 | return | |
1897 | if "File opened" in l: | |
1898 | wx.CallAfter(self.statusbar.SetStatusText, l) | |
1899 | if "File selected" in l: | |
1900 | wx.CallAfter(self.statusbar.SetStatusText, _("Starting print")) | |
1901 | self.sdprinting = True | |
1902 | self.p.send_now("M24") | |
1903 | self.startcb() | |
1904 | return | |
1905 | if "Done printing file" in l: | |
1906 | wx.CallAfter(self.statusbar.SetStatusText, l) | |
1907 | self.sdprinting = False | |
1908 | self.recvlisteners.remove(self.waitforsdresponse) | |
1909 | self.endcb() | |
1910 | return | |
1911 | if "SD printing byte" in l: | |
1912 | # M27 handler | |
1913 | try: | |
1914 | resp = l.split() | |
1915 | vals = resp[-1].split("/") | |
1916 | self.percentdone = 100.0 * int(vals[0]) / int(vals[1]) | |
1917 | except: | |
1918 | pass | |
1919 | ||
1920 | # -------------------------------------------------------------- | |
1921 | # Custom buttons handling | |
1922 | # -------------------------------------------------------------- | |
1923 | ||
1924 | def cbuttons_reload(self): | |
1925 | allcbs = getattr(self, "custombuttons_widgets", []) | |
1926 | for button in allcbs: | |
1927 | self.cbuttonssizer.Detach(button) | |
1928 | button.Destroy() | |
1929 | self.custombuttons_widgets = [] | |
1930 | custombuttons = self.custombuttons[:] + [None] | |
1931 | for i, btndef in enumerate(custombuttons): | |
1932 | if btndef is None: | |
1933 | if i == len(custombuttons) - 1: | |
1934 | self.newbuttonbutton = b = wx.Button(self.centerpanel, -1, "+", size = (19, 18), style = wx.BU_EXACTFIT) | |
1935 | b.SetForegroundColour("#4444ff") | |
1936 | b.SetToolTip(wx.ToolTip(_("click to add new custom button"))) | |
1937 | b.Bind(wx.EVT_BUTTON, self.cbutton_edit) | |
1938 | else: | |
1939 | b = wx.StaticText(self.panel, -1, "") | |
1940 | else: | |
1941 | b = wx.Button(self.centerpanel, -1, btndef.label, style = wx.BU_EXACTFIT) | |
1942 | b.SetToolTip(wx.ToolTip(_("Execute command: ") + btndef.command)) | |
1943 | if btndef.background: | |
1944 | b.SetBackgroundColour(btndef.background) | |
1945 | rr, gg, bb = b.GetBackgroundColour().Get() | |
1946 | if 0.3 * rr + 0.59 * gg + 0.11 * bb < 60: | |
1947 | b.SetForegroundColour("#ffffff") | |
1948 | b.custombutton = i | |
1949 | b.properties = btndef | |
1950 | if btndef is not None: | |
1951 | b.Bind(wx.EVT_BUTTON, self.process_button) | |
1952 | b.Bind(wx.EVT_MOUSE_EVENTS, self.editbutton) | |
1953 | self.custombuttons_widgets.append(b) | |
1954 | if type(self.cbuttonssizer) == wx.GridBagSizer: | |
1955 | self.cbuttonssizer.Add(b, pos = (i // 4, i % 4), flag = wx.EXPAND) | |
1956 | else: | |
1957 | self.cbuttonssizer.Add(b, flag = wx.EXPAND) | |
1958 | self.centerpanel.Layout() | |
1959 | self.centerpanel.GetContainingSizer().Layout() | |
1960 | ||
1961 | def help_button(self): | |
1962 | self.log(_('Defines custom button. Usage: button <num> "title" [/c "colour"] command')) | |
1963 | ||
1964 | def do_button(self, argstr): | |
1965 | def nextarg(rest): | |
1966 | rest = rest.lstrip() | |
1967 | if rest.startswith('"'): | |
1968 | return rest[1:].split('"', 1) | |
1969 | else: | |
1970 | return rest.split(None, 1) | |
1971 | # try: | |
1972 | num, argstr = nextarg(argstr) | |
1973 | num = int(num) | |
1974 | title, argstr = nextarg(argstr) | |
1975 | colour = None | |
1976 | try: | |
1977 | c1, c2 = nextarg(argstr) | |
1978 | if c1 == "/c": | |
1979 | colour, argstr = nextarg(c2) | |
1980 | except: | |
1981 | pass | |
1982 | command = argstr.strip() | |
1983 | if num < 0 or num >= 64: | |
1984 | self.log(_("Custom button number should be between 0 and 63")) | |
1985 | return | |
1986 | while num >= len(self.custombuttons): | |
1987 | self.custombuttons.append(None) | |
1988 | self.custombuttons[num] = SpecialButton(title, command) | |
1989 | if colour is not None: | |
1990 | self.custombuttons[num].background = colour | |
1991 | if not self.processing_rc: | |
1992 | self.cbuttons_reload() | |
1993 | ||
1994 | def cbutton_save(self, n, bdef, new_n = None): | |
1995 | if new_n is None: new_n = n | |
1996 | if bdef is None or bdef == "": | |
1997 | self.save_in_rc(("button %d" % n), '') | |
1998 | elif bdef.background: | |
1999 | colour = bdef.background | |
2000 | if type(colour) not in (str, unicode): | |
2001 | if type(colour) == tuple and tuple(map(type, colour)) == (int, int, int): | |
2002 | colour = map(lambda x: x % 256, colour) | |
2003 | colour = wx.Colour(*colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX) | |
2004 | else: | |
2005 | colour = wx.Colour(colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX) | |
2006 | self.save_in_rc(("button %d" % n), 'button %d "%s" /c "%s" %s' % (new_n, bdef.label, colour, bdef.command)) | |
2007 | else: | |
2008 | self.save_in_rc(("button %d" % n), 'button %d "%s" %s' % (new_n, bdef.label, bdef.command)) | |
2009 | ||
2010 | def cbutton_edit(self, e, button = None): | |
2011 | bedit = ButtonEdit(self) | |
2012 | if button is not None: | |
2013 | n = button.custombutton | |
2014 | bedit.name.SetValue(button.properties.label) | |
2015 | bedit.command.SetValue(button.properties.command) | |
2016 | if button.properties.background: | |
2017 | colour = button.properties.background | |
2018 | if type(colour) not in (str, unicode): | |
2019 | if type(colour) == tuple and tuple(map(type, colour)) == (int, int, int): | |
2020 | colour = map(lambda x: x % 256, colour) | |
2021 | colour = wx.Colour(*colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX) | |
2022 | else: | |
2023 | colour = wx.Colour(colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX) | |
2024 | bedit.color.SetValue(colour) | |
2025 | else: | |
2026 | n = len(self.custombuttons) | |
2027 | while n > 0 and self.custombuttons[n - 1] is None: | |
2028 | n -= 1 | |
2029 | if bedit.ShowModal() == wx.ID_OK: | |
2030 | if n == len(self.custombuttons): | |
2031 | self.custombuttons.append(None) | |
2032 | self.custombuttons[n] = SpecialButton(bedit.name.GetValue().strip(), bedit.command.GetValue().strip(), custom = True) | |
2033 | if bedit.color.GetValue().strip() != "": | |
2034 | self.custombuttons[n].background = bedit.color.GetValue() | |
2035 | self.cbutton_save(n, self.custombuttons[n]) | |
2036 | wx.CallAfter(bedit.Destroy) | |
2037 | wx.CallAfter(self.cbuttons_reload) | |
2038 | ||
2039 | def cbutton_remove(self, e, button): | |
2040 | n = button.custombutton | |
2041 | self.cbutton_save(n, None) | |
2042 | del self.custombuttons[n] | |
2043 | for i in range(n, len(self.custombuttons)): | |
2044 | self.cbutton_save(i, self.custombuttons[i]) | |
2045 | wx.CallAfter(self.cbuttons_reload) | |
2046 | ||
2047 | def cbutton_order(self, e, button, dir): | |
2048 | n = button.custombutton | |
2049 | if dir < 0: | |
2050 | n = n - 1 | |
2051 | if n + 1 >= len(self.custombuttons): | |
2052 | self.custombuttons.append(None) # pad | |
2053 | # swap | |
2054 | self.custombuttons[n], self.custombuttons[n + 1] = self.custombuttons[n + 1], self.custombuttons[n] | |
2055 | self.cbutton_save(n, self.custombuttons[n]) | |
2056 | self.cbutton_save(n + 1, self.custombuttons[n + 1]) | |
2057 | wx.CallAfter(self.cbuttons_reload) | |
2058 | ||
2059 | def editbutton(self, e): | |
2060 | if e.IsCommandEvent() or e.ButtonUp(wx.MOUSE_BTN_RIGHT): | |
2061 | if e.IsCommandEvent(): | |
2062 | pos = (0, 0) | |
2063 | else: | |
2064 | pos = e.GetPosition() | |
2065 | popupmenu = wx.Menu() | |
2066 | obj = e.GetEventObject() | |
2067 | if hasattr(obj, "custombutton"): | |
2068 | item = popupmenu.Append(-1, _("Edit custom button '%s'") % e.GetEventObject().GetLabelText()) | |
2069 | self.Bind(wx.EVT_MENU, lambda e, button = e.GetEventObject(): self.cbutton_edit(e, button), item) | |
2070 | item = popupmenu.Append(-1, _("Move left <<")) | |
2071 | self.Bind(wx.EVT_MENU, lambda e, button = e.GetEventObject(): self.cbutton_order(e, button, -1), item) | |
2072 | if obj.custombutton == 0: item.Enable(False) | |
2073 | item = popupmenu.Append(-1, _("Move right >>")) | |
2074 | self.Bind(wx.EVT_MENU, lambda e, button = e.GetEventObject(): self.cbutton_order(e, button, 1), item) | |
2075 | if obj.custombutton == 63: item.Enable(False) | |
2076 | pos = self.panel.ScreenToClient(e.GetEventObject().ClientToScreen(pos)) | |
2077 | item = popupmenu.Append(-1, _("Remove custom button '%s'") % e.GetEventObject().GetLabelText()) | |
2078 | self.Bind(wx.EVT_MENU, lambda e, button = e.GetEventObject(): self.cbutton_remove(e, button), item) | |
2079 | else: | |
2080 | item = popupmenu.Append(-1, _("Add custom button")) | |
2081 | self.Bind(wx.EVT_MENU, self.cbutton_edit, item) | |
2082 | self.panel.PopupMenu(popupmenu, pos) | |
2083 | elif e.Dragging() and e.ButtonIsDown(wx.MOUSE_BTN_LEFT): | |
2084 | obj = e.GetEventObject() | |
2085 | scrpos = obj.ClientToScreen(e.GetPosition()) | |
2086 | if not hasattr(self, "dragpos"): | |
2087 | self.dragpos = scrpos | |
2088 | e.Skip() | |
2089 | return | |
2090 | else: | |
2091 | dx, dy = self.dragpos[0] - scrpos[0], self.dragpos[1] - scrpos[1] | |
2092 | if dx * dx + dy * dy < 5 * 5: # threshold to detect dragging for jittery mice | |
2093 | e.Skip() | |
2094 | return | |
2095 | if not hasattr(self, "dragging"): | |
2096 | # init dragging of the custom button | |
2097 | if hasattr(obj, "custombutton") and obj.properties is not None: | |
2098 | for b in self.custombuttons_widgets: | |
2099 | if b.properties is None: | |
2100 | b.Enable() | |
2101 | b.SetLabel("") | |
2102 | b.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) | |
2103 | b.SetForegroundColour("black") | |
2104 | b.SetSize(obj.GetSize()) | |
2105 | if self.toolbarsizer.GetItem(b) is not None: | |
2106 | self.toolbarsizer.SetItemMinSize(b, obj.GetSize()) | |
2107 | self.mainsizer.Layout() | |
2108 | self.dragging = wx.Button(self.panel, -1, obj.GetLabel(), style = wx.BU_EXACTFIT) | |
2109 | self.dragging.SetBackgroundColour(obj.GetBackgroundColour()) | |
2110 | self.dragging.SetForegroundColour(obj.GetForegroundColour()) | |
2111 | self.dragging.sourcebutton = obj | |
2112 | self.dragging.Raise() | |
2113 | self.dragging.Disable() | |
2114 | self.dragging.SetPosition(self.panel.ScreenToClient(scrpos)) | |
2115 | self.last_drag_dest = obj | |
2116 | self.dragging.label = obj.s_label = obj.GetLabel() | |
2117 | self.dragging.bgc = obj.s_bgc = obj.GetBackgroundColour() | |
2118 | self.dragging.fgc = obj.s_fgc = obj.GetForegroundColour() | |
2119 | else: | |
2120 | # dragging in progress | |
2121 | self.dragging.SetPosition(self.panel.ScreenToClient(scrpos)) | |
2122 | wx.CallAfter(self.dragging.Refresh) | |
2123 | dst = None | |
2124 | src = self.dragging.sourcebutton | |
2125 | drg = self.dragging | |
2126 | for b in self.custombuttons_widgets: | |
2127 | if b.GetScreenRect().Contains(scrpos): | |
2128 | dst = b | |
2129 | break | |
2130 | if dst is not self.last_drag_dest: | |
2131 | if self.last_drag_dest is not None: | |
2132 | self.last_drag_dest.SetBackgroundColour(self.last_drag_dest.s_bgc) | |
2133 | self.last_drag_dest.SetForegroundColour(self.last_drag_dest.s_fgc) | |
2134 | self.last_drag_dest.SetLabel(self.last_drag_dest.s_label) | |
2135 | if dst is not None and dst is not src: | |
2136 | dst.s_bgc = dst.GetBackgroundColour() | |
2137 | dst.s_fgc = dst.GetForegroundColour() | |
2138 | dst.s_label = dst.GetLabel() | |
2139 | src.SetBackgroundColour(dst.GetBackgroundColour()) | |
2140 | src.SetForegroundColour(dst.GetForegroundColour()) | |
2141 | src.SetLabel(dst.GetLabel()) | |
2142 | dst.SetBackgroundColour(drg.bgc) | |
2143 | dst.SetForegroundColour(drg.fgc) | |
2144 | dst.SetLabel(drg.label) | |
2145 | else: | |
2146 | src.SetBackgroundColour(drg.bgc) | |
2147 | src.SetForegroundColour(drg.fgc) | |
2148 | src.SetLabel(drg.label) | |
2149 | self.last_drag_dest = dst | |
2150 | elif hasattr(self, "dragging") and not e.ButtonIsDown(wx.MOUSE_BTN_LEFT): | |
2151 | # dragging finished | |
2152 | obj = e.GetEventObject() | |
2153 | scrpos = obj.ClientToScreen(e.GetPosition()) | |
2154 | dst = None | |
2155 | src = self.dragging.sourcebutton | |
2156 | drg = self.dragging | |
2157 | for b in self.custombuttons_widgets: | |
2158 | if b.GetScreenRect().Contains(scrpos): | |
2159 | dst = b | |
2160 | break | |
2161 | if dst is not None: | |
2162 | src_i = src.custombutton | |
2163 | dst_i = dst.custombutton | |
2164 | self.custombuttons[src_i], self.custombuttons[dst_i] = self.custombuttons[dst_i], self.custombuttons[src_i] | |
2165 | self.cbutton_save(src_i, self.custombuttons[src_i]) | |
2166 | self.cbutton_save(dst_i, self.custombuttons[dst_i]) | |
2167 | while self.custombuttons[-1] is None: | |
2168 | del self.custombuttons[-1] | |
2169 | wx.CallAfter(self.dragging.Destroy) | |
2170 | del self.dragging | |
2171 | wx.CallAfter(self.cbuttons_reload) | |
2172 | del self.last_drag_dest | |
2173 | del self.dragpos | |
2174 | else: | |
2175 | e.Skip() | |
2176 | ||
2177 | def process_button(self, e): | |
2178 | try: | |
2179 | if hasattr(e.GetEventObject(), "custombutton"): | |
2180 | if wx.GetKeyState(wx.WXK_CONTROL) or wx.GetKeyState(wx.WXK_ALT): | |
2181 | return self.editbutton(e) | |
2182 | self.cur_button = e.GetEventObject().custombutton | |
2183 | command = e.GetEventObject().properties.command | |
2184 | command = self.precmd(command) | |
2185 | self.onecmd(command) | |
2186 | self.cur_button = None | |
2187 | except: | |
2188 | self.log(_("Failed to handle button")) | |
2189 | self.cur_button = None | |
2190 | raise | |
2191 | ||
2192 | # -------------------------------------------------------------- | |
2193 | # Macros handling | |
2194 | # -------------------------------------------------------------- | |
2195 | ||
2196 | def start_macro(self, macro_name, old_macro_definition = ""): | |
2197 | if not self.processing_rc: | |
2198 | def cb(definition): | |
2199 | if len(definition.strip()) == 0: | |
2200 | if old_macro_definition != "": | |
2201 | dialog = wx.MessageDialog(self, _("Do you want to erase the macro?"), style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION) | |
2202 | if dialog.ShowModal() == wx.ID_YES: | |
2203 | self.delete_macro(macro_name) | |
2204 | return | |
2205 | self.log(_("Cancelled.")) | |
2206 | return | |
2207 | self.cur_macro_name = macro_name | |
2208 | self.cur_macro_def = definition | |
2209 | self.end_macro() | |
2210 | MacroEditor(macro_name, old_macro_definition, cb) | |
2211 | else: | |
2212 | pronsole.pronsole.start_macro(self, macro_name, old_macro_definition) | |
2213 | ||
2214 | def end_macro(self): | |
2215 | pronsole.pronsole.end_macro(self) | |
2216 | self.update_macros_menu() | |
2217 | ||
2218 | def delete_macro(self, macro_name): | |
2219 | pronsole.pronsole.delete_macro(self, macro_name) | |
2220 | self.update_macros_menu() | |
2221 | ||
2222 | def new_macro(self, e = None): | |
2223 | dialog = wx.Dialog(self, -1, _("Enter macro name"), size = (260, 85)) | |
2224 | panel = wx.Panel(dialog, -1) | |
2225 | vbox = wx.BoxSizer(wx.VERTICAL) | |
2226 | wx.StaticText(panel, -1, _("Macro name:"), (8, 14)) | |
2227 | dialog.namectrl = wx.TextCtrl(panel, -1, '', (110, 8), size = (130, 24), style = wx.TE_PROCESS_ENTER) | |
2228 | hbox = wx.BoxSizer(wx.HORIZONTAL) | |
2229 | okb = wx.Button(dialog, wx.ID_OK, _("Ok"), size = (60, 24)) | |
2230 | dialog.Bind(wx.EVT_TEXT_ENTER, lambda e: dialog.EndModal(wx.ID_OK), dialog.namectrl) | |
2231 | hbox.Add(okb) | |
2232 | hbox.Add(wx.Button(dialog, wx.ID_CANCEL, _("Cancel"), size = (60, 24))) | |
2233 | vbox.Add(panel) | |
2234 | vbox.Add(hbox, 1, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 10) | |
2235 | dialog.SetSizer(vbox) | |
2236 | dialog.Centre() | |
2237 | macro = "" | |
2238 | if dialog.ShowModal() == wx.ID_OK: | |
2239 | macro = dialog.namectrl.GetValue() | |
2240 | if macro != "": | |
2241 | wx.CallAfter(self.edit_macro, macro) | |
2242 | dialog.Destroy() | |
2243 | return macro | |
2244 | ||
2245 | def edit_macro(self, macro): | |
2246 | if macro == "": return self.new_macro() | |
2247 | if macro in self.macros: | |
2248 | old_def = self.macros[macro] | |
2249 | elif len([c for c in macro.encode("ascii", "replace") if not c.isalnum() and c != "_"]): | |
2250 | self.log(_("Macro name may contain only ASCII alphanumeric symbols and underscores")) | |
2251 | return | |
2252 | elif hasattr(self.__class__, "do_" + macro): | |
2253 | self.log(_("Name '%s' is being used by built-in command") % macro) | |
2254 | return | |
2255 | else: | |
2256 | old_def = "" | |
2257 | self.start_macro(macro, old_def) | |
2258 | return macro | |
2259 | ||
2260 | def update_macros_menu(self): | |
2261 | if not hasattr(self, "macros_menu"): | |
2262 | return # too early, menu not yet built | |
2263 | try: | |
2264 | while True: | |
2265 | item = self.macros_menu.FindItemByPosition(1) | |
2266 | if item is None: break | |
2267 | self.macros_menu.DeleteItem(item) | |
2268 | except: | |
2269 | pass | |
2270 | for macro in self.macros.keys(): | |
2271 | self.Bind(wx.EVT_MENU, lambda x, m = macro: self.start_macro(m, self.macros[m]), self.macros_menu.Append(-1, macro)) | |
2272 | ||
2273 | # -------------------------------------------------------------- | |
2274 | # Slic3r integration | |
2275 | # -------------------------------------------------------------- | |
2276 | ||
2277 | def load_slic3r_configs(self, menus): | |
2278 | """List Slic3r configurations and create menu""" | |
2279 | # Hack to get correct path for Slic3r config | |
2280 | orig_appname = self.app.GetAppName() | |
2281 | self.app.SetAppName("Slic3r") | |
2282 | configpath = wx.StandardPaths.Get().GetUserDataDir() | |
2283 | self.app.SetAppName(orig_appname) | |
2284 | self.slic3r_configpath = configpath | |
2285 | configfile = os.path.join(configpath, "slic3r.ini") | |
2286 | config = self.read_slic3r_config(configfile) | |
2287 | self.slic3r_configs = {} | |
2288 | for cat in menus: | |
2289 | menu = menus[cat] | |
2290 | pattern = os.path.join(configpath, cat, "*.ini") | |
2291 | files = sorted(glob.glob(pattern)) | |
2292 | try: | |
2293 | preset = config.get("presets", cat) | |
2294 | self.slic3r_configs[cat] = preset | |
2295 | except: | |
2296 | preset = None | |
2297 | self.slic3r_configs[cat] = None | |
2298 | for f in files: | |
2299 | name = os.path.splitext(os.path.basename(f))[0] | |
2300 | item = menu.Append(-1, name, f, wx.ITEM_RADIO) | |
2301 | item.Check(os.path.basename(f) == preset) | |
2302 | self.Bind(wx.EVT_MENU, | |
2303 | lambda event, cat = cat, f = f: | |
2304 | self.set_slic3r_config(configfile, cat, f), item) | |
2305 | ||
2306 | def read_slic3r_config(self, configfile, parser = None): | |
2307 | """Helper to read a Slic3r configuration file""" | |
2308 | import ConfigParser | |
2309 | parser = ConfigParser.RawConfigParser() | |
2310 | ||
2311 | class add_header(object): | |
2312 | def __init__(self, f): | |
2313 | self.f = f | |
2314 | self.header = '[dummy]' | |
2315 | ||
2316 | def readline(self): | |
2317 | if self.header: | |
2318 | try: return self.header | |
2319 | finally: self.header = None | |
2320 | else: | |
2321 | return self.f.readline() | |
2322 | parser.readfp(add_header(open(configfile)), configfile) | |
2323 | return parser | |
2324 | ||
2325 | def set_slic3r_config(self, configfile, cat, file): | |
2326 | """Set new preset for a given category""" | |
2327 | self.slic3r_configs[cat] = file | |
2328 | if self.settings.slic3rupdate: | |
2329 | config = self.read_slic3r_config(configfile) | |
2330 | config.set("presets", cat, os.path.basename(file)) | |
2331 | f = StringIO.StringIO() | |
2332 | config.write(f) | |
2333 | data = f.getvalue() | |
2334 | f.close() | |
2335 | data = data.replace("[dummy]\n", "") | |
2336 | with open(configfile, "w") as f: | |
2337 | f.write(data) | |
2338 | ||
2339 | class PronterApp(wx.App): | |
2340 | ||
2341 | mainwindow = None | |
2342 | ||
2343 | def __init__(self, *args, **kwargs): | |
2344 | super(PronterApp, self).__init__(*args, **kwargs) | |
2345 | self.SetAppName("Pronterface") | |
2346 | self.mainwindow = PronterWindow(self) | |
2347 | self.mainwindow.Show() |