printrun-src/printrun/settings.py

changeset 46
cce0af6351f0
parent 15
0bbb006204fc
equal deleted inserted replaced
45:c82943fb205f 46:cce0af6351f0
13 # 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
14 # along with Printrun. If not, see <http://www.gnu.org/licenses/>. 14 # along with Printrun. If not, see <http://www.gnu.org/licenses/>.
15 15
16 import logging 16 import logging
17 import traceback 17 import traceback
18 import os
19 import sys
18 20
19 from functools import wraps 21 from functools import wraps
20 22
21 from .utils import parse_build_dimensions 23 from .utils import parse_build_dimensions
22 24
28 sep, deftxt = "", "" 30 sep, deftxt = "", ""
29 if len(helptxt): 31 if len(helptxt):
30 sep = "\n" 32 sep = "\n"
31 if helptxt.find("\n") >= 0: 33 if helptxt.find("\n") >= 0:
32 sep = "\n\n" 34 sep = "\n\n"
33 if self.default is not "": 35 if self.default != "":
34 deftxt = _("Default: ") 36 deftxt = _("Default: ")
35 resethelp = _("(Control-doubleclick to reset to default value)") 37 resethelp = _("(Control-doubleclick to reset to default value)")
36 if len(repr(self.default)) > 10: 38 if len(repr(self.default)) > 10:
37 deftxt += "\n " + repr(self.default).strip("'") + "\n" + resethelp 39 deftxt += "\n " + repr(self.default).strip("'") + "\n" + resethelp
38 else: 40 else:
39 deftxt += repr(self.default) + " " + resethelp 41 deftxt += repr(self.default) + " " + resethelp
40 helptxt += sep + deftxt 42 helptxt += sep + deftxt
41 if len(helptxt): 43 if len(helptxt):
42 widget.SetToolTipString(helptxt) 44 widget.SetToolTip(helptxt)
43 return widget 45 return widget
44 return decorator 46 return decorator
45 47
46 class Setting(object): 48 class Setting:
47 49
48 DEFAULT_GROUP = "Printer" 50 DEFAULT_GROUP = "Printer"
49 51
50 hidden = False 52 hidden = False
51 53
62 64
63 def _set_value(self, value): 65 def _set_value(self, value):
64 raise NotImplementedError 66 raise NotImplementedError
65 value = property(_get_value, _set_value) 67 value = property(_get_value, _set_value)
66 68
67 def set_default(self, e):
68 import wx
69 if e.CmdDown() and e.ButtonDClick() and self.default is not "":
70 confirmation = wx.MessageDialog(None, _("Are you sure you want to reset the setting to the default value: {0!r} ?").format(self.default), _("Confirm set default"), wx.ICON_EXCLAMATION | wx.YES_NO | wx.NO_DEFAULT)
71 if confirmation.ShowModal() == wx.ID_YES:
72 self._set_value(self.default)
73 else:
74 e.Skip()
75
76 @setting_add_tooltip 69 @setting_add_tooltip
77 def get_label(self, parent): 70 def get_label(self, parent):
78 import wx 71 import wx
79 widget = wx.StaticText(parent, -1, self.label or self.name) 72 widget = wx.StaticText(parent, -1, self.label or self.name)
80 widget.set_default = self.set_default 73 widget.set_default = self.set_default
87 def get_specific_widget(self, parent): 80 def get_specific_widget(self, parent):
88 raise NotImplementedError 81 raise NotImplementedError
89 82
90 def update(self): 83 def update(self):
91 raise NotImplementedError 84 raise NotImplementedError
85
86 def validate(self, value): pass
92 87
93 def __str__(self): 88 def __str__(self):
94 return self.name 89 return self.name
95 90
96 def __repr__(self): 91 def __repr__(self):
115 value = property(Setting._get_value, _set_value) 110 value = property(Setting._get_value, _set_value)
116 111
117 def update(self): 112 def update(self):
118 self.value = self.widget.GetValue() 113 self.value = self.widget.GetValue()
119 114
115 def set_default(self, e):
116 if e.CmdDown() and e.ButtonDClick() and self.default != "":
117 self.widget.SetValue(self.default)
118 else:
119 e.Skip()
120
120 class StringSetting(wxSetting): 121 class StringSetting(wxSetting):
121 122
122 def get_specific_widget(self, parent): 123 def get_specific_widget(self, parent):
123 import wx 124 import wx
124 self.widget = wx.TextCtrl(parent, -1, str(self.value)) 125 self.widget = wx.TextCtrl(parent, -1, str(self.value))
125 return self.widget 126 return self.widget
127
128 def wxColorToStr(color, withAlpha = True):
129 # including Alpha seems to be non standard in CSS
130 format = '#{0.red:02X}{0.green:02X}{0.blue:02X}' \
131 + ('{0.alpha:02X}' if withAlpha else '')
132 return format.format(color)
133
134 class ColorSetting(wxSetting):
135 def __init__(self, name, default, label = None, help = None, group = None, isRGBA=True):
136 super().__init__(name, default, label, help, group)
137 self.isRGBA = isRGBA
138
139 def validate(self, value):
140 from .utils import check_rgb_color, check_rgba_color
141 validate = check_rgba_color if self.isRGBA else check_rgb_color
142 validate(value)
143
144 def get_specific_widget(self, parent):
145 import wx
146 self.widget = wx.ColourPickerCtrl(parent, colour=wx.Colour(self.value), style=wx.CLRP_USE_TEXTCTRL)
147 self.widget.SetValue = self.widget.SetColour
148 self.widget.LayoutDirection = wx.Layout_RightToLeft
149 return self.widget
150 def update(self):
151 self._value = wxColorToStr(self.widget.Colour, self.isRGBA)
126 152
127 class ComboSetting(wxSetting): 153 class ComboSetting(wxSetting):
128 154
129 def __init__(self, name, default, choices, label = None, help = None, group = None): 155 def __init__(self, name, default, choices, label = None, help = None, group = None):
130 super(ComboSetting, self).__init__(name, default, label, help, group) 156 super(ComboSetting, self).__init__(name, default, label, help, group)
131 self.choices = choices 157 self.choices = choices
132 158
133 def get_specific_widget(self, parent): 159 def get_specific_widget(self, parent):
134 import wx 160 import wx
135 self.widget = wx.ComboBox(parent, -1, str(self.value), choices = self.choices, style = wx.CB_DROPDOWN) 161 readonly = isinstance(self.choices, tuple)
162 if readonly:
163 # wx.Choice drops its list on click, no need to click down arrow
164 # which is far to the right because of wx.EXPAND
165 self.widget = wx.Choice(parent, -1, choices = self.choices)
166 self.widget.GetValue = lambda: self.choices[self.widget.Selection]
167 self.widget.SetValue = lambda v: self.widget.SetSelection(self.choices.index(v))
168 self.widget.SetValue(self.value)
169 else:
170 self.widget = wx.ComboBox(parent, -1, str(self.value), choices = self.choices, style = wx.CB_DROPDOWN)
136 return self.widget 171 return self.widget
137 172
138 class SpinSetting(wxSetting): 173 class SpinSetting(wxSetting):
139 174
140 def __init__(self, name, default, min, max, label = None, help = None, group = None, increment = 0.1): 175 def __init__(self, name, default, min, max, label = None, help = None, group = None, increment = 0.1):
141 super(SpinSetting, self).__init__(name, default, label, help, group) 176 super().__init__(name, default, label, help, group)
142 self.min = min 177 self.min = min
143 self.max = max 178 self.max = max
144 self.increment = increment 179 self.increment = increment
145 180
146 def get_specific_widget(self, parent): 181 def get_specific_widget(self, parent):
147 from wx.lib.agw.floatspin import FloatSpin 182 import wx
148 self.widget = FloatSpin(parent, -1, min_val = self.min, max_val = self.max, digits = 0) 183 self.widget = wx.SpinCtrlDouble(parent, -1, min = self.min, max = self.max)
184 self.widget.SetDigits(0)
149 self.widget.SetValue(self.value) 185 self.widget.SetValue(self.value)
150 orig = self.widget.GetValue 186 orig = self.widget.GetValue
151 self.widget.GetValue = lambda: int(orig()) 187 self.widget.GetValue = lambda: int(orig())
152 return self.widget 188 return self.widget
153 189
190 def MySpin(parent, digits, *args, **kw):
191 # in GTK 3.[01], spinner is not large enough to fit text
192 # Could be a class, but use function to avoid load errors if wx
193 # not installed
194 # If native wx.SpinCtrlDouble has problems in different platforms
195 # try agw
196 # from wx.lib.agw.floatspin import FloatSpin
197 import wx
198 sp = wx.SpinCtrlDouble(parent, *args, **kw)
199 # sp = FloatSpin(parent)
200 sp.SetDigits(digits)
201 # sp.SetValue(kw['initial'])
202 def fitValue(ev):
203 text = '%%.%df'% digits % sp.Max
204 # native wx.SpinCtrlDouble does not return good size
205 # in GTK 3.0
206 tex = sp.GetTextExtent(text)
207 tsz = sp.GetSizeFromTextSize(tex.x)
208
209 if sp.MinSize.x < tsz.x:
210 # print('fitValue', getattr(sp, 'setting', None), sp.Value, sp.Digits, tsz.x)
211 sp.MinSize = tsz
212 # sp.Size = tsz
213 # sp.Bind(wx.EVT_TEXT, fitValue)
214 fitValue(None)
215 return sp
216
154 class FloatSpinSetting(SpinSetting): 217 class FloatSpinSetting(SpinSetting):
155 218
156 def get_specific_widget(self, parent): 219 def get_specific_widget(self, parent):
157 from wx.lib.agw.floatspin import FloatSpin 220 self.widget = MySpin(parent, 2, initial = self.value, min = self.min, max = self.max, inc = self.increment)
158 self.widget = FloatSpin(parent, -1, value = self.value, min_val = self.min, max_val = self.max, increment = self.increment, digits = 2)
159 return self.widget 221 return self.widget
160 222
161 class BooleanSetting(wxSetting): 223 class BooleanSetting(wxSetting):
162 224
163 def _get_value(self): 225 def _get_value(self):
214 def get_widget(self, parent): 276 def get_widget(self, parent):
215 from wx.lib.agw.floatspin import FloatSpin 277 from wx.lib.agw.floatspin import FloatSpin
216 import wx 278 import wx
217 build_dimensions = parse_build_dimensions(self.value) 279 build_dimensions = parse_build_dimensions(self.value)
218 self.widgets = [] 280 self.widgets = []
219 w = lambda val, m, M: self.widgets.append(FloatSpin(parent, -1, value = val, min_val = m, max_val = M, digits = 2)) 281 def w(val, m, M):
220 addlabel = lambda name, pos: self.widget.Add(wx.StaticText(parent, -1, name), pos = pos, flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border = 5) 282 self.widgets.append(MySpin(parent, 2, initial = val, min = m, max = M))
221 addwidget = lambda *pos: self.widget.Add(self.widgets[-1], pos = pos, flag = wx.RIGHT, border = 5) 283 def addlabel(name, pos):
284 self.widget.Add(wx.StaticText(parent, -1, name), pos = pos, flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border = 5)
285 def addwidget(*pos):
286 self.widget.Add(self.widgets[-1], pos = pos, flag = wx.RIGHT | wx.EXPAND, border = 5)
222 self.widget = wx.GridBagSizer() 287 self.widget = wx.GridBagSizer()
223 addlabel(_("Width"), (0, 0)) 288 addlabel(_("Width"), (0, 0))
224 w(build_dimensions[0], 0, 2000) 289 w(build_dimensions[0], 0, 2000)
225 addwidget(0, 1) 290 addwidget(0, 1)
226 addlabel(_("Depth"), (0, 2)) 291 addlabel(_("Depth"), (0, 2))
238 addlabel(_("Z offset"), (1, 4)) 303 addlabel(_("Z offset"), (1, 4))
239 w(build_dimensions[5], -2000, 2000) 304 w(build_dimensions[5], -2000, 2000)
240 addwidget(1, 5) 305 addwidget(1, 5)
241 addlabel(_("X home pos."), (2, 0)) 306 addlabel(_("X home pos."), (2, 0))
242 w(build_dimensions[6], -2000, 2000) 307 w(build_dimensions[6], -2000, 2000)
243 self.widget.Add(self.widgets[-1], pos = (2, 1)) 308 addwidget(2, 1)
244 addlabel(_("Y home pos."), (2, 2)) 309 addlabel(_("Y home pos."), (2, 2))
245 w(build_dimensions[7], -2000, 2000) 310 w(build_dimensions[7], -2000, 2000)
246 self.widget.Add(self.widgets[-1], pos = (2, 3)) 311 addwidget(2, 3)
247 addlabel(_("Z home pos."), (2, 4)) 312 addlabel(_("Z home pos."), (2, 4))
248 w(build_dimensions[8], -2000, 2000) 313 w(build_dimensions[8], -2000, 2000)
249 self.widget.Add(self.widgets[-1], pos = (2, 5)) 314 addwidget(2, 5)
250 return self.widget 315 return self.widget
251 316
252 def update(self): 317 def update(self):
253 values = [float(w.GetValue()) for w in self.widgets] 318 values = [float(w.GetValue()) for w in self.widgets]
254 self.value = "%.02fx%.02fx%.02f%+.02f%+.02f%+.02f%+.02f%+.02f%+.02f" % tuple(values) 319 self.value = "%.02fx%.02fx%.02f%+.02f%+.02f%+.02f%+.02f%+.02f%+.02f" % tuple(values)
255 320
256 class Settings(object): 321 class Settings:
257 def __baudrate_list(self): return ["2400", "9600", "19200", "38400", "57600", "115200", "250000"] 322 def __baudrate_list(self): return ["2400", "9600", "19200", "38400", "57600", "115200", "250000"]
258 323
259 def __init__(self, root): 324 def __init__(self, root):
260 # defaults here. 325 # defaults here.
261 # the initial value determines the type 326 # the initial value determines the type
262 self._add(StringSetting("port", "", _("Serial port"), _("Port used to communicate with printer"))) 327 self._add(StringSetting("port", "", _("Serial port"), _("Port used to communicate with printer")))
263 self._add(ComboSetting("baudrate", 115200, self.__baudrate_list(), _("Baud rate"), _("Communications Speed"))) 328 self._add(ComboSetting("baudrate", 115200, self.__baudrate_list(), _("Baud rate"), _("Communications Speed")))
264 self._add(BooleanSetting("tcp_streaming_mode", False, _("TCP streaming mode"), _("When using a TCP connection to the printer, the streaming mode will not wait for acks from the printer to send new commands. This will break things such as ETA prediction, but can result in smoother prints.")), root.update_tcp_streaming_mode) 329 self._add(BooleanSetting("tcp_streaming_mode", False, _("TCP streaming mode"), _("When using a TCP connection to the printer, the streaming mode will not wait for acks from the printer to send new commands. This will break things such as ETA prediction, but can result in smoother prints.")), root.update_tcp_streaming_mode)
265 self._add(BooleanSetting("rpc_server", True, _("RPC server"), _("Enable RPC server to allow remotely querying print status")), root.update_rpc_server) 330 self._add(BooleanSetting("rpc_server", True, _("RPC server"), _("Enable RPC server to allow remotely querying print status")), root.update_rpc_server)
266 self._add(BooleanSetting("dtr", True, _("DTR"), _("Disabling DTR would prevent Arduino (RAMPS) from resetting upon connection"), "Printer")) 331 self._add(BooleanSetting("dtr", True, _("DTR"), _("Disabling DTR would prevent Arduino (RAMPS) from resetting upon connection"), "Printer"))
267 self._add(SpinSetting("bedtemp_abs", 110, 0, 400, _("Bed temperature for ABS"), _("Heated Build Platform temp for ABS (deg C)"), "Printer")) 332 if sys.platform != "win32":
268 self._add(SpinSetting("bedtemp_pla", 60, 0, 400, _("Bed temperature for PLA"), _("Heated Build Platform temp for PLA (deg C)"), "Printer")) 333 self._add(StringSetting("devicepath", "", _("Device name pattern"), _("Custom device pattern: for example /dev/3DP_* "), "Printer"))
269 self._add(SpinSetting("temperature_abs", 230, 0, 400, _("Extruder temperature for ABS"), _("Extruder temp for ABS (deg C)"), "Printer")) 334 self._add(SpinSetting("bedtemp_abs", 110, 0, 400, _("Bed temperature for ABS"), _("Heated Build Platform temp for ABS (deg C)"), "Printer"), root.set_temp_preset)
270 self._add(SpinSetting("temperature_pla", 185, 0, 400, _("Extruder temperature for PLA"), _("Extruder temp for PLA (deg C)"), "Printer")) 335 self._add(SpinSetting("bedtemp_pla", 60, 0, 400, _("Bed temperature for PLA"), _("Heated Build Platform temp for PLA (deg C)"), "Printer"), root.set_temp_preset)
336 self._add(SpinSetting("temperature_abs", 230, 0, 400, _("Extruder temperature for ABS"), _("Extruder temp for ABS (deg C)"), "Printer"), root.set_temp_preset)
337 self._add(SpinSetting("temperature_pla", 185, 0, 400, _("Extruder temperature for PLA"), _("Extruder temp for PLA (deg C)"), "Printer"), root.set_temp_preset)
271 self._add(SpinSetting("xy_feedrate", 3000, 0, 50000, _("X && Y manual feedrate"), _("Feedrate for Control Panel Moves in X and Y (mm/min)"), "Printer")) 338 self._add(SpinSetting("xy_feedrate", 3000, 0, 50000, _("X && Y manual feedrate"), _("Feedrate for Control Panel Moves in X and Y (mm/min)"), "Printer"))
272 self._add(SpinSetting("z_feedrate", 100, 0, 50000, _("Z manual feedrate"), _("Feedrate for Control Panel Moves in Z (mm/min)"), "Printer")) 339 self._add(SpinSetting("z_feedrate", 100, 0, 50000, _("Z manual feedrate"), _("Feedrate for Control Panel Moves in Z (mm/min)"), "Printer"))
273 self._add(SpinSetting("e_feedrate", 100, 0, 1000, _("E manual feedrate"), _("Feedrate for Control Panel Moves in Extrusions (mm/min)"), "Printer")) 340 self._add(SpinSetting("e_feedrate", 100, 0, 1000, _("E manual feedrate"), _("Feedrate for Control Panel Moves in Extrusions (mm/min)"), "Printer"))
274 self._add(StringSetting("slicecommand", "python skeinforge/skeinforge_application/skeinforge_utilities/skeinforge_craft.py $s", _("Slice command"), _("Slice command"), "External")) 341 defaultslicerpath = ""
275 self._add(StringSetting("sliceoptscommand", "python skeinforge/skeinforge_application/skeinforge.py", _("Slicer options command"), _("Slice settings command"), "External")) 342 if getattr(sys, 'frozen', False):
343 if sys.platform == "darwin":
344 defaultslicerpath = "/Applications/Slic3r.app/Contents/MacOS/"
345 elif sys.platform == "win32":
346 defaultslicerpath = ".\\slic3r\\"
347 self._add(StringSetting("slicecommandpath", defaultslicerpath, _("Path to slicer"), _("Path to slicer"), "External"))
348 slicer = 'slic3r-console' if sys.platform == 'win32' else 'slic3r'
349 self._add(StringSetting("slicecommand", slicer + ' $s --output $o', _("Slice command"), _("Slice command"), "External"))
350 self._add(StringSetting("sliceoptscommand", "slic3r", _("Slicer options command"), _("Slice settings command"), "External"))
276 self._add(StringSetting("start_command", "", _("Start command"), _("Executable to run when the print is started"), "External")) 351 self._add(StringSetting("start_command", "", _("Start command"), _("Executable to run when the print is started"), "External"))
277 self._add(StringSetting("final_command", "", _("Final command"), _("Executable to run when the print is finished"), "External")) 352 self._add(StringSetting("final_command", "", _("Final command"), _("Executable to run when the print is finished"), "External"))
278 self._add(StringSetting("error_command", "", _("Error command"), _("Executable to run when an error occurs"), "External")) 353 self._add(StringSetting("error_command", "", _("Error command"), _("Executable to run when an error occurs"), "External"))
279 self._add(StringSetting("log_path", "", _("Log path"), _("Path to the log file. An empty path will log to the console."), "UI")) 354 self._add(StringSetting("log_path", "", _("Log path"), _("Path to the log file. An empty path will log to the console."), "UI"))
280 355
294 self._add(HiddenSetting("project_postlift_gcode", "")) 369 self._add(HiddenSetting("project_postlift_gcode", ""))
295 self._add(HiddenSetting("pause_between_prints", True)) 370 self._add(HiddenSetting("pause_between_prints", True))
296 self._add(HiddenSetting("default_extrusion", 5.0)) 371 self._add(HiddenSetting("default_extrusion", 5.0))
297 self._add(HiddenSetting("last_extrusion", 5.0)) 372 self._add(HiddenSetting("last_extrusion", 5.0))
298 self._add(HiddenSetting("total_filament_used", 0.0)) 373 self._add(HiddenSetting("total_filament_used", 0.0))
374 self._add(HiddenSetting("spool_list", ""))
299 375
300 _settings = [] 376 _settings = []
301 377
302 def __setattr__(self, name, value): 378 def __setattr__(self, name, value):
303 if name.startswith("_"): 379 if name.startswith("_"):
314 def __getattr__(self, name): 390 def __getattr__(self, name):
315 if name.startswith("_"): 391 if name.startswith("_"):
316 return object.__getattribute__(self, name) 392 return object.__getattribute__(self, name)
317 return getattr(self, "_" + name).value 393 return getattr(self, "_" + name).value
318 394
319 def _add(self, setting, callback = None, validate = None, 395 def _add(self, setting, callback = None,
320 alias = None, autocomplete_list = None): 396 alias = None, autocomplete_list = None):
321 setattr(self, setting.name, setting) 397 setattr(self, setting.name, setting)
322 if callback: 398 if callback:
323 setattr(self, "__" + setting.name + "_cb", callback) 399 setattr(self, "__" + setting.name + "_cb", callback)
324 if validate:
325 setattr(self, "__" + setting.name + "_validate", validate)
326 if alias: 400 if alias:
327 setattr(self, "__" + setting.name + "_alias", alias) 401 setattr(self, "__" + setting.name + "_alias", alias)
328 if autocomplete_list: 402 if autocomplete_list:
329 setattr(self, "__" + setting.name + "_list", autocomplete_list) 403 setattr(self, "__" + setting.name + "_list", autocomplete_list)
330 404
333 value = getattr(self, "__%s_alias" % key)()[value] 407 value = getattr(self, "__%s_alias" % key)()[value]
334 except KeyError: 408 except KeyError:
335 pass 409 pass
336 except AttributeError: 410 except AttributeError:
337 pass 411 pass
412 setting = getattr(self, '_'+key)
413 setting.validate(value)
414 t = type(getattr(self, key))
415 if t == bool and value == "False":
416 value = False
417 setattr(self, key, t(value))
338 try: 418 try:
339 getattr(self, "__%s_validate" % key)(value) 419 cb = getattr(self, "__%s_cb" % key, None)
340 except AttributeError: 420 if cb is not None:
341 pass 421 cb(key, value)
342 t = type(getattr(self, key))
343 if t == bool and value == "False": setattr(self, key, False)
344 else: setattr(self, key, t(value))
345 try:
346 cb = None
347 try:
348 cb = getattr(self, "__%s_cb" % key)
349 except AttributeError:
350 pass
351 if cb is not None: cb(key, value)
352 except: 422 except:
353 logging.warning((_("Failed to run callback after setting \"%s\":") % key) + 423 logging.warning((_("Failed to run callback after setting \"%s\":") % key) +
354 "\n" + traceback.format_exc()) 424 "\n" + traceback.format_exc())
355 return value 425 return value
356 426
358 try: 428 try:
359 return getattr(self, "__%s_list" % key)() 429 return getattr(self, "__%s_list" % key)()
360 except AttributeError: 430 except AttributeError:
361 pass 431 pass
362 try: 432 try:
363 return getattr(self, "__%s_alias" % key)().keys() 433 return list(getattr(self, "__%s_alias" % key)().keys())
364 except AttributeError: 434 except AttributeError:
365 pass 435 pass
366 return [] 436 return []
367 437
368 def _all_settings(self): 438 def _all_settings(self):

mercurial