26 # |
12 # |
27 # You should have received a copy of the GNU General Public License |
13 # You should have received a copy of the GNU General Public License |
28 # along with Printrun. If not, see <http://www.gnu.org/licenses/>. |
14 # along with Printrun. If not, see <http://www.gnu.org/licenses/>. |
29 |
15 |
30 import os |
16 import os |
31 import Queue |
17 import platform |
|
18 import queue |
32 import sys |
19 import sys |
33 import time |
20 import time |
34 import threading |
21 import threading |
35 import traceback |
22 import traceback |
36 import cStringIO as StringIO |
23 import io as StringIO |
37 import subprocess |
24 import subprocess |
38 import glob |
25 import glob |
39 import logging |
26 import logging |
|
27 import re |
40 |
28 |
41 try: import simplejson as json |
29 try: import simplejson as json |
42 except ImportError: import json |
30 except ImportError: import json |
43 |
31 |
44 from . import pronsole |
32 from . import pronsole |
45 from . import printcore |
33 from . import printcore |
|
34 from printrun.spoolmanager import spoolmanager_gui |
46 |
35 |
47 from .utils import install_locale, setup_logging, dosify, \ |
36 from .utils import install_locale, setup_logging, dosify, \ |
48 iconfile, configfile, format_time, format_duration, \ |
37 iconfile, configfile, format_time, format_duration, \ |
49 hexcolor_to_float, parse_temperature_report, \ |
38 hexcolor_to_float, parse_temperature_report, \ |
50 prepare_command, check_rgb_color, check_rgba_color |
39 prepare_command, check_rgb_color, check_rgba_color, compile_file, \ |
|
40 write_history_to, read_history_from |
51 install_locale('pronterface') |
41 install_locale('pronterface') |
52 |
42 |
53 try: |
43 try: |
54 import wx |
44 import wx |
|
45 import wx.adv |
|
46 if wx.VERSION < (4,): |
|
47 raise ImportError() |
55 except: |
48 except: |
56 logging.error(_("WX is not installed. This program requires WX to run.")) |
49 logging.error(_("WX >= 4 is not installed. This program requires WX >= 4 to run.")) |
57 raise |
50 raise |
58 |
51 |
59 from .gui.widgets import SpecialButton, MacroEditor, PronterOptions, ButtonEdit |
52 from .gui.widgets import SpecialButton, MacroEditor, PronterOptions, ButtonEdit |
60 |
53 |
61 winsize = (800, 500) |
54 winsize = (800, 500) |
68 class PronterfaceQuitException(Exception): |
61 class PronterfaceQuitException(Exception): |
69 pass |
62 pass |
70 |
63 |
71 from .gui import MainWindow |
64 from .gui import MainWindow |
72 from .settings import wxSetting, HiddenSetting, StringSetting, SpinSetting, \ |
65 from .settings import wxSetting, HiddenSetting, StringSetting, SpinSetting, \ |
73 FloatSpinSetting, BooleanSetting, StaticTextSetting |
66 FloatSpinSetting, BooleanSetting, StaticTextSetting, ColorSetting, ComboSetting |
74 from printrun import gcoder |
67 from printrun import gcoder |
75 from .pronsole import REPORT_NONE, REPORT_POS, REPORT_TEMP, REPORT_MANUAL |
68 from .pronsole import REPORT_NONE, REPORT_POS, REPORT_TEMP, REPORT_MANUAL |
76 |
69 |
77 class ConsoleOutputHandler(object): |
70 def format_length(mm, fractional=2): |
|
71 if mm <= 10: |
|
72 units = mm |
|
73 suffix = 'mm' |
|
74 elif mm < 1000: |
|
75 units = mm / 10 |
|
76 suffix = 'cm' |
|
77 else: |
|
78 units = mm / 1000 |
|
79 suffix = 'm' |
|
80 return '%%.%df' % fractional % units + suffix |
|
81 |
|
82 class ConsoleOutputHandler: |
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. |
83 """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.""" |
84 We also redirect stdout and stderr to ourself to catch print messages and al.""" |
80 |
85 |
81 def __init__(self, target, log_path): |
86 def __init__(self, target, log_path): |
82 self.stdout = sys.stdout |
87 self.stdout = sys.stdout |
83 self.stderr = sys.stderr |
88 self.stderr = sys.stderr |
84 sys.stdout = self |
89 sys.stdout = self |
85 sys.stderr = self |
90 sys.stderr = self |
|
91 self.print_on_stdout = not log_path |
86 if log_path: |
92 if log_path: |
87 self.print_on_stdout = False |
|
88 setup_logging(self, log_path, reset_handlers = True) |
93 setup_logging(self, log_path, reset_handlers = True) |
89 self.target = target |
|
90 else: |
94 else: |
91 self.print_on_stdout = True |
95 setup_logging(sys.stdout, reset_handlers = True) |
92 setup_logging(sys.stdout) |
96 self.target = target |
93 self.target = target |
|
94 |
97 |
95 def __del__(self): |
98 def __del__(self): |
96 sys.stdout = self.stdout |
99 sys.stdout = self.stdout |
97 sys.stderr = self.stderr |
100 sys.stderr = self.stderr |
98 |
101 |
170 self.excluder = None |
153 self.excluder = None |
171 self.slicep = None |
154 self.slicep = None |
172 self.current_pos = [0, 0, 0] |
155 self.current_pos = [0, 0, 0] |
173 self.paused = False |
156 self.paused = False |
174 self.uploading = False |
157 self.uploading = False |
175 self.sentglines = Queue.Queue(0) |
158 self.sentglines = queue.Queue(0) |
176 self.cpbuttons = { |
159 self.cpbuttons = { |
177 "motorsoff": SpecialButton(_("Motors off"), ("M84"), (250, 250, 250), _("Switch all motors off")), |
160 "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")), |
161 "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")), |
162 "reverse": SpecialButton(_("Reverse"), ("pront_reverse"), (225, 200, 200), _("Reverse extruder by set length")), |
180 } |
163 } |
181 self.custombuttons = [] |
164 self.custombuttons = [] |
182 self.btndict = {} |
165 self.btndict = {} |
183 self.filehistory = None |
166 self.filehistory = None |
184 self.autoconnect = False |
167 self.autoconnect = False |
|
168 self.autoscrolldisable = False |
|
169 |
185 self.parse_cmdline(sys.argv[1:]) |
170 self.parse_cmdline(sys.argv[1:]) |
|
171 for field in dir(self.settings): |
|
172 if field.startswith("_gcview_color_"): |
|
173 cleanname = field[1:] |
|
174 color = hexcolor_to_float(getattr(self.settings, cleanname), 4) |
|
175 setattr(self, cleanname, list(color)) |
186 |
176 |
187 # FIXME: We need to initialize the main window after loading the |
177 # FIXME: We need to initialize the main window after loading the |
188 # configs to restore the size, but this might have some unforeseen |
178 # configs to restore the size, but this might have some unforeseen |
189 # consequences. |
179 # consequences. |
190 # -- Okai, it seems it breaks things like update_gviz_params >< |
180 # -- Okai, it seems it breaks things like update_gviz_params >< |
195 self.Maximize() |
185 self.Maximize() |
196 self.SetIcon(wx.Icon(iconfile("pronterface.png"), wx.BITMAP_TYPE_PNG)) |
186 self.SetIcon(wx.Icon(iconfile("pronterface.png"), wx.BITMAP_TYPE_PNG)) |
197 self.Bind(wx.EVT_SIZE, self.on_resize) |
187 self.Bind(wx.EVT_SIZE, self.on_resize) |
198 self.Bind(wx.EVT_MAXIMIZE, self.on_maximize) |
188 self.Bind(wx.EVT_MAXIMIZE, self.on_maximize) |
199 self.window_ready = True |
189 self.window_ready = True |
200 |
190 self.Bind(wx.EVT_CLOSE, self.closewin) |
|
191 self.Bind(wx.EVT_CHAR_HOOK, self.on_key) |
201 # set feedrates in printcore for pause/resume |
192 # set feedrates in printcore for pause/resume |
202 self.p.xy_feedrate = self.settings.xy_feedrate |
193 self.p.xy_feedrate = self.settings.xy_feedrate |
203 self.p.z_feedrate = self.settings.z_feedrate |
194 self.p.z_feedrate = self.settings.z_feedrate |
204 |
195 |
205 self.panel.SetBackgroundColour(self.bgcolor) |
196 self.panel.SetBackgroundColour(self.bgcolor) |
206 customdict = {} |
197 customdict = {} |
207 try: |
198 try: |
208 execfile(configfile("custombtn.txt"), customdict) |
199 exec(compile_file(configfile("custombtn.txt")), customdict) |
209 if len(customdict["btns"]): |
200 if len(customdict["btns"]): |
210 if not len(self.custombuttons): |
201 if not len(self.custombuttons): |
211 try: |
202 try: |
212 self.custombuttons = customdict["btns"] |
203 self.custombuttons = customdict["btns"] |
213 for n in xrange(len(self.custombuttons)): |
204 for n in range(len(self.custombuttons)): |
214 self.cbutton_save(n, self.custombuttons[n]) |
205 self.cbutton_save(n, self.custombuttons[n]) |
215 os.rename("custombtn.txt", "custombtn.old") |
206 os.rename("custombtn.txt", "custombtn.old") |
216 rco = open("custombtn.txt", "w") |
207 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")) |
208 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() |
209 rco.close() |
219 except IOError, x: |
210 except IOError as x: |
220 logging.error(str(x)) |
211 logging.error(str(x)) |
221 else: |
212 else: |
222 logging.warning(_("Note!!! You have specified custom buttons in both custombtn.txt and .pronsolerc")) |
213 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")) |
214 logging.warning(_("Ignoring custombtn.txt. Remove all current buttons to revert to custombtn.txt")) |
224 |
215 |
225 except: |
216 except: |
226 pass |
217 pass |
227 self.create_menu() |
218 self.menustrip = wx.MenuBar() |
228 self.update_recent_files("recentfiles", self.settings.recentfiles) |
|
229 |
|
230 self.reload_ui() |
219 self.reload_ui() |
231 # disable all printer controls until we connect to a printer |
220 # disable all printer controls until we connect to a printer |
232 self.gui_set_disconnected() |
221 self.gui_set_disconnected() |
233 self.statusbar = self.CreateStatusBar() |
222 self.statusbar = self.CreateStatusBar() |
234 self.statusbar.SetStatusText(_("Not connected to printer.")) |
223 self.statusbar.SetStatusText(_("Not connected to printer.")) |
255 if self.filename is not None: |
244 if self.filename is not None: |
256 self.do_load(self.filename) |
245 self.do_load(self.filename) |
257 if self.settings.monitor: |
246 if self.settings.monitor: |
258 self.update_monitor() |
247 self.update_monitor() |
259 |
248 |
260 self.lc_printing = False |
|
261 self.pass_current = 1 |
|
262 |
|
263 # -------------------------------------------------------------- |
|
264 # Lasercutter methods |
|
265 # -------------------------------------------------------------- |
|
266 |
|
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())) |
|
271 wx.CallAfter(self.onecmd, line) |
|
272 self.lc_printing = True |
|
273 wx.CallAfter(self.printfile, None) |
|
274 |
|
275 def endcb_lasercut(self): |
|
276 # LASERCUT: Now check if we should do another print pass? |
|
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) |
|
294 |
|
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())) |
|
306 self.onecmd(line) |
|
307 else: |
|
308 self.lc_printing = False |
|
309 wx.CallAfter(self.lc_printbtn.Enable) |
|
310 wx.CallAfter(self.lc_printbtn.SetLabel, _("Start cutting")) |
|
311 |
|
312 |
|
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")) |
|
319 self.settings._add(BooleanSetting("lc_melzi_hack", False, "Use Melzi M571 Hack instead M3/M5", "no description :)", "Laser"), self.update_lc_settings) |
|
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) |
|
329 self.settings._add(SpinSetting("lc_dpi", 300, 25, 600, "Image DPI", "Image resolution for scaling", "Laser"), self.update_lc_settings) |
|
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) |
|
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) |
|
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) |
|
344 |
|
345 # -------------------------------------------------------------- |
249 # -------------------------------------------------------------- |
346 # Main interface handling |
250 # Main interface handling |
347 # -------------------------------------------------------------- |
251 # -------------------------------------------------------------- |
348 |
252 |
349 def reset_ui(self): |
253 def reset_ui(self): |
350 MainWindow.reset_ui(self) |
254 MainWindow.reset_ui(self) |
351 self.custombuttons_widgets = [] |
255 self.custombuttons_widgets = [] |
352 |
256 |
353 def reload_ui(self, *args): |
257 def reload_ui(self, *args): |
354 if not self.window_ready: return |
258 if not self.window_ready: return |
|
259 temp_monitor = self.settings.monitor |
|
260 self.settings.monitor = False |
|
261 self.update_monitor() |
355 self.Freeze() |
262 self.Freeze() |
356 |
263 |
357 # If UI is being recreated, delete current one |
264 # If UI is being recreated, delete current one |
358 if self.ui_ready: |
265 if self.ui_ready: |
359 # Store log console content |
266 # Store log console content |
360 logcontent = self.logbox.GetValue() |
267 logcontent = self.logbox.GetValue() |
|
268 self.menustrip.SetMenus([]) |
|
269 if len(self.commandbox.history): |
|
270 #save current command box history |
|
271 if not os.path.exists(self.history_file): |
|
272 if not os.path.exists(self.cache_dir): |
|
273 os.makedirs(self.cache_dir) |
|
274 write_history_to(self.history_file, self.commandbox.history) |
361 # Create a temporary panel to reparent widgets with state we want |
275 # Create a temporary panel to reparent widgets with state we want |
362 # to retain across UI changes |
276 # to retain across UI changes |
363 temppanel = wx.Panel(self) |
277 temppanel = wx.Panel(self) |
364 # TODO: add viz widgets to statefulControls |
278 # TODO: add viz widgets to statefulControls |
365 for control in self.statefulControls: |
279 for control in self.statefulControls: |
422 event.Skip() |
342 event.Skip() |
423 |
343 |
424 def on_exit(self, event): |
344 def on_exit(self, event): |
425 self.Close() |
345 self.Close() |
426 |
346 |
427 def kill(self, e): |
347 def on_settings_change(self, changed_settings): |
|
348 if self.gviz: |
|
349 self.gviz.on_settings_change(changed_settings) |
|
350 |
|
351 def on_key(self, event): |
|
352 if not isinstance(event.EventObject, (wx.TextCtrl, wx.ComboBox)) \ |
|
353 or event.HasModifiers(): |
|
354 ch = chr(event.KeyCode) |
|
355 keys = {'B': self.btemp, 'H': self.htemp, 'J': self.xyb, 'S': self.commandbox, |
|
356 'V': self.gviz} |
|
357 widget = keys.get(ch) |
|
358 #ignore Alt+(S, H), so it can open Settings, Help menu |
|
359 if widget and (ch not in 'SH' or not event.AltDown()) \ |
|
360 and not (event.ControlDown() and ch == 'V' |
|
361 and event.EventObject is self.commandbox): |
|
362 widget.SetFocus() |
|
363 return |
|
364 # On MSWindows button mnemonics are processed only if the |
|
365 # focus is in the parent panel |
|
366 if event.AltDown() and ch < 'Z': |
|
367 in_toolbar = self.toolbarsizer.GetItem(event.EventObject) |
|
368 candidates = (self.connectbtn, self.connectbtn_cb_var), \ |
|
369 (self.pausebtn, self.pause), \ |
|
370 (self.printbtn, self.printfile) |
|
371 for ctl, cb in candidates: |
|
372 match = ('&' + ch) in ctl.Label.upper() |
|
373 handled = in_toolbar and match |
|
374 if handled: |
|
375 break |
|
376 # react to 'P' even for 'Restart', 'Resume' |
|
377 # print('match', match, 'handled', handled, ctl.Label, ctl.Enabled) |
|
378 if (match or ch == 'P' and ctl != self.connectbtn) and ctl.Enabled: |
|
379 # print('call', ch, cb) |
|
380 cb() |
|
381 # react to only 1 of 'P' buttons, prefer Resume |
|
382 return |
|
383 |
|
384 event.Skip() |
|
385 |
|
386 def closewin(self, e): |
|
387 e.StopPropagation() |
|
388 self.do_exit("force") |
|
389 |
|
390 def kill(self, e=None): |
|
391 if len(self.commandbox.history): |
|
392 #save current command box history |
|
393 history = (self.history_file) |
|
394 if not os.path.exists(history): |
|
395 if not os.path.exists(self.cache_dir): |
|
396 os.makedirs(self.cache_dir) |
|
397 write_history_to(history,self.commandbox.history) |
428 if self.p.printing or self.p.paused: |
398 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) |
399 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: |
400 if dlg.ShowModal() == wx.ID_NO: |
431 return |
401 return |
432 pronsole.pronsole.kill(self) |
402 pronsole.pronsole.kill(self) |
471 self.log(_("Monitoring printer.")) |
440 self.log(_("Monitoring printer.")) |
472 else: |
441 else: |
473 self.log(_("Done monitoring.")) |
442 self.log(_("Done monitoring.")) |
474 |
443 |
475 def do_pront_extrude(self, l = ""): |
444 def do_pront_extrude(self, l = ""): |
|
445 if self.p.printing and not self.paused: |
|
446 self.log(_("Please pause or stop print before extruding.")) |
|
447 return |
476 feed = self.settings.e_feedrate |
448 feed = self.settings.e_feedrate |
477 self.do_extrude_final(self.edist.GetValue(), feed) |
449 self.do_extrude_final(self.edist.GetValue(), feed) |
478 |
450 |
479 def do_pront_reverse(self, l = ""): |
451 def do_pront_reverse(self, l = ""): |
|
452 if self.p.printing and not self.paused: |
|
453 self.log(_("Please pause or stop print before reversing.")) |
|
454 return |
480 feed = self.settings.e_feedrate |
455 feed = self.settings.e_feedrate |
481 self.do_extrude_final(- self.edist.GetValue(), feed) |
456 self.do_extrude_final(- self.edist.GetValue(), feed) |
482 |
457 |
483 def do_settemp(self, l = ""): |
458 def do_settemp(self, l = ""): |
484 try: |
459 try: |
485 if l.__class__ not in (str, unicode) or not len(l): |
460 if not isinstance(l, str) or not len(l): |
486 l = str(self.htemp.GetValue().split()[0]) |
461 l = str(self.htemp.GetValue().split()[0]) |
487 l = l.lower().replace(", ", ".") |
462 l = l.lower().replace(", ", ".") |
488 for i in self.temps.keys(): |
463 for i in self.temps.keys(): |
489 l = l.replace(i, self.temps[i]) |
464 l = l.replace(i, self.temps[i]) |
490 f = float(l) |
465 f = float(l) |
491 if f >= 0: |
466 if f >= 0: |
492 if self.p.online: |
467 if self.p.online: |
493 self.p.send_now("M104 S" + l) |
468 self.p.send_now("M104 S" + l) |
494 self.log(_("Setting hotend temperature to %f degrees Celsius.") % f) |
469 self.log(_("Setting hotend temperature to %g degrees Celsius.") % f) |
495 self.sethotendgui(f) |
470 self.sethotendgui(f) |
496 else: |
471 else: |
497 self.logError(_("Printer is not online.")) |
472 self.logError(_("Printer is not online.")) |
498 else: |
473 else: |
499 self.logError(_("You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0.")) |
474 self.logError(_("You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0.")) |
500 except Exception, x: |
475 except Exception as x: |
501 self.logError(_("You must enter a temperature. (%s)") % (repr(x),)) |
476 self.logError(_("You must enter a temperature. (%s)") % (repr(x),)) |
502 |
477 |
503 def do_bedtemp(self, l = ""): |
478 def do_bedtemp(self, l = ""): |
504 try: |
479 try: |
505 if l.__class__ not in (str, unicode) or not len(l): |
480 if not isinstance(l, str) or not len(l): |
506 l = str(self.btemp.GetValue().split()[0]) |
481 l = str(self.btemp.GetValue().split()[0]) |
507 l = l.lower().replace(", ", ".") |
482 l = l.lower().replace(", ", ".") |
508 for i in self.bedtemps.keys(): |
483 for i in self.bedtemps.keys(): |
509 l = l.replace(i, self.bedtemps[i]) |
484 l = l.replace(i, self.bedtemps[i]) |
510 f = float(l) |
485 f = float(l) |
511 if f >= 0: |
486 if f >= 0: |
512 if self.p.online: |
487 if self.p.online: |
513 self.p.send_now("M140 S" + l) |
488 self.p.send_now("M140 S" + l) |
514 self.log(_("Setting bed temperature to %f degrees Celsius.") % f) |
489 self.log(_("Setting bed temperature to %g degrees Celsius.") % f) |
515 self.setbedgui(f) |
490 self.setbedgui(f) |
516 else: |
491 else: |
517 self.logError(_("Printer is not online.")) |
492 self.logError(_("Printer is not online.")) |
518 else: |
493 else: |
519 self.logError(_("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.")) |
494 self.logError(_("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.")) |
520 except Exception, x: |
495 except Exception as x: |
521 self.logError(_("You must enter a temperature. (%s)") % (repr(x),)) |
496 self.logError(_("You must enter a temperature. (%s)") % (repr(x),)) |
522 |
497 |
523 def do_setspeed(self, l = ""): |
498 def do_setspeed(self, l = ""): |
524 try: |
499 try: |
525 if l.__class__ not in (str, unicode) or not len(l): |
500 if not isinstance(l, str) or not len(l): |
526 l = str(self.speed_slider.GetValue()) |
501 l = str(self.speed_slider.GetValue()) |
527 else: |
502 else: |
528 l = l.lower() |
503 l = l.lower() |
529 speed = int(l) |
504 speed = int(l) |
530 if self.p.online: |
505 if self.p.online: |
531 self.p.send_now("M220 S" + l) |
506 self.p.send_now("M220 S" + l) |
532 self.log(_("Setting print speed factor to %d%%.") % speed) |
507 self.log(_("Setting print speed factor to %d%%.") % speed) |
533 else: |
508 else: |
534 self.logError(_("Printer is not online.")) |
509 self.logError(_("Printer is not online.")) |
535 except Exception, x: |
510 except Exception as x: |
536 self.logError(_("You must enter a speed. (%s)") % (repr(x),)) |
511 self.logError(_("You must enter a speed. (%s)") % (repr(x),)) |
537 |
512 |
538 def do_setflow(self, l = ""): |
513 def do_setflow(self, l = ""): |
539 try: |
514 try: |
540 if l.__class__ not in (str, unicode) or not len(l): |
515 if not isinstance(l, str) or not len(l): |
541 l = str(self.flow_slider.GetValue()) |
516 l = str(self.flow_slider.GetValue()) |
542 else: |
517 else: |
543 l = l.lower() |
518 l = l.lower() |
544 flow = int(l) |
519 flow = int(l) |
545 if self.p.online: |
520 if self.p.online: |
546 self.p.send_now("M221 S" + l) |
521 self.p.send_now("M221 S" + l) |
547 self.log(_("Setting print flow factor to %d%%.") % flow) |
522 self.log(_("Setting print flow factor to %d%%.") % flow) |
548 else: |
523 else: |
549 self.logError(_("Printer is not online.")) |
524 self.logError(_("Printer is not online.")) |
550 except Exception, x: |
525 except Exception as x: |
551 self.logError(_("You must enter a flow. (%s)") % (repr(x),)) |
526 self.logError(_("You must enter a flow. (%s)") % (repr(x),)) |
552 |
527 |
553 def setbedgui(self, f): |
528 def setbedgui(self, f): |
554 self.bsetpoint = f |
529 self.bsetpoint = f |
555 if self.display_gauges: self.bedtgauge.SetTarget(int(f)) |
530 if self.display_gauges: self.bedtgauge.SetTarget(int(f)) |
600 if os.path.exists(self.settings.port) or self.settings.port in scanned: |
575 if os.path.exists(self.settings.port) or self.settings.port in scanned: |
601 self.serialport.SetValue(self.settings.port) |
576 self.serialport.SetValue(self.settings.port) |
602 elif portslist: |
577 elif portslist: |
603 self.serialport.SetValue(portslist[0]) |
578 self.serialport.SetValue(portslist[0]) |
604 |
579 |
|
580 def appendCommandHistory(self): |
|
581 cmd = self.commandbox.Value |
|
582 hist = self.commandbox.history |
|
583 append = cmd and (not hist or hist[-1] != cmd) |
|
584 if append: |
|
585 self.commandbox.history.append(cmd) |
|
586 return append |
|
587 |
605 def cbkey(self, e): |
588 def cbkey(self, e): |
606 if e.GetKeyCode() == wx.WXK_UP: |
589 dir = {wx.WXK_UP: -1, wx.WXK_DOWN: 1}.get(e.KeyCode) |
|
590 if dir: |
607 if self.commandbox.histindex == len(self.commandbox.history): |
591 if self.commandbox.histindex == len(self.commandbox.history): |
608 self.commandbox.history.append(self.commandbox.GetValue()) # save current command |
592 if dir == 1: |
609 if len(self.commandbox.history): |
593 # do not cycle top => bottom |
610 self.commandbox.histindex = (self.commandbox.histindex - 1) % len(self.commandbox.history) |
594 return |
611 self.commandbox.SetValue(self.commandbox.history[self.commandbox.histindex]) |
595 #save unsent command before going back |
612 self.commandbox.SetSelection(0, len(self.commandbox.history[self.commandbox.histindex])) |
596 self.appendCommandHistory() |
613 elif e.GetKeyCode() == wx.WXK_DOWN: |
597 self.commandbox.histindex = max(0, min(self.commandbox.histindex + dir, len(self.commandbox.history))) |
614 if self.commandbox.histindex == len(self.commandbox.history): |
598 self.commandbox.Value = (self.commandbox.history[self.commandbox.histindex] |
615 self.commandbox.history.append(self.commandbox.GetValue()) # save current command |
599 if self.commandbox.histindex < len(self.commandbox.history) |
616 if len(self.commandbox.history): |
600 else '') |
617 self.commandbox.histindex = (self.commandbox.histindex + 1) % len(self.commandbox.history) |
601 self.commandbox.SetInsertionPointEnd() |
618 self.commandbox.SetValue(self.commandbox.history[self.commandbox.histindex]) |
|
619 self.commandbox.SetSelection(0, len(self.commandbox.history[self.commandbox.histindex])) |
|
620 else: |
602 else: |
621 e.Skip() |
603 e.Skip() |
622 |
604 |
623 def plate(self, e): |
605 def plate(self, e): |
624 from . import stlplater as plater |
606 from . import stlplater as plater |
773 if not self.IsFrozen(): |
755 if not self.IsFrozen(): |
774 wx.CallAfter(self.addtexttolog, l) |
756 wx.CallAfter(self.addtexttolog, l) |
775 |
757 |
776 def addtexttolog(self, text): |
758 def addtexttolog(self, text): |
777 try: |
759 try: |
778 self.logbox.AppendText(text) |
|
779 max_length = 20000 |
760 max_length = 20000 |
780 current_length = self.logbox.GetLastPosition() |
761 current_length = self.logbox.GetLastPosition() |
781 if current_length > max_length: |
762 if current_length > max_length: |
782 self.logbox.Remove(0, current_length / 10) |
763 self.logbox.Remove(0, current_length / 10) |
|
764 currentCaretPosition = self.logbox.GetInsertionPoint() |
|
765 currentLengthOfText = self.logbox.GetLastPosition() |
|
766 if self.autoscrolldisable: |
|
767 self.logbox.Freeze() |
|
768 currentSelectionStart, currentSelectionEnd = self.logbox.GetSelection() |
|
769 self.logbox.SetInsertionPointEnd() |
|
770 self.logbox.AppendText(text) |
|
771 self.logbox.SetInsertionPoint(currentCaretPosition) |
|
772 self.logbox.SetSelection(currentSelectionStart, currentSelectionEnd) |
|
773 self.logbox.Thaw() |
|
774 else: |
|
775 self.logbox.SetInsertionPointEnd() |
|
776 self.logbox.AppendText(text) |
|
777 |
783 except: |
778 except: |
784 self.log(_("Attempted to write invalid text to console, which could be due to an invalid baudrate")) |
779 self.log(_("Attempted to write invalid text to console, which could be due to an invalid baudrate")) |
785 |
780 |
786 def clear_log(self, e): |
781 def clear_log(self, e): |
787 self.logbox.Clear() |
782 self.logbox.Clear() |
788 |
783 |
789 def set_verbose_communications(self, e): |
784 def set_verbose_communications(self, e): |
790 self.p.loud = e.IsChecked() |
785 self.p.loud = e.IsChecked() |
791 |
786 |
|
787 def set_autoscrolldisable(self,e): |
|
788 self.autoscrolldisable = e.IsChecked() |
|
789 |
792 def sendline(self, e): |
790 def sendline(self, e): |
793 command = self.commandbox.GetValue() |
791 command = self.commandbox.Value |
794 if not len(command): |
792 if not len(command): |
795 return |
793 return |
796 wx.CallAfter(self.addtexttolog, ">>> " + command + "\n") |
794 logging.info(">>> " + command) |
797 line = self.precmd(str(command)) |
795 line = self.precmd(str(command)) |
798 self.onecmd(line) |
796 self.onecmd(line) |
799 self.commandbox.SetSelection(0, len(command)) |
797 self.appendCommandHistory() |
800 self.commandbox.history.append(command) |
|
801 self.commandbox.histindex = len(self.commandbox.history) |
798 self.commandbox.histindex = len(self.commandbox.history) |
|
799 self.commandbox.Value = '' |
802 |
800 |
803 # -------------------------------------------------------------- |
801 # -------------------------------------------------------------- |
804 # Main menu handling & actions |
802 # Main menu handling & actions |
805 # -------------------------------------------------------------- |
803 # -------------------------------------------------------------- |
806 |
804 |
807 def create_menu(self): |
805 def create_menu(self): |
808 """Create main menu""" |
806 """Create main menu""" |
809 self.menustrip = wx.MenuBar() |
807 |
810 # File menu |
808 # File menu |
811 m = wx.Menu() |
809 m = wx.Menu() |
812 self.Bind(wx.EVT_MENU, self.loadfile, m.Append(-1, _("&Open..."), _(" Open file"))) |
810 self.Bind(wx.EVT_MENU, self.loadfile, m.Append(-1, _("&Open...\tCtrl+O"), _(" Open file"))) |
813 self.savebtn = m.Append(-1, _("&Save..."), _(" Save file")) |
811 self.savebtn = m.Append(-1, _("&Save..."), _(" Save file")) |
814 self.savebtn.Enable(False) |
812 self.savebtn.Enable(False) |
815 self.Bind(wx.EVT_MENU, self.savefile, self.savebtn) |
813 self.Bind(wx.EVT_MENU, self.savefile, self.savebtn) |
816 |
814 |
817 self.filehistory = wx.FileHistory(maxFiles = 8, idBase = wx.ID_FILE1) |
815 self.filehistory = wx.FileHistory(maxFiles = 8, idBase = wx.ID_FILE1) |
818 recent = wx.Menu() |
816 recent = wx.Menu() |
819 self.filehistory.UseMenu(recent) |
817 self.filehistory.UseMenu(recent) |
820 self.Bind(wx.EVT_MENU_RANGE, self.load_recent_file, |
818 self.Bind(wx.EVT_MENU_RANGE, self.load_recent_file, |
821 id = wx.ID_FILE1, id2 = wx.ID_FILE9) |
819 id = wx.ID_FILE1, id2 = wx.ID_FILE9) |
822 m.AppendMenu(wx.ID_ANY, _("&Recent Files"), recent) |
820 m.Append(wx.ID_ANY, _("&Recent Files"), recent) |
823 self.Bind(wx.EVT_MENU, self.clear_log, m.Append(-1, _("Clear console"), _(" Clear output console"))) |
821 self.Bind(wx.EVT_MENU, self.clear_log, m.Append(-1, _("Clear console\tCtrl+L"), _(" Clear output console"))) |
824 self.Bind(wx.EVT_MENU, self.on_exit, m.Append(wx.ID_EXIT, _("E&xit"), _(" Closes the Window"))) |
822 self.Bind(wx.EVT_MENU, self.on_exit, m.Append(wx.ID_EXIT, _("E&xit"), _(" Closes the Window"))) |
825 self.menustrip.Append(m, _("&File")) |
823 self.menustrip.Append(m, _("&File")) |
826 |
824 |
|
825 # Tools Menu |
827 m = wx.Menu() |
826 m = wx.Menu() |
828 self.Bind(wx.EVT_MENU, self.do_editgcode, m.Append(-1, _("&Edit..."), _(" Edit open file"))) |
827 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"))) |
828 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"))) |
829 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"))) |
830 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"))) |
831 self.Bind(wx.EVT_MENU, self.project, m.Append(-1, _("Projector"), _(" Project slices"))) |
|
832 self.Bind(wx.EVT_MENU, |
|
833 self.show_spool_manager, |
|
834 m.Append(-1, _("Spool Manager"), |
|
835 _(" Manage different spools of filament"))) |
833 self.menustrip.Append(m, _("&Tools")) |
836 self.menustrip.Append(m, _("&Tools")) |
834 |
837 |
|
838 # Advanced Menu |
835 m = wx.Menu() |
839 m = wx.Menu() |
836 self.recoverbtn = m.Append(-1, _("Recover"), _(" Recover previous print after a disconnect (homes X, Y, restores Z and E status)")) |
840 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) |
841 self.recoverbtn.Disable = lambda *a: self.recoverbtn.Enable(False) |
838 self.Bind(wx.EVT_MENU, self.recover, self.recoverbtn) |
842 self.Bind(wx.EVT_MENU, self.recover, self.recoverbtn) |
839 self.menustrip.Append(m, _("&Advanced")) |
843 self.menustrip.Append(m, _("&Advanced")) |
929 Printrun. If not, see <http://www.gnu.org/licenses/>.""" |
942 Printrun. If not, see <http://www.gnu.org/licenses/>.""" |
930 |
943 |
931 info.SetLicence(licence) |
944 info.SetLicence(licence) |
932 info.AddDeveloper('Kliment Yanev') |
945 info.AddDeveloper('Kliment Yanev') |
933 info.AddDeveloper('Guillaume Seguin') |
946 info.AddDeveloper('Guillaume Seguin') |
934 info.AddDeveloper('Malte Bayer') |
947 |
935 |
948 wx.adv.AboutBox(info) |
936 wx.AboutBox(info) |
|
937 |
949 |
938 # -------------------------------------------------------------- |
950 # -------------------------------------------------------------- |
939 # Settings & command line handling (including update callbacks) |
951 # Settings & command line handling (including update callbacks) |
940 # -------------------------------------------------------------- |
952 # -------------------------------------------------------------- |
|
953 |
941 def _add_settings(self, size): |
954 def _add_settings(self, size): |
942 self._lc_add_settings(size) |
|
943 |
|
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) |
955 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")) |
956 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) |
957 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")) |
958 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")) |
959 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) |
960 self.settings._add(BooleanSetting("display_progress_on_printer", False, _("Display progress on printer"), _("Show progress on printers display (sent via M117, might not be supported by all printers)"), "Printer")) |
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) |
961 self.settings._add(SpinSetting("printer_progress_update_interval", 10., 0, 120, _("Printer progress update interval"), _("Interval in which pronterface sends the progress to the printer if enabled, in seconds"), "Printer")) |
|
962 self.settings._add(BooleanSetting("cutting_as_extrusion", True, _("Display cutting moves"), _("Show moves where spindle is active as printing moves"), "Printer")) |
|
963 self.settings._add(ComboSetting("uimode", _("Standard"), [_("Standard"), _("Compact"), ], _("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"), "UI"), self.reload_ui) |
|
964 #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"), "UI"), self.reload_ui) |
|
965 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) |
966 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")) |
967 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) |
968 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) |
969 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")) |
970 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) |
971 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) |
972 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")) |
973 self.settings._add(BooleanSetting("trackcurrentlayer3d", False, _("Track current layer in main 3D view"), _("Track the currently printing layer in the main 3D visualization"), "Viewer")) |
966 self.settings._add(HiddenSetting("last_window_width", size[0])) |
981 self.settings._add(HiddenSetting("last_window_width", size[0])) |
967 self.settings._add(HiddenSetting("last_window_height", size[1])) |
982 self.settings._add(HiddenSetting("last_window_height", size[1])) |
968 self.settings._add(HiddenSetting("last_window_maximized", False)) |
983 self.settings._add(HiddenSetting("last_window_maximized", False)) |
969 self.settings._add(HiddenSetting("last_sash_position", -1)) |
984 self.settings._add(HiddenSetting("last_sash_position", -1)) |
970 self.settings._add(HiddenSetting("last_bed_temperature", 0.0)) |
985 self.settings._add(HiddenSetting("last_bed_temperature", 0.0)) |
971 self.settings._add(HiddenSetting("last_file_path", u"")) |
986 self.settings._add(HiddenSetting("last_file_path", "")) |
972 self.settings._add(HiddenSetting("last_file_filter", 0)) |
987 self.settings._add(HiddenSetting("last_file_filter", 0)) |
973 self.settings._add(HiddenSetting("last_temperature", 0.0)) |
988 self.settings._add(HiddenSetting("last_temperature", 0.0)) |
974 self.settings._add(StaticTextSetting("separator_2d_viewer", _("2D viewer options"), "", group = "Viewer")) |
989 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) |
990 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) |
991 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) |
992 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) |
993 self.settings._add(ColorSetting("bgcolor", self._preferred_bgcolour_hex(), _("Background color"), _("Pronterface background color"), "Colors", isRGBA=False), self.reload_ui) |
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) |
994 self.settings._add(ColorSetting("graph_color_background", "#FAFAC7", _("Graph background color"), _("Color of the temperature graph background"), "Colors", isRGBA=False), self.reload_ui) |
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) |
995 self.settings._add(ColorSetting("gcview_color_background", "#FAFAC7FF", _("3D view background color"), _("Color of the 3D view background"), "Colors"), self.update_gcview_colors) |
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) |
996 self.settings._add(ColorSetting("gcview_color_travel", "#99999999", _("3D view travel moves color"), _("Color of travel moves in 3D view"), "Colors"), self.update_gcview_colors) |
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) |
997 self.settings._add(ColorSetting("gcview_color_tool0", "#FF000099", _("3D view print moves color"), _("Color of print moves with tool 0 in 3D view"), "Colors"), self.update_gcview_colors) |
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) |
998 self.settings._add(ColorSetting("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) |
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) |
999 self.settings._add(ColorSetting("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) |
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) |
1000 self.settings._add(ColorSetting("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) |
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) |
1001 self.settings._add(ColorSetting("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) |
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) |
1002 self.settings._add(ColorSetting("gcview_color_printed", "#33BF0099", _("3D view printed moves color"), _("Color of printed moves in 3D view"), "Colors"), self.update_gcview_colors) |
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) |
1003 self.settings._add(ColorSetting("gcview_color_current", "#00E5FFCC", _("3D view current layer moves color"), _("Color of moves in current layer in 3D view"), "Colors"), self.update_gcview_colors) |
|
1004 self.settings._add(ColorSetting("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) |
989 self.settings._add(StaticTextSetting("note1", _("Note:"), _("Changing some of these settings might require a restart to get effect"), group = "UI")) |
1005 self.settings._add(StaticTextSetting("note1", _("Note:"), _("Changing some of these settings might require a restart to get effect"), group = "UI")) |
990 recentfilessetting = StringSetting("recentfiles", "[]") |
1006 recentfilessetting = StringSetting("recentfiles", "[]") |
991 recentfilessetting.hidden = True |
1007 recentfilessetting.hidden = True |
992 self.settings._add(recentfilessetting, self.update_recent_files) |
1008 self.settings._add(recentfilessetting, self.update_recent_files) |
|
1009 |
|
1010 def _preferred_bgcolour_hex(self): |
|
1011 id = wx.SYS_COLOUR_WINDOW \ |
|
1012 if platform.system() == 'Windows' \ |
|
1013 else wx.SYS_COLOUR_BACKGROUND |
|
1014 sys_bgcolour = wx.SystemSettings.GetColour(id) |
|
1015 return sys_bgcolour.GetAsString(flags=wx.C2S_HTML_SYNTAX) |
993 |
1016 |
994 def add_cmdline_arguments(self, parser): |
1017 def add_cmdline_arguments(self, parser): |
995 pronsole.pronsole.add_cmdline_arguments(self, parser) |
1018 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") |
1019 parser.add_argument('-a', '--autoconnect', help = _("automatically try to connect to printer on startup"), action = "store_true") |
997 |
1020 |
1538 self.loading_gcode = True |
1567 self.loading_gcode = True |
1539 self.loading_gcode_message = _("Loading %s...") % self.filename |
1568 self.loading_gcode_message = _("Loading %s...") % self.filename |
1540 if self.settings.mainviz == "None": |
1569 if self.settings.mainviz == "None": |
1541 gcode = gcoder.LightGCode(deferred = True) |
1570 gcode = gcoder.LightGCode(deferred = True) |
1542 else: |
1571 else: |
1543 gcode = gcoder.GCode(deferred = True) |
1572 gcode = gcoder.GCode(deferred = True, cutting_as_extrusion = self.settings.cutting_as_extrusion) |
1544 self.viz_last_yield = 0 |
1573 self.viz_last_yield = 0 |
1545 self.viz_last_layer = -1 |
1574 self.viz_last_layer = -1 |
1546 self.start_viz_thread(gcode) |
1575 self.start_viz_thread(gcode) |
1547 return gcode |
1576 return gcode |
1548 |
1577 |
1549 def post_gcode_load(self, print_stats = True): |
1578 def post_gcode_load(self, print_stats = True, failed=False): |
1550 # Must be called in wx.CallAfter for safety |
1579 # Must be called in wx.CallAfter for safety |
1551 self.loading_gcode = False |
1580 self.loading_gcode = False |
1552 self.SetTitle(_(u"Pronterface - %s") % self.filename) |
1581 if not failed: |
1553 message = _("Loaded %s, %d lines") % (self.filename, len(self.fgcode),) |
1582 self.SetTitle(_("Pronterface - %s") % self.filename) |
1554 self.log(message) |
1583 message = _("Loaded %s, %d lines") % (self.filename, len(self.fgcode),) |
1555 self.statusbar.SetStatusText(message) |
1584 self.log(message) |
1556 self.savebtn.Enable(True) |
1585 self.statusbar.SetStatusText(message) |
|
1586 self.savebtn.Enable(True) |
1557 self.loadbtn.SetLabel(_("Load File")) |
1587 self.loadbtn.SetLabel(_("Load File")) |
1558 self.printbtn.SetLabel(_("Print")) |
1588 self.printbtn.SetLabel(_("&Print")) |
1559 self.pausebtn.SetLabel(_("Pause")) |
1589 self.pausebtn.SetLabel(_("&Pause")) |
1560 self.pausebtn.Disable() |
1590 self.pausebtn.Disable() |
1561 self.recoverbtn.Disable() |
1591 self.recoverbtn.Disable() |
1562 if self.p.online: |
1592 if not failed and self.p.online: |
1563 self.printbtn.Enable() |
1593 self.printbtn.Enable() |
1564 self.toolbarsizer.Layout() |
1594 self.toolbarsizer.Layout() |
1565 self.viz_last_layer = None |
1595 self.viz_last_layer = None |
1566 if print_stats: |
1596 if print_stats: |
1567 self.output_gcode_stats() |
1597 self.output_gcode_stats() |
1568 |
1598 |
|
1599 def calculate_remaining_filament(self, length, extruder = 0): |
|
1600 """ |
|
1601 float calculate_remaining_filament( float length, int extruder ) |
|
1602 |
|
1603 Calculate the remaining length of filament for the given extruder if |
|
1604 the given length were to be extruded. |
|
1605 """ |
|
1606 |
|
1607 remainder = self.spool_manager.getRemainingFilament(extruder) - length |
|
1608 minimum_warning_length = 1000.0 |
|
1609 if remainder < minimum_warning_length: |
|
1610 self.log(_("\nWARNING: Currently loaded spool for extruder " + |
|
1611 "%d will likely run out of filament during the print.\n" % |
|
1612 extruder)) |
|
1613 return remainder |
|
1614 |
1569 def output_gcode_stats(self): |
1615 def output_gcode_stats(self): |
1570 gcode = self.fgcode |
1616 gcode = self.fgcode |
1571 self.log(_("%.2fmm of filament used in this print") % gcode.filament_length) |
1617 self.spool_manager.refresh() |
1572 if(len(gcode.filament_length_multi)>1): |
1618 |
|
1619 self.log(_("%s of filament used in this print") % format_length(gcode.filament_length)) |
|
1620 |
|
1621 if len(gcode.filament_length_multi) > 1: |
1573 for i in enumerate(gcode.filament_length_multi): |
1622 for i in enumerate(gcode.filament_length_multi): |
1574 print "Extruder %d: %0.02fmm" % (i[0],i[1]) |
1623 if self.spool_manager.getSpoolName(i[0]) == None: |
|
1624 logging.info("- Extruder %d: %0.02fmm" % (i[0], i[1])) |
|
1625 else: |
|
1626 logging.info(("- Extruder %d: %0.02fmm" % (i[0], i[1]) + |
|
1627 " from spool '%s' (%.2fmm will remain)" % |
|
1628 (self.spool_manager.getSpoolName(i[0]), |
|
1629 self.calculate_remaining_filament(i[1], i[0])))) |
|
1630 elif self.spool_manager.getSpoolName(0) != None: |
|
1631 self.log( |
|
1632 _("Using spool '%s' (%s of filament will remain)") % |
|
1633 (self.spool_manager.getSpoolName(0), |
|
1634 format_length(self.calculate_remaining_filament( |
|
1635 gcode.filament_length, 0)))) |
|
1636 |
1575 self.log(_("The print goes:")) |
1637 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)) |
1638 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)) |
1639 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)) |
1640 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()) |
1641 self.log(_("Estimated duration: %d layers, %s") % gcode.estimate_duration()) |
1580 |
1642 |
1581 def loadviz(self, gcode = None): |
1643 def loadviz(self, gcode = None): |
1582 self.gviz.clear() |
1644 try: |
1583 self.gwindow.p.clear() |
1645 self.gviz.clear() |
1584 if gcode is not None: |
1646 self.gwindow.p.clear() |
1585 generator = self.gviz.addfile_perlayer(gcode, True) |
1647 if gcode is not None: |
1586 next_layer = 0 |
1648 generator = self.gviz.addfile_perlayer(gcode, True) |
1587 # Progressive loading of visualization |
1649 next_layer = 0 |
1588 # We load layers up to the last one which has been processed in GCoder |
1650 # Progressive loading of visualization |
1589 # (self.viz_last_layer) |
1651 # We load layers up to the last one which has been processed in GCoder |
1590 # Once the GCode has been entirely loaded, this variable becomes None, |
1652 # (self.viz_last_layer) |
1591 # indicating that we can do the last generator call to finish the |
1653 # Once the GCode has been entirely loaded, this variable becomes None, |
1592 # loading of the visualization, which will itself return None. |
1654 # indicating that we can do the last generator call to finish the |
1593 # During preloading we verify that the layer we added is the one we |
1655 # loading of the visualization, which will itself return None. |
1594 # expected through the assert call. |
1656 # During preloading we verify that the layer we added is the one we |
1595 while True: |
1657 # expected through the assert call. |
1596 global pronterface_quitting |
1658 while True: |
1597 if pronterface_quitting: |
1659 global pronterface_quitting |
1598 return |
1660 if pronterface_quitting: |
1599 max_layer = self.viz_last_layer |
1661 return |
1600 if max_layer is None: |
1662 max_layer = self.viz_last_layer |
1601 break |
1663 if max_layer is None: |
1602 while next_layer <= max_layer: |
1664 break |
1603 assert(generator.next() == next_layer) |
1665 start_layer = next_layer |
|
1666 while next_layer <= max_layer: |
|
1667 assert next(generator) == next_layer |
|
1668 next_layer += 1 |
|
1669 if next_layer != start_layer: |
|
1670 wx.CallAfter(self.gviz.Refresh) |
|
1671 time.sleep(0.1) |
|
1672 generator_output = next(generator) |
|
1673 while generator_output is not None: |
|
1674 assert generator_output == next_layer |
1604 next_layer += 1 |
1675 next_layer += 1 |
1605 time.sleep(0.1) |
1676 generator_output = next(generator) |
1606 generator_output = generator.next() |
1677 else: |
1607 while generator_output is not None: |
1678 # If GCode is not being loaded asynchroneously, it is already |
1608 assert(generator_output in (None, next_layer)) |
1679 # loaded, so let's make visualization sequentially |
1609 next_layer += 1 |
1680 gcode = self.fgcode |
1610 generator_output = generator.next() |
1681 self.gviz.addfile(gcode) |
1611 else: |
1682 wx.CallAfter(self.gviz.Refresh) |
1612 # If GCode is not being loaded asynchroneously, it is already |
1683 # Load external window sequentially now that everything is ready. |
1613 # loaded, so let's make visualization sequentially |
1684 # We can't really do any better as the 3D viewer might clone the |
1614 gcode = self.fgcode |
1685 # finalized model from the main visualization |
1615 self.gviz.addfile(gcode) |
1686 self.gwindow.p.addfile(gcode) |
1616 wx.CallAfter(self.gviz.Refresh) |
1687 except: |
1617 # Load external window sequentially now that everything is ready. |
1688 logging.error(traceback.format_exc()) |
1618 # We can't really do any better as the 3D viewer might clone the |
1689 wx.CallAfter(self.gviz.Refresh) |
1619 # finalized model from the main visualization |
|
1620 self.gwindow.p.addfile(gcode) |
|
1621 |
1690 |
1622 # -------------------------------------------------------------- |
1691 # -------------------------------------------------------------- |
1623 # File saving handling |
1692 # File saving handling |
1624 # -------------------------------------------------------------- |
1693 # -------------------------------------------------------------- |
1625 |
1694 |
1660 def endcb(self): |
1729 def endcb(self): |
1661 """Callback on print end/pause""" |
1730 """Callback on print end/pause""" |
1662 pronsole.pronsole.endcb(self) |
1731 pronsole.pronsole.endcb(self) |
1663 if self.p.queueindex == 0: |
1732 if self.p.queueindex == 0: |
1664 self.p.runSmallScript(self.endScript) |
1733 self.p.runSmallScript(self.endScript) |
|
1734 if self.settings.display_progress_on_printer: |
|
1735 printer_progress_string = "M117 Finished Print" |
|
1736 self.p.send_now(printer_progress_string) |
1665 wx.CallAfter(self.pausebtn.Disable) |
1737 wx.CallAfter(self.pausebtn.Disable) |
1666 wx.CallAfter(self.printbtn.SetLabel, _("Print")) |
1738 wx.CallAfter(self.printbtn.SetLabel, _("&Print")) |
1667 wx.CallAfter(self.toolbarsizer.Layout) |
1739 wx.CallAfter(self.toolbarsizer.Layout) |
1668 wx.CallAfter(self.endcb_lasercut) |
|
1669 |
1740 |
1670 def online(self): |
1741 def online(self): |
1671 """Callback when printer goes online""" |
1742 """Callback when printer goes online""" |
1672 self.log(_("Printer is now online.")) |
1743 self.log(_("Printer is now online.")) |
1673 wx.CallAfter(self.online_gui) |
1744 wx.CallAfter(self.online_gui) |
1674 |
1745 |
1675 def online_gui(self): |
1746 def online_gui(self): |
1676 """Callback when printer goes online (graphical bits)""" |
1747 """Callback when printer goes online (graphical bits)""" |
1677 self.connectbtn.SetLabel(_("Disconnect")) |
1748 self.connectbtn.SetLabel(_("Dis&connect")) |
1678 self.connectbtn.SetToolTip(wx.ToolTip("Disconnect from the printer")) |
1749 self.connectbtn.SetToolTip(wx.ToolTip("Disconnect from the printer")) |
1679 self.connectbtn.Bind(wx.EVT_BUTTON, self.disconnect) |
1750 self.connectbtn_cb_var = self.disconnect |
1680 |
1751 |
1681 if hasattr(self, "extrudersel"): |
1752 if hasattr(self, "extrudersel"): |
1682 self.do_tool(self.extrudersel.GetValue()) |
1753 self.do_tool(self.extrudersel.GetValue()) |
1683 |
1754 |
1684 self.gui_set_connected() |
1755 self.gui_set_connected() |
1929 self.custombuttons_widgets = [] |
2000 self.custombuttons_widgets = [] |
1930 custombuttons = self.custombuttons[:] + [None] |
2001 custombuttons = self.custombuttons[:] + [None] |
1931 for i, btndef in enumerate(custombuttons): |
2002 for i, btndef in enumerate(custombuttons): |
1932 if btndef is None: |
2003 if btndef is None: |
1933 if i == len(custombuttons) - 1: |
2004 if i == len(custombuttons) - 1: |
1934 self.newbuttonbutton = b = wx.Button(self.centerpanel, -1, "+", size = (19, 18), style = wx.BU_EXACTFIT) |
2005 self.newbuttonbutton = b = wx.Button(self.centerpanel, -1, "+", size = (35, 18), style = wx.BU_EXACTFIT) |
1935 b.SetForegroundColour("#4444ff") |
2006 b.SetForegroundColour("#4444ff") |
1936 b.SetToolTip(wx.ToolTip(_("click to add new custom button"))) |
2007 b.SetToolTip(wx.ToolTip(_("click to add new custom button"))) |
1937 b.Bind(wx.EVT_BUTTON, self.cbutton_edit) |
2008 b.Bind(wx.EVT_BUTTON, self.cbutton_edit) |
1938 else: |
2009 else: |
1939 b = wx.StaticText(self.panel, -1, "") |
2010 b = wx.StaticText(self.panel, -1, "") |
1940 else: |
2011 else: |
1941 b = wx.Button(self.centerpanel, -1, btndef.label, style = wx.BU_EXACTFIT) |
2012 b = wx.Button(self.centerpanel, -1, btndef.label, style = wx.BU_EXACTFIT) |
1942 b.SetToolTip(wx.ToolTip(_("Execute command: ") + btndef.command)) |
2013 b.SetToolTip(wx.ToolTip(_("Execute command: ") + btndef.command)) |
1943 if btndef.background: |
2014 if btndef.background: |
1944 b.SetBackgroundColour(btndef.background) |
2015 b.SetBackgroundColour(btndef.background) |
1945 rr, gg, bb = b.GetBackgroundColour().Get() |
2016 rr, gg, bb, aa = b.GetBackgroundColour().Get() #last item is alpha |
1946 if 0.3 * rr + 0.59 * gg + 0.11 * bb < 60: |
2017 if 0.3 * rr + 0.59 * gg + 0.11 * bb < 60: |
1947 b.SetForegroundColour("#ffffff") |
2018 b.SetForegroundColour("#ffffff") |
1948 b.custombutton = i |
2019 b.custombutton = i |
1949 b.properties = btndef |
2020 b.properties = btndef |
1950 if btndef is not None: |
2021 if btndef is not None: |
1951 b.Bind(wx.EVT_BUTTON, self.process_button) |
2022 b.Bind(wx.EVT_BUTTON, self.process_button) |
1952 b.Bind(wx.EVT_MOUSE_EVENTS, self.editbutton) |
2023 b.Bind(wx.EVT_MOUSE_EVENTS, self.editbutton) |
1953 self.custombuttons_widgets.append(b) |
2024 self.custombuttons_widgets.append(b) |
1954 if type(self.cbuttonssizer) == wx.GridBagSizer: |
2025 if isinstance(self.cbuttonssizer, wx.GridBagSizer): |
1955 self.cbuttonssizer.Add(b, pos = (i // 4, i % 4), flag = wx.EXPAND) |
2026 self.cbuttonssizer.Add(b, pos = (i // 4, i % 4), flag = wx.EXPAND) |
1956 else: |
2027 else: |
1957 self.cbuttonssizer.Add(b, flag = wx.EXPAND) |
2028 self.cbuttonssizer.Add(b, flag = wx.EXPAND) |
1958 self.centerpanel.Layout() |
2029 self.centerpanel.Layout() |
1959 self.centerpanel.GetContainingSizer().Layout() |
2030 self.centerpanel.GetContainingSizer().Layout() |
2078 self.Bind(wx.EVT_MENU, lambda e, button = e.GetEventObject(): self.cbutton_remove(e, button), item) |
2149 self.Bind(wx.EVT_MENU, lambda e, button = e.GetEventObject(): self.cbutton_remove(e, button), item) |
2079 else: |
2150 else: |
2080 item = popupmenu.Append(-1, _("Add custom button")) |
2151 item = popupmenu.Append(-1, _("Add custom button")) |
2081 self.Bind(wx.EVT_MENU, self.cbutton_edit, item) |
2152 self.Bind(wx.EVT_MENU, self.cbutton_edit, item) |
2082 self.panel.PopupMenu(popupmenu, pos) |
2153 self.panel.PopupMenu(popupmenu, pos) |
2083 elif e.Dragging() and e.ButtonIsDown(wx.MOUSE_BTN_LEFT): |
2154 elif e.Dragging() and e.LeftIsDown(): |
2084 obj = e.GetEventObject() |
2155 obj = e.GetEventObject() |
2085 scrpos = obj.ClientToScreen(e.GetPosition()) |
2156 scrpos = obj.ClientToScreen(e.GetPosition()) |
2086 if not hasattr(self, "dragpos"): |
2157 if not hasattr(self, "dragpos"): |
2087 self.dragpos = scrpos |
2158 self.dragpos = scrpos |
2088 e.Skip() |
2159 e.Skip() |
2089 return |
2160 return |
2090 else: |
2161 else: |
2091 dx, dy = self.dragpos[0] - scrpos[0], self.dragpos[1] - scrpos[1] |
2162 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 |
2163 if dx * dx + dy * dy < 30 * 30: # threshold to detect dragging for jittery mice |
2093 e.Skip() |
2164 e.Skip() |
2094 return |
2165 return |
2095 if not hasattr(self, "dragging"): |
2166 if not hasattr(self, "dragging"): |
2096 # init dragging of the custom button |
2167 # init dragging of the custom button |
2097 if hasattr(obj, "custombutton") and obj.properties is not None: |
2168 if hasattr(obj, "custombutton") and (not hasattr(obj,"properties") or obj.properties is not None): |
2098 for b in self.custombuttons_widgets: |
2169 for b in self.custombuttons_widgets: |
2099 if b.properties is None: |
2170 if not hasattr(b,"properties") or b.properties is None: |
2100 b.Enable() |
2171 b.Enable() |
2101 b.SetLabel("") |
2172 b.SetLabel("") |
2102 b.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) |
2173 b.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) |
2103 b.SetForegroundColour("black") |
2174 b.SetForegroundColour("black") |
2104 b.SetSize(obj.GetSize()) |
2175 b.SetSize(obj.GetSize()) |
2218 def delete_macro(self, macro_name): |
2289 def delete_macro(self, macro_name): |
2219 pronsole.pronsole.delete_macro(self, macro_name) |
2290 pronsole.pronsole.delete_macro(self, macro_name) |
2220 self.update_macros_menu() |
2291 self.update_macros_menu() |
2221 |
2292 |
2222 def new_macro(self, e = None): |
2293 def new_macro(self, e = None): |
2223 dialog = wx.Dialog(self, -1, _("Enter macro name"), size = (260, 85)) |
2294 dialog = wx.Dialog(self, -1, _("Enter macro name")) |
2224 panel = wx.Panel(dialog, -1) |
2295 text = wx.StaticText(dialog, -1, _("Macro name:")) |
2225 vbox = wx.BoxSizer(wx.VERTICAL) |
2296 namectrl = wx.TextCtrl(dialog, -1, style = wx.TE_PROCESS_ENTER) |
2226 wx.StaticText(panel, -1, _("Macro name:"), (8, 14)) |
2297 okb = wx.Button(dialog, wx.ID_OK, _("Ok")) |
2227 dialog.namectrl = wx.TextCtrl(panel, -1, '', (110, 8), size = (130, 24), style = wx.TE_PROCESS_ENTER) |
2298 dialog.Bind(wx.EVT_TEXT_ENTER, |
2228 hbox = wx.BoxSizer(wx.HORIZONTAL) |
2299 lambda e: dialog.EndModal(wx.ID_OK), namectrl) |
2229 okb = wx.Button(dialog, wx.ID_OK, _("Ok"), size = (60, 24)) |
2300 cancel_button = wx.Button(dialog, wx.ID_CANCEL, _("Cancel")) |
2230 dialog.Bind(wx.EVT_TEXT_ENTER, lambda e: dialog.EndModal(wx.ID_OK), dialog.namectrl) |
2301 |
2231 hbox.Add(okb) |
2302 # Layout |
2232 hbox.Add(wx.Button(dialog, wx.ID_CANCEL, _("Cancel"), size = (60, 24))) |
2303 ## Group the buttons horizontally |
2233 vbox.Add(panel) |
2304 buttons_sizer = wx.BoxSizer(wx.HORIZONTAL) |
2234 vbox.Add(hbox, 1, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 10) |
2305 buttons_sizer.Add(okb, 0) |
2235 dialog.SetSizer(vbox) |
2306 buttons_sizer.Add(cancel_button, 0) |
|
2307 ## Set a minimum size for the name control box |
|
2308 min_size = namectrl.GetTextExtent('Default Long Macro Name') |
|
2309 namectrl.SetMinSize(wx.Size(min_size.width, -1)) |
|
2310 ## Group the text and the name control box horizontally |
|
2311 name_sizer = wx.BoxSizer(wx.HORIZONTAL) |
|
2312 name_sizer.Add(text, 0, flag = wx.ALIGN_CENTER) |
|
2313 name_sizer.AddSpacer(10) |
|
2314 name_sizer.Add(namectrl, 1, wx.EXPAND) |
|
2315 ## Group everything vertically |
|
2316 dialog_sizer = wx.BoxSizer(wx.VERTICAL) |
|
2317 dialog_sizer.Add(name_sizer, 0, border = 10, |
|
2318 flag = wx.LEFT | wx.TOP | wx.RIGHT) |
|
2319 dialog_sizer.Add(buttons_sizer, 0, border = 10, |
|
2320 flag = wx.ALIGN_CENTER | wx.ALL) |
|
2321 dialog.SetSizerAndFit(dialog_sizer) |
2236 dialog.Centre() |
2322 dialog.Centre() |
|
2323 |
2237 macro = "" |
2324 macro = "" |
2238 if dialog.ShowModal() == wx.ID_OK: |
2325 if dialog.ShowModal() == wx.ID_OK: |
2239 macro = dialog.namectrl.GetValue() |
2326 macro = namectrl.GetValue() |
2240 if macro != "": |
2327 if macro != "": |
2241 wx.CallAfter(self.edit_macro, macro) |
2328 wx.CallAfter(self.edit_macro, macro) |
2242 dialog.Destroy() |
2329 dialog.Destroy() |
2243 return macro |
2330 return macro |
2244 |
2331 |
2245 def edit_macro(self, macro): |
2332 def edit_macro(self, macro): |
2246 if macro == "": return self.new_macro() |
2333 if macro == "": return self.new_macro() |
2247 if macro in self.macros: |
2334 if macro in self.macros: |
2248 old_def = self.macros[macro] |
2335 old_def = self.macros[macro] |
2249 elif len([c for c in macro.encode("ascii", "replace") if not c.isalnum() and c != "_"]): |
2336 elif len([chr(c) for c in macro.encode("ascii", "replace") if not chr(c).isalnum() and chr(c) != "_"]): |
2250 self.log(_("Macro name may contain only ASCII alphanumeric symbols and underscores")) |
2337 self.log(_("Macro name may contain only ASCII alphanumeric symbols and underscores")) |
2251 return |
2338 return |
2252 elif hasattr(self.__class__, "do_" + macro): |
2339 elif hasattr(self.__class__, "do_" + macro): |
2253 self.log(_("Name '%s' is being used by built-in command") % macro) |
2340 self.log(_("Name '%s' is being used by built-in command") % macro) |
2254 return |
2341 return |
2278 """List Slic3r configurations and create menu""" |
2365 """List Slic3r configurations and create menu""" |
2279 # Hack to get correct path for Slic3r config |
2366 # Hack to get correct path for Slic3r config |
2280 orig_appname = self.app.GetAppName() |
2367 orig_appname = self.app.GetAppName() |
2281 self.app.SetAppName("Slic3r") |
2368 self.app.SetAppName("Slic3r") |
2282 configpath = wx.StandardPaths.Get().GetUserDataDir() |
2369 configpath = wx.StandardPaths.Get().GetUserDataDir() |
2283 self.app.SetAppName(orig_appname) |
|
2284 self.slic3r_configpath = configpath |
2370 self.slic3r_configpath = configpath |
2285 configfile = os.path.join(configpath, "slic3r.ini") |
2371 configfile = os.path.join(configpath, "slic3r.ini") |
|
2372 if not os.path.exists(configfile): |
|
2373 self.app.SetAppName("Slic3rPE") |
|
2374 configpath = wx.StandardPaths.Get().GetUserDataDir() |
|
2375 self.slic3r_configpath = configpath |
|
2376 configfile = os.path.join(configpath, "slic3r.ini") |
|
2377 if not os.path.exists(configfile): |
|
2378 self.settings.slic3rintegration=False; |
|
2379 return |
|
2380 self.app.SetAppName(orig_appname) |
2286 config = self.read_slic3r_config(configfile) |
2381 config = self.read_slic3r_config(configfile) |
|
2382 version = config.get("dummy", "version") # Slic3r version |
2287 self.slic3r_configs = {} |
2383 self.slic3r_configs = {} |
2288 for cat in menus: |
2384 for cat in menus: |
2289 menu = menus[cat] |
2385 menu = menus[cat] |
2290 pattern = os.path.join(configpath, cat, "*.ini") |
2386 pattern = os.path.join(configpath, cat, "*.ini") |
2291 files = sorted(glob.glob(pattern)) |
2387 files = sorted(glob.glob(pattern)) |
2292 try: |
2388 try: |
2293 preset = config.get("presets", cat) |
2389 preset = config.get("presets", cat) |
|
2390 # Starting from Slic3r 1.3.0, preset names have no extension |
|
2391 if version.split(".") >= ["1","3","0"]: preset += ".ini" |
2294 self.slic3r_configs[cat] = preset |
2392 self.slic3r_configs[cat] = preset |
2295 except: |
2393 except: |
2296 preset = None |
2394 preset = None |
2297 self.slic3r_configs[cat] = None |
2395 self.slic3r_configs[cat] = None |
2298 for f in files: |
2396 for f in files: |
2303 lambda event, cat = cat, f = f: |
2401 lambda event, cat = cat, f = f: |
2304 self.set_slic3r_config(configfile, cat, f), item) |
2402 self.set_slic3r_config(configfile, cat, f), item) |
2305 |
2403 |
2306 def read_slic3r_config(self, configfile, parser = None): |
2404 def read_slic3r_config(self, configfile, parser = None): |
2307 """Helper to read a Slic3r configuration file""" |
2405 """Helper to read a Slic3r configuration file""" |
2308 import ConfigParser |
2406 import configparser |
2309 parser = ConfigParser.RawConfigParser() |
2407 parser = configparser.RawConfigParser() |
2310 |
2408 |
2311 class add_header(object): |
2409 class add_header: |
2312 def __init__(self, f): |
2410 def __init__(self, f): |
2313 self.f = f |
2411 self.f = f |
2314 self.header = '[dummy]' |
2412 self.header = '[dummy]' |
2315 |
2413 |
2316 def readline(self): |
2414 def readline(self): |
2317 if self.header: |
2415 if self.header: |
2318 try: return self.header |
2416 try: return self.header |
2319 finally: self.header = None |
2417 finally: self.header = None |
2320 else: |
2418 else: |
2321 return self.f.readline() |
2419 return self.f.readline() |
|
2420 |
|
2421 def __iter__(self): |
|
2422 import itertools |
|
2423 return itertools.chain([self.header], iter(self.f)) |
|
2424 |
2322 parser.readfp(add_header(open(configfile)), configfile) |
2425 parser.readfp(add_header(open(configfile)), configfile) |
2323 return parser |
2426 return parser |
2324 |
2427 |
2325 def set_slic3r_config(self, configfile, cat, file): |
2428 def set_slic3r_config(self, configfile, cat, file): |
2326 """Set new preset for a given category""" |
2429 """Set new preset for a given category""" |
2327 self.slic3r_configs[cat] = file |
2430 self.slic3r_configs[cat] = file |
2328 if self.settings.slic3rupdate: |
2431 if self.settings.slic3rupdate: |
2329 config = self.read_slic3r_config(configfile) |
2432 config = self.read_slic3r_config(configfile) |
2330 config.set("presets", cat, os.path.basename(file)) |
2433 version = config.get("dummy", "version") # Slic3r version |
|
2434 preset = os.path.basename(file) |
|
2435 # Starting from Slic3r 1.3.0, preset names have no extension |
|
2436 if version.split(".") >= ["1","3","0"]: |
|
2437 preset = os.path.splitext(preset)[0] |
|
2438 config.set("presets", cat, preset) |
2331 f = StringIO.StringIO() |
2439 f = StringIO.StringIO() |
2332 config.write(f) |
2440 config.write(f) |
2333 data = f.getvalue() |
2441 data = f.getvalue() |
2334 f.close() |
2442 f.close() |
2335 data = data.replace("[dummy]\n", "") |
2443 data = data.replace("[dummy]\n", "") |