printrun-src/printrun/pronterface.py

changeset 47
dcc64b767b64
parent 43
f7e9bd735ce1
child 48
3c27b4ee6fec
equal deleted inserted replaced
46:cce0af6351f0 47:dcc64b767b64
1 #!/usr/bin/env python
2
3 # FILE MODIFIED BY NEOSOFT - MALTE DI DONATO
4 # Embed Lasercut functions from laser.py
5 from . import laser
6 try:
7 from . import module_watcher
8 mw = module_watcher.ModuleWatcher()
9 mw.watch_module('laser')
10 mw.start_watching()
11 except Exception, e:
12 print e
13 print "ModuleWatcher not loaded, skipping autoreloading of changed modules"
14
15 # This file is part of the Printrun suite. 1 # This file is part of the Printrun suite.
16 # 2 #
17 # Printrun is free software: you can redistribute it and/or modify 3 # 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 4 # 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 5 # the Free Software Foundation, either version 3 of the License, or
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
100 try: 103 try:
101 self.target(data) 104 self.target(data)
102 except: 105 except:
103 pass 106 pass
104 if self.print_on_stdout: 107 if self.print_on_stdout:
105 try:
106 data = data.encode("utf-8")
107 except:
108 pass
109 self.stdout.write(data) 108 self.stdout.write(data)
110 109
111 def flush(self): 110 def flush(self):
112 if self.stdout: 111 if self.stdout:
113 self.stdout.flush() 112 self.stdout.flush()
114 113
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): 114 class PronterWindow(MainWindow, pronsole.pronsole):
127 115
128 _fgcode = None 116 _fgcode = None
117 printer_progress_time = time.time()
129 118
130 def _get_fgcode(self): 119 def _get_fgcode(self):
131 return self._fgcode 120 return self._fgcode
132 121
133 def _set_fgcode(self, value): 122 def _set_fgcode(self, value):
150 pronsole.pronsole.__init__(self) 139 pronsole.pronsole.__init__(self)
151 self.app = app 140 self.app = app
152 self.window_ready = False 141 self.window_ready = False
153 self.ui_ready = False 142 self.ui_ready = False
154 self._add_settings(size) 143 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 144
162 self.pauseScript = None #"pause.gcode" 145 self.pauseScript = None #"pause.gcode"
163 self.endScript = None #"end.gcode" 146 self.endScript = None #"end.gcode"
164 147
165 self.filename = filename 148 self.filename = filename
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:
368 self.panel.DestroyChildren() 282 self.panel.DestroyChildren()
369 self.gwindow.Destroy() 283 self.gwindow.Destroy()
370 self.reset_ui() 284 self.reset_ui()
371 285
372 # Create UI 286 # Create UI
287 self.create_menu()
288 self.update_recent_files("recentfiles", self.settings.recentfiles)
289 self.splitterwindow = None
373 if self.settings.uimode in (_("Tabbed"), _("Tabbed with platers")): 290 if self.settings.uimode in (_("Tabbed"), _("Tabbed with platers")):
374 self.createTabbedGui() 291 self.createTabbedGui()
375 else: 292 else:
376 self.createGui(self.settings.uimode == _("Compact"), 293 self.createGui(self.settings.uimode == _("Compact"),
377 self.settings.controlsmode == "Mini") 294 self.settings.controlsmode == "Mini")
378 295
379 if hasattr(self, "splitterwindow"): 296 if self.splitterwindow:
380 self.splitterwindow.SetSashPosition(self.settings.last_sash_position) 297 self.splitterwindow.SetSashPosition(self.settings.last_sash_position)
381 298
382 def splitter_resize(event): 299 def splitter_resize(event):
383 self.splitterwindow.UpdateSize() 300 self.splitterwindow.UpdateSize()
384 self.splitterwindow.Bind(wx.EVT_SIZE, splitter_resize) 301 self.splitterwindow.Bind(wx.EVT_SIZE, splitter_resize)
390 # Set gcview parameters here as they don't get set when viewers are 307 # Set gcview parameters here as they don't get set when viewers are
391 # created 308 # created
392 self.update_gcview_params() 309 self.update_gcview_params()
393 310
394 # Finalize 311 # Finalize
395 if self.online: 312 if self.p.online:
396 self.gui_set_connected() 313 self.gui_set_connected()
397 if self.ui_ready: 314 if self.ui_ready:
398 self.logbox.SetValue(logcontent) 315 self.logbox.SetValue(logcontent)
399 temppanel.Destroy() 316 temppanel.Destroy()
400 self.panel.Layout() 317 self.panel.Layout()
401 if self.fgcode: 318 if self.fgcode:
402 self.start_viz_thread() 319 self.start_viz_thread()
403 if self.settings.monitor:
404 self.update_monitor()
405 self.ui_ready = True 320 self.ui_ready = True
321 self.settings.monitor = temp_monitor
322 self.commandbox.history = read_history_from(self.history_file)
323 self.commandbox.histindex = len(self.commandbox.history)
406 self.Thaw() 324 self.Thaw()
325 if self.settings.monitor:
326 self.update_monitor()
407 327
408 def on_resize(self, event): 328 def on_resize(self, event):
409 wx.CallAfter(self.on_resize_real) 329 wx.CallAfter(self.on_resize_real)
410 event.Skip() 330 event.Skip()
411 331
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)
443 if self.excluder: 413 if self.excluder:
444 self.excluder.close_window() 414 self.excluder.close_window()
445 wx.CallAfter(self.gwindow.Destroy) 415 wx.CallAfter(self.gwindow.Destroy)
446 wx.CallAfter(self.Destroy) 416 wx.CallAfter(self.Destroy)
447 417
448 def _get_bgcolor(self): 418 @property
449 if self.settings.bgcolor != "auto": 419 def bgcolor(self):
450 return self.settings.bgcolor 420 return (wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWFRAME)
451 else: 421 if self.settings.bgcolor == 'auto'
452 return wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWFRAME) 422 else self.settings.bgcolor)
453 bgcolor = property(_get_bgcolor)
454 423
455 # -------------------------------------------------------------- 424 # --------------------------------------------------------------
456 # Main interface actions 425 # Main interface actions
457 # -------------------------------------------------------------- 426 # --------------------------------------------------------------
458 427
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"))
868 mItem = m.AppendCheckItem(-1, _("Debug communications"), 872 mItem = m.AppendCheckItem(-1, _("Debug communications"),
869 _("Print all G-code sent to and received from the printer.")) 873 _("Print all G-code sent to and received from the printer."))
870 m.Check(mItem.GetId(), self.p.loud) 874 m.Check(mItem.GetId(), self.p.loud)
871 self.Bind(wx.EVT_MENU, self.set_verbose_communications, mItem) 875 self.Bind(wx.EVT_MENU, self.set_verbose_communications, mItem)
872 876
877 mItem = m.AppendCheckItem(-1, _("Don't autoscroll"),
878 _("Disables automatic scrolling of the console when new text is added"))
879 m.Check(mItem.GetId(), self.autoscrolldisable)
880 self.Bind(wx.EVT_MENU, self.set_autoscrolldisable, mItem)
881
873 self.menustrip.Append(m, _("&Settings")) 882 self.menustrip.Append(m, _("&Settings"))
874 self.update_macros_menu() 883 self.update_macros_menu()
875 self.SetMenuBar(self.menustrip) 884 self.SetMenuBar(self.menustrip)
876 885
877 m = wx.Menu() 886 m = wx.Menu()
893 from .excluder import Excluder 902 from .excluder import Excluder
894 self.excluder = Excluder() 903 self.excluder = Excluder()
895 self.excluder.pop_window(self.fgcode, bgcolor = self.bgcolor, 904 self.excluder.pop_window(self.fgcode, bgcolor = self.bgcolor,
896 build_dimensions = self.build_dimensions_list) 905 build_dimensions = self.build_dimensions_list)
897 906
907 def show_spool_manager(self, event):
908 """Show Spool Manager Window"""
909 spoolmanager_gui.SpoolManagerMainWindow(self, self.spool_manager).Show()
910
898 def about(self, event): 911 def about(self, event):
899 """Show about dialog""" 912 """Show about dialog"""
900 913
901 info = wx.AboutDialogInfo() 914 info = wx.adv.AboutDialogInfo()
902 915
903 info.SetIcon(wx.Icon(iconfile("pronterface.png"), wx.BITMAP_TYPE_PNG)) 916 info.SetIcon(wx.Icon(iconfile("pronterface.png"), wx.BITMAP_TYPE_PNG))
904 info.SetName('Printrun') 917 info.SetName('Printrun')
905 info.SetVersion(printcore.__version__) 918 info.SetVersion(printcore.__version__)
906 919
910 description += "\n\n" + \ 923 description += "\n\n" + \
911 _("%.02fmm of filament have been extruded during prints") \ 924 _("%.02fmm of filament have been extruded during prints") \
912 % self.settings.total_filament_used 925 % self.settings.total_filament_used
913 926
914 info.SetDescription(description) 927 info.SetDescription(description)
915 info.SetCopyright('(C) 2011 - 2015') 928 info.SetCopyright('(C) 2011 - 2020')
916 info.SetWebSite('https://github.com/kliment/Printrun') 929 info.SetWebSite('https://github.com/kliment/Printrun')
917 930
918 licence = """\ 931 licence = """\
919 Printrun is free software: you can redistribute it and/or modify it under the 932 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 933 terms of the GNU General Public License as published by the Free Software
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
1048 setattr(widget, param, value) 1071 setattr(widget, param, value)
1049 widget.dirty = 1 1072 widget.dirty = 1
1050 wx.CallAfter(widget.Refresh) 1073 wx.CallAfter(widget.Refresh)
1051 1074
1052 def update_gcview_colors(self, param, value): 1075 def update_gcview_colors(self, param, value):
1076 if not self.window_ready:
1077 return
1053 color = hexcolor_to_float(value, 4) 1078 color = hexcolor_to_float(value, 4)
1054 # This is sort of a hack: we copy the color values into the preexisting 1079 # 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 1080 # color tuple so that we don't need to update the tuple used by gcview
1056 target_color = getattr(self, param) 1081 target_color = getattr(self, param)
1057 for i, v in enumerate(color): 1082 for i, v in enumerate(color):
1063 self.update_bed_viz() 1088 self.update_bed_viz()
1064 1089
1065 def update_bed_viz(self, *args): 1090 def update_bed_viz(self, *args):
1066 """Update bed visualization when size/type changed""" 1091 """Update bed visualization when size/type changed"""
1067 if hasattr(self, "gviz") and hasattr(self.gviz, "recreate_platform"): 1092 if hasattr(self, "gviz") and hasattr(self.gviz, "recreate_platform"):
1068 self.gviz.recreate_platform(self.build_dimensions_list, self.settings.circular_bed) 1093 self.gviz.recreate_platform(self.build_dimensions_list, self.settings.circular_bed,
1094 grid = (self.settings.preview_grid_step1, self.settings.preview_grid_step2))
1069 if hasattr(self, "gwindow") and hasattr(self.gwindow, "recreate_platform"): 1095 if hasattr(self, "gwindow") and hasattr(self.gwindow, "recreate_platform"):
1070 self.gwindow.recreate_platform(self.build_dimensions_list, self.settings.circular_bed) 1096 self.gwindow.recreate_platform(self.build_dimensions_list, self.settings.circular_bed,
1097 grid = (self.settings.preview_grid_step1, self.settings.preview_grid_step2))
1071 1098
1072 def update_gcview_params(self, *args): 1099 def update_gcview_params(self, *args):
1073 need_reload = False 1100 need_reload = False
1074 if hasattr(self, "gviz") and hasattr(self.gviz, "set_gcview_params"): 1101 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) 1102 need_reload |= self.gviz.set_gcview_params(self.settings.gcview_path_width, self.settings.gcview_path_height)
1104 status_string += _(" Line# %d of %d lines |") % (self.p.queueindex, len(self.p.mainqueue)) 1131 status_string += _(" Line# %d of %d lines |") % (self.p.queueindex, len(self.p.mainqueue))
1105 if progress > 0: 1132 if progress > 0:
1106 status_string += _(" Est: %s of %s remaining | ") % (format_duration(secondsremain), 1133 status_string += _(" Est: %s of %s remaining | ") % (format_duration(secondsremain),
1107 format_duration(secondsestimate)) 1134 format_duration(secondsestimate))
1108 status_string += _(" Z: %.3f mm") % self.curlayer 1135 status_string += _(" Z: %.3f mm") % self.curlayer
1136 if self.settings.display_progress_on_printer and time.time() - self.printer_progress_time >= self.settings.printer_progress_update_interval:
1137 self.printer_progress_time = time.time()
1138 printer_progress_string = "M117 " + str(round(100 * float(self.p.queueindex) / len(self.p.mainqueue), 2)) + "% Est " + format_duration(secondsremain)
1139 #":" seems to be some kind of seperator for G-CODE"
1140 self.p.send_now(printer_progress_string.replace(":", "."))
1141 if len(printer_progress_string) > 25:
1142 logging.info("Warning: The print progress message might be too long to be displayed properly")
1143 #13 chars for up to 99h est.
1109 elif self.loading_gcode: 1144 elif self.loading_gcode:
1110 status_string = self.loading_gcode_message 1145 status_string = self.loading_gcode_message
1111 wx.CallAfter(self.statusbar.SetStatusText, status_string) 1146 wx.CallAfter(self.statusbar.SetStatusText, status_string)
1112 wx.CallAfter(self.gviz.Refresh) 1147 wx.CallAfter(self.gviz.Refresh)
1113 # Call pronsole's statuschecker inner loop function to handle 1148 # Call pronsole's statuschecker inner loop function to handle
1116 try: 1151 try:
1117 while not self.sentglines.empty(): 1152 while not self.sentglines.empty():
1118 gc = self.sentglines.get_nowait() 1153 gc = self.sentglines.get_nowait()
1119 wx.CallAfter(self.gviz.addgcodehighlight, gc) 1154 wx.CallAfter(self.gviz.addgcodehighlight, gc)
1120 self.sentglines.task_done() 1155 self.sentglines.task_done()
1121 except Queue.Empty: 1156 except queue.Empty:
1122 pass 1157 pass
1123 1158
1124 def statuschecker(self): 1159 def statuschecker(self):
1125 pronsole.pronsole.statuschecker(self) 1160 pronsole.pronsole.statuschecker(self)
1126 wx.CallAfter(self.statusbar.SetStatusText, _("Not connected to printer.")) 1161 wx.CallAfter(self.statusbar.SetStatusText, _("Not connected to printer."))
1142 panel.Enable() 1177 panel.Enable()
1143 1178
1144 # -------------------------------------------------------------- 1179 # --------------------------------------------------------------
1145 # Printer connection handling 1180 # Printer connection handling
1146 # -------------------------------------------------------------- 1181 # --------------------------------------------------------------
1182
1183 def connectbtn_cb(self, event):
1184 # Implement toggle behavior with a single Bind
1185 # and switched variable, so we have reference to
1186 # the actual callback to use in on_key
1187 self.connectbtn_cb_var()
1147 1188
1148 def connect(self, event = None): 1189 def connect(self, event = None):
1149 self.log(_("Connecting...")) 1190 self.log(_("Connecting..."))
1150 port = None 1191 port = None
1151 if self.serialport.GetValue(): 1192 if self.serialport.GetValue():
1161 self.logError(_("Could not parse baud rate: ") 1202 self.logError(_("Could not parse baud rate: ")
1162 + "\n" + traceback.format_exc()) 1203 + "\n" + traceback.format_exc())
1163 if self.paused: 1204 if self.paused:
1164 self.p.paused = 0 1205 self.p.paused = 0
1165 self.p.printing = 0 1206 self.p.printing = 0
1166 wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) 1207 wx.CallAfter(self.pausebtn.SetLabel, _("&Pause"))
1167 wx.CallAfter(self.printbtn.SetLabel, _("Print")) 1208 wx.CallAfter(self.printbtn.SetLabel, _("&Print"))
1168 wx.CallAfter(self.toolbarsizer.Layout) 1209 wx.CallAfter(self.toolbarsizer.Layout)
1169 self.paused = 0 1210 self.paused = 0
1170 if self.sdprinting: 1211 if self.sdprinting:
1171 self.p.send_now("M26 S0") 1212 self.p.send_now("M26 S0")
1172 if not self.connect_to_printer(port, baud, self.settings.dtr): 1213 if not self.connect_to_printer(port, baud, self.settings.dtr):
1191 self.statuscheck = False 1232 self.statuscheck = False
1192 if self.status_thread: 1233 if self.status_thread:
1193 self.status_thread.join() 1234 self.status_thread.join()
1194 self.status_thread = None 1235 self.status_thread = None
1195 1236
1196 wx.CallAfter(self.connectbtn.SetLabel, _("Connect")) 1237 def toggle():
1197 wx.CallAfter(self.connectbtn.SetToolTip, wx.ToolTip(_("Connect to the printer"))) 1238 self.connectbtn.SetLabel(_("&Connect"))
1198 wx.CallAfter(self.connectbtn.Bind, wx.EVT_BUTTON, self.connect) 1239 self.connectbtn.SetToolTip(wx.ToolTip(_("Connect to the printer")))
1199 1240 self.connectbtn_cb_var = self.connect
1200 wx.CallAfter(self.gui_set_disconnected) 1241 self.gui_set_disconnected()
1242 wx.CallAfter(toggle)
1201 1243
1202 if self.paused: 1244 if self.paused:
1203 self.p.paused = 0 1245 self.p.paused = 0
1204 self.p.printing = 0 1246 self.p.printing = 0
1205 wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) 1247 wx.CallAfter(self.pausebtn.SetLabel, _("&Pause"))
1206 wx.CallAfter(self.printbtn.SetLabel, _("Print")) 1248 wx.CallAfter(self.printbtn.SetLabel, _("&Print"))
1207 self.paused = 0 1249 self.paused = 0
1208 if self.sdprinting: 1250 if self.sdprinting:
1209 self.p.send_now("M26 S0") 1251 self.p.send_now("M26 S0")
1210 1252
1211 # Relayout the toolbar to handle new buttons size 1253 # Relayout the toolbar to handle new buttons size
1217 if dlg.ShowModal() == wx.ID_YES: 1259 if dlg.ShowModal() == wx.ID_YES:
1218 self.p.reset() 1260 self.p.reset()
1219 self.sethotendgui(0) 1261 self.sethotendgui(0)
1220 self.setbedgui(0) 1262 self.setbedgui(0)
1221 self.p.printing = 0 1263 self.p.printing = 0
1222 wx.CallAfter(self.printbtn.SetLabel, _("Print")) 1264 wx.CallAfter(self.printbtn.SetLabel, _("&Print"))
1223 if self.paused: 1265 if self.paused:
1224 self.p.paused = 0 1266 self.p.paused = 0
1225 wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) 1267 wx.CallAfter(self.pausebtn.SetLabel, _("&Pause"))
1226 self.paused = 0 1268 self.paused = 0
1227 wx.CallAfter(self.toolbarsizer.Layout) 1269 wx.CallAfter(self.toolbarsizer.Layout)
1228 dlg.Destroy() 1270 dlg.Destroy()
1229 1271
1230 # -------------------------------------------------------------- 1272 # --------------------------------------------------------------
1231 # Print/upload handling 1273 # Print/upload handling
1232 # -------------------------------------------------------------- 1274 # --------------------------------------------------------------
1233 1275
1234 def on_startprint(self): 1276 def on_startprint(self):
1235 wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) 1277 wx.CallAfter(self.pausebtn.SetLabel, _("&Pause"))
1236 wx.CallAfter(self.pausebtn.Enable) 1278 wx.CallAfter(self.pausebtn.Enable)
1237 wx.CallAfter(self.printbtn.SetLabel, _("Restart")) 1279 wx.CallAfter(self.printbtn.SetLabel, _("Restart"))
1238 wx.CallAfter(self.toolbarsizer.Layout) 1280 wx.CallAfter(self.toolbarsizer.Layout)
1239 1281
1240 wx.CallAfter(self.lc_printbtn.Disable) 1282 def printfile(self, event=None):
1241 wx.CallAfter(self.lc_printbtn.SetLabel, _("Cut in progress"))
1242
1243 def printfile(self, event):
1244 self.extra_print_time = 0 1283 self.extra_print_time = 0
1245 if self.paused: 1284 if self.paused:
1246 self.p.paused = 0 1285 self.p.paused = 0
1247 self.paused = 0 1286 self.paused = 0
1248 if self.sdprinting: 1287 if self.sdprinting:
1258 wx.CallAfter(self.statusbar.SetStatusText, _("Not connected to printer.")) 1297 wx.CallAfter(self.statusbar.SetStatusText, _("Not connected to printer."))
1259 return 1298 return
1260 self.sdprinting = False 1299 self.sdprinting = False
1261 self.on_startprint() 1300 self.on_startprint()
1262 self.p.startprint(self.fgcode) 1301 self.p.startprint(self.fgcode)
1263 self.pass_current = 1
1264 1302
1265 def sdprintfile(self, event): 1303 def sdprintfile(self, event):
1266 self.extra_print_time = 0 1304 self.extra_print_time = 0
1267 self.on_startprint() 1305 self.on_startprint()
1268 threading.Thread(target = self.getfiles).start() 1306 threading.Thread(target = self.getfiles).start()
1296 self.uploading = False 1334 self.uploading = False
1297 1335
1298 def pause(self, event = None): 1336 def pause(self, event = None):
1299 if not self.paused: 1337 if not self.paused:
1300 self.log(_("Print paused at: %s") % format_time(time.time())) 1338 self.log(_("Print paused at: %s") % format_time(time.time()))
1339 if self.settings.display_progress_on_printer:
1340 printer_progress_string = "M117 PausedInPronterface"
1341 self.p.send_now(printer_progress_string)
1301 if self.sdprinting: 1342 if self.sdprinting:
1302 self.p.send_now("M25") 1343 self.p.send_now("M25")
1303 else: 1344 else:
1304 if not self.p.printing: 1345 if not self.p.printing:
1305 return 1346 return
1310 self.extra_print_time += int(time.time() - self.starttime) 1351 self.extra_print_time += int(time.time() - self.starttime)
1311 wx.CallAfter(self.pausebtn.SetLabel, _("Resume")) 1352 wx.CallAfter(self.pausebtn.SetLabel, _("Resume"))
1312 wx.CallAfter(self.toolbarsizer.Layout) 1353 wx.CallAfter(self.toolbarsizer.Layout)
1313 else: 1354 else:
1314 self.log(_("Resuming.")) 1355 self.log(_("Resuming."))
1356 if self.settings.display_progress_on_printer:
1357 printer_progress_string = "M117 Resuming"
1358 self.p.send_now(printer_progress_string)
1315 self.paused = False 1359 self.paused = False
1316 if self.sdprinting: 1360 if self.sdprinting:
1317 self.p.send_now("M24") 1361 self.p.send_now("M24")
1318 else: 1362 else:
1319 self.p.resume() 1363 self.p.resume()
1320 wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) 1364 wx.CallAfter(self.pausebtn.SetLabel, _("&Pause"))
1321 wx.CallAfter(self.toolbarsizer.Layout) 1365 wx.CallAfter(self.toolbarsizer.Layout)
1322 1366
1323 def recover(self, event): 1367 def recover(self, event):
1324 self.extra_print_time = 0 1368 self.extra_print_time = 0
1325 if not self.p.online: 1369 if not self.p.online:
1363 return filename 1407 return filename
1364 1408
1365 def slice_func(self): 1409 def slice_func(self):
1366 try: 1410 try:
1367 output_filename = self.model_to_gcode_filename(self.filename) 1411 output_filename = self.model_to_gcode_filename(self.filename)
1368 pararray = prepare_command(self.settings.slicecommand, 1412 pararray = prepare_command(self.settings.slicecommandpath+self.settings.slicecommand,
1369 {"$s": self.filename, "$o": output_filename}) 1413 {"$s": self.filename, "$o": output_filename})
1370 if self.settings.slic3rintegration: 1414 if self.settings.slic3rintegration:
1371 for cat, config in self.slic3r_configs.items(): 1415 for cat, config in self.slic3r_configs.items():
1372 if config: 1416 if config:
1373 fpath = os.path.join(self.slic3r_configpath, cat, config) 1417 fpath = os.path.join(self.slic3r_configpath, cat, config)
1374 pararray += ["--load", fpath] 1418 pararray += ["--load", fpath]
1375 self.log(_("Running ") + " ".join(pararray)) 1419 self.log(_("Running ") + " ".join(pararray))
1376 self.slicep = subprocess.Popen(pararray, stderr = subprocess.STDOUT, stdout = subprocess.PIPE) 1420 self.slicep = subprocess.Popen(pararray, stdin=subprocess.DEVNULL, stderr = subprocess.STDOUT, stdout = subprocess.PIPE, universal_newlines = True)
1377 while True: 1421 while True:
1378 o = self.slicep.stdout.read(1) 1422 o = self.slicep.stdout.read(1)
1379 if o == '' and self.slicep.poll() is not None: break 1423 if o == '' and self.slicep.poll() is not None: break
1380 sys.stdout.write(o) 1424 sys.stdout.write(o)
1381 self.slicep.wait() 1425 self.slicep.wait()
1397 self.load_gcode_async(self.model_to_gcode_filename(self.filename)) 1441 self.load_gcode_async(self.model_to_gcode_filename(self.filename))
1398 except: 1442 except:
1399 self.filename = fn 1443 self.filename = fn
1400 self.slicing = False 1444 self.slicing = False
1401 self.slicep = None 1445 self.slicep = None
1446 self.loadbtn.SetLabel, _("Load file")
1402 1447
1403 def slice(self, filename): 1448 def slice(self, filename):
1404 wx.CallAfter(self.loadbtn.SetLabel, _("Cancel")) 1449 wx.CallAfter(self.loadbtn.SetLabel, _("Cancel"))
1405 wx.CallAfter(self.toolbarsizer.Layout) 1450 wx.CallAfter(self.toolbarsizer.Layout)
1406 self.log(_("Slicing ") + filename) 1451 self.log(_("Slicing ") + filename)
1439 except: 1484 except:
1440 pass 1485 pass
1441 dlg = None 1486 dlg = None
1442 if filename is None: 1487 if filename is None:
1443 dlg = wx.FileDialog(self, _("Open file to print"), basedir, style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) 1488 dlg = wx.FileDialog(self, _("Open file to print"), basedir, style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
1444 # add image files to GCODE file list 1489 dlg.SetWildcard(_("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 (*.*)|*.*"))
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 (*.*)|*.*"))
1446 try: 1490 try:
1447 dlg.SetFilterIndex(self.settings.last_file_filter) 1491 dlg.SetFilterIndex(self.settings.last_file_filter)
1448 except: 1492 except:
1449 pass 1493 pass
1450 if filename or dlg.ShowModal() == wx.ID_OK: 1494 if filename or dlg.ShowModal() == wx.ID_OK:
1475 recent_files = recent_files[:5] 1519 recent_files = recent_files[:5]
1476 self.set("recentfiles", json.dumps(recent_files)) 1520 self.set("recentfiles", json.dumps(recent_files))
1477 except: 1521 except:
1478 self.logError(_("Could not update recent files list:") + 1522 self.logError(_("Could not update recent files list:") +
1479 "\n" + traceback.format_exc()) 1523 "\n" + traceback.format_exc())
1480
1481 # reload the library local so we dont have to restart the whole app when making code changes
1482 reload(laser)
1483
1484 if name.lower().endswith(".stl") or name.lower().endswith(".obj"): 1524 if name.lower().endswith(".stl") or name.lower().endswith(".obj"):
1485 self.slice(name) 1525 self.slice(name)
1486 elif name.lower().endswith(".png") or name.lower().endswith(".jpg") or name.lower().endswith(".gif"):
1487 # Generate GCODE from IMAGE
1488 lc = laser.Lasercutter(pronterwindow = self)
1489 lc.image2gcode(name)
1490 wx.CallAfter(self.endcb_lasercut)
1491 elif name.lower().endswith(".svg"):
1492 # Generate GCODE from SVG
1493 lc = laser.Lasercutter(pronterwindow = self)
1494 lc.svg2gcode(name)
1495 wx.CallAfter(self.endcb_lasercut)
1496 elif name.lower().endswith(".hpgl") or name.lower().endswith(".plt"):
1497 # Generate GCODE from HPGL
1498 lc = laser.Lasercutter(pronterwindow = self)
1499 lc.hpgl2gcode(name)
1500 wx.CallAfter(self.endcb_lasercut)
1501 else: 1526 else:
1502 self.load_gcode_async(name) 1527 self.load_gcode_async(name)
1503 else: 1528 else:
1504 dlg.Destroy() 1529 dlg.Destroy()
1505 1530
1514 self.load_gcode(self.filename, 1539 self.load_gcode(self.filename,
1515 layer_callback = self.layer_ready_cb, 1540 layer_callback = self.layer_ready_cb,
1516 gcode = gcode) 1541 gcode = gcode)
1517 except PronterfaceQuitException: 1542 except PronterfaceQuitException:
1518 return 1543 return
1544 except Exception as e:
1545 self.log(str(e))
1546 wx.CallAfter(self.post_gcode_load,False,True)
1547 return
1519 wx.CallAfter(self.post_gcode_load) 1548 wx.CallAfter(self.post_gcode_load)
1520 1549
1521 def layer_ready_cb(self, gcode, layer): 1550 def layer_ready_cb(self, gcode, layer):
1522 global pronterface_quitting 1551 global pronterface_quitting
1523 if pronterface_quitting: 1552 if pronterface_quitting:
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()
1839 if l.startswith("!!"): 1910 if l.startswith("!!"):
1840 if not self.paused: 1911 if not self.paused:
1841 wx.CallAfter(self.pause) 1912 wx.CallAfter(self.pause)
1842 msg = l.split(" ", 1) 1913 msg = l.split(" ", 1)
1843 if len(msg) > 1 and not self.p.loud: 1914 if len(msg) > 1 and not self.p.loud:
1844 wx.CallAfter(self.addtexttolog, msg[1] + "\n") 1915 self.log(msg[1] + "\n")
1845 return True 1916 return True
1846 elif l.startswith("//"): 1917 elif l.startswith("//"):
1847 command = l.split(" ", 1) 1918 command = l.split(" ", 1)
1848 if len(command) > 1: 1919 if len(command) > 1:
1849 command = command[1] 1920 command = command[1]
1872 if report_type & REPORT_POS: 1943 if report_type & REPORT_POS:
1873 self.update_pos() 1944 self.update_pos()
1874 elif report_type & REPORT_TEMP: 1945 elif report_type & REPORT_TEMP:
1875 wx.CallAfter(self.tempdisp.SetLabel, self.tempreadings.strip().replace("ok ", "")) 1946 wx.CallAfter(self.tempdisp.SetLabel, self.tempreadings.strip().replace("ok ", ""))
1876 self.update_tempdisplay() 1947 self.update_tempdisplay()
1877 if not self.p.loud and (l not in ["ok", "wait"] and (not isreport or report_type & REPORT_MANUAL)): 1948 if not self.lineignorepattern.match(l) and 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") 1949 self.log(l)
1879 for listener in self.recvlisteners: 1950 for listener in self.recvlisteners:
1880 listener(l) 1951 listener(l)
1881 1952
1882 def listfiles(self, line, ignored = False): 1953 def listfiles(self, line, ignored = False):
1883 if "Begin file list" in line: 1954 if "Begin file list" in line:
1885 elif "End file list" in line: 1956 elif "End file list" in line:
1886 self.sdlisting = False 1957 self.sdlisting = False
1887 self.recvlisteners.remove(self.listfiles) 1958 self.recvlisteners.remove(self.listfiles)
1888 wx.CallAfter(self.filesloaded) 1959 wx.CallAfter(self.filesloaded)
1889 elif self.sdlisting: 1960 elif self.sdlisting:
1890 self.sdfiles.append(line.strip().lower()) 1961 self.sdfiles.append(re.sub(" \d+$","",line.strip().lower()))
1891 1962
1892 def waitforsdresponse(self, l): 1963 def waitforsdresponse(self, l):
1893 if "file.open failed" in l: 1964 if "file.open failed" in l:
1894 wx.CallAfter(self.statusbar.SetStatusText, _("Opening file failed.")) 1965 wx.CallAfter(self.statusbar.SetStatusText, _("Opening file failed."))
1895 self.recvlisteners.remove(self.waitforsdresponse) 1966 self.recvlisteners.remove(self.waitforsdresponse)
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()
1995 if new_n is None: new_n = n 2066 if new_n is None: new_n = n
1996 if bdef is None or bdef == "": 2067 if bdef is None or bdef == "":
1997 self.save_in_rc(("button %d" % n), '') 2068 self.save_in_rc(("button %d" % n), '')
1998 elif bdef.background: 2069 elif bdef.background:
1999 colour = bdef.background 2070 colour = bdef.background
2000 if type(colour) not in (str, unicode): 2071 if not isinstance(colour, str):
2001 if type(colour) == tuple and tuple(map(type, colour)) == (int, int, int): 2072 if isinstance(colour, tuple) and tuple(map(type, colour)) == (int, int, int):
2002 colour = map(lambda x: x % 256, colour) 2073 colour = (x % 256 for x in colour)
2003 colour = wx.Colour(*colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX) 2074 colour = wx.Colour(*colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX)
2004 else: 2075 else:
2005 colour = wx.Colour(colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX) 2076 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)) 2077 self.save_in_rc(("button %d" % n), 'button %d "%s" /c "%s" %s' % (new_n, bdef.label, colour, bdef.command))
2007 else: 2078 else:
2013 n = button.custombutton 2084 n = button.custombutton
2014 bedit.name.SetValue(button.properties.label) 2085 bedit.name.SetValue(button.properties.label)
2015 bedit.command.SetValue(button.properties.command) 2086 bedit.command.SetValue(button.properties.command)
2016 if button.properties.background: 2087 if button.properties.background:
2017 colour = button.properties.background 2088 colour = button.properties.background
2018 if type(colour) not in (str, unicode): 2089 if not isinstance(colour, str):
2019 if type(colour) == tuple and tuple(map(type, colour)) == (int, int, int): 2090 if isinstance(colour, tuple) and tuple(map(type, colour)) == (int, int, int):
2020 colour = map(lambda x: x % 256, colour) 2091 colour = (x % 256 for x in colour)
2021 colour = wx.Colour(*colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX) 2092 colour = wx.Colour(*colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX)
2022 else: 2093 else:
2023 colour = wx.Colour(colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX) 2094 colour = wx.Colour(colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX)
2024 bedit.color.SetValue(colour) 2095 bedit.color.SetValue(colour)
2025 else: 2096 else:
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())
2145 else: 2216 else:
2146 src.SetBackgroundColour(drg.bgc) 2217 src.SetBackgroundColour(drg.bgc)
2147 src.SetForegroundColour(drg.fgc) 2218 src.SetForegroundColour(drg.fgc)
2148 src.SetLabel(drg.label) 2219 src.SetLabel(drg.label)
2149 self.last_drag_dest = dst 2220 self.last_drag_dest = dst
2150 elif hasattr(self, "dragging") and not e.ButtonIsDown(wx.MOUSE_BTN_LEFT): 2221 elif hasattr(self, "dragging") and not e.LeftIsDown():
2151 # dragging finished 2222 # dragging finished
2152 obj = e.GetEventObject() 2223 obj = e.GetEventObject()
2153 scrpos = obj.ClientToScreen(e.GetPosition()) 2224 scrpos = obj.ClientToScreen(e.GetPosition())
2154 dst = None 2225 dst = None
2155 src = self.dragging.sourcebutton 2226 src = self.dragging.sourcebutton
2156 drg = self.dragging 2227 drg = self.dragging
2157 for b in self.custombuttons_widgets: 2228 for b in self.custombuttons_widgets:
2158 if b.GetScreenRect().Contains(scrpos): 2229 if b.GetScreenRect().Contains(scrpos):
2159 dst = b 2230 dst = b
2160 break 2231 break
2161 if dst is not None: 2232 if dst is not None and hasattr(dst,"custombutton"):
2162 src_i = src.custombutton 2233 src_i = src.custombutton
2163 dst_i = dst.custombutton 2234 dst_i = dst.custombutton
2164 self.custombuttons[src_i], self.custombuttons[dst_i] = self.custombuttons[dst_i], self.custombuttons[src_i] 2235 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]) 2236 self.cbutton_save(src_i, self.custombuttons[src_i])
2166 self.cbutton_save(dst_i, self.custombuttons[dst_i]) 2237 self.cbutton_save(dst_i, self.custombuttons[dst_i])
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
2262 return # too early, menu not yet built 2349 return # too early, menu not yet built
2263 try: 2350 try:
2264 while True: 2351 while True:
2265 item = self.macros_menu.FindItemByPosition(1) 2352 item = self.macros_menu.FindItemByPosition(1)
2266 if item is None: break 2353 if item is None: break
2267 self.macros_menu.DeleteItem(item) 2354 self.macros_menu.DestroyItem(item)
2268 except: 2355 except:
2269 pass 2356 pass
2270 for macro in self.macros.keys(): 2357 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)) 2358 self.Bind(wx.EVT_MENU, lambda x, m = macro: self.start_macro(m, self.macros[m]), self.macros_menu.Append(-1, macro))
2272 2359
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", "")
2341 mainwindow = None 2449 mainwindow = None
2342 2450
2343 def __init__(self, *args, **kwargs): 2451 def __init__(self, *args, **kwargs):
2344 super(PronterApp, self).__init__(*args, **kwargs) 2452 super(PronterApp, self).__init__(*args, **kwargs)
2345 self.SetAppName("Pronterface") 2453 self.SetAppName("Pronterface")
2454 self.locale = wx.Locale(wx.Locale.GetSystemLanguage())
2346 self.mainwindow = PronterWindow(self) 2455 self.mainwindow = PronterWindow(self)
2347 self.mainwindow.Show() 2456 self.mainwindow.Show()

mercurial