--- a/printrun-src/printrun/settings.py Tue Jan 19 20:45:09 2021 +0100 +++ b/printrun-src/printrun/settings.py Wed Jan 20 10:15:13 2021 +0100 @@ -15,6 +15,8 @@ import logging import traceback +import os +import sys from functools import wraps @@ -30,7 +32,7 @@ sep = "\n" if helptxt.find("\n") >= 0: sep = "\n\n" - if self.default is not "": + if self.default != "": deftxt = _("Default: ") resethelp = _("(Control-doubleclick to reset to default value)") if len(repr(self.default)) > 10: @@ -39,11 +41,11 @@ deftxt += repr(self.default) + " " + resethelp helptxt += sep + deftxt if len(helptxt): - widget.SetToolTipString(helptxt) + widget.SetToolTip(helptxt) return widget return decorator -class Setting(object): +class Setting: DEFAULT_GROUP = "Printer" @@ -64,15 +66,6 @@ raise NotImplementedError value = property(_get_value, _set_value) - def set_default(self, e): - import wx - if e.CmdDown() and e.ButtonDClick() and self.default is not "": - 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) - if confirmation.ShowModal() == wx.ID_YES: - self._set_value(self.default) - else: - e.Skip() - @setting_add_tooltip def get_label(self, parent): import wx @@ -90,6 +83,8 @@ def update(self): raise NotImplementedError + def validate(self, value): pass + def __str__(self): return self.name @@ -117,6 +112,12 @@ def update(self): self.value = self.widget.GetValue() + def set_default(self, e): + if e.CmdDown() and e.ButtonDClick() and self.default != "": + self.widget.SetValue(self.default) + else: + e.Skip() + class StringSetting(wxSetting): def get_specific_widget(self, parent): @@ -124,6 +125,31 @@ self.widget = wx.TextCtrl(parent, -1, str(self.value)) return self.widget +def wxColorToStr(color, withAlpha = True): + # including Alpha seems to be non standard in CSS + format = '#{0.red:02X}{0.green:02X}{0.blue:02X}' \ + + ('{0.alpha:02X}' if withAlpha else '') + return format.format(color) + +class ColorSetting(wxSetting): + def __init__(self, name, default, label = None, help = None, group = None, isRGBA=True): + super().__init__(name, default, label, help, group) + self.isRGBA = isRGBA + + def validate(self, value): + from .utils import check_rgb_color, check_rgba_color + validate = check_rgba_color if self.isRGBA else check_rgb_color + validate(value) + + def get_specific_widget(self, parent): + import wx + self.widget = wx.ColourPickerCtrl(parent, colour=wx.Colour(self.value), style=wx.CLRP_USE_TEXTCTRL) + self.widget.SetValue = self.widget.SetColour + self.widget.LayoutDirection = wx.Layout_RightToLeft + return self.widget + def update(self): + self._value = wxColorToStr(self.widget.Colour, self.isRGBA) + class ComboSetting(wxSetting): def __init__(self, name, default, choices, label = None, help = None, group = None): @@ -132,30 +158,66 @@ def get_specific_widget(self, parent): import wx - self.widget = wx.ComboBox(parent, -1, str(self.value), choices = self.choices, style = wx.CB_DROPDOWN) + readonly = isinstance(self.choices, tuple) + if readonly: + # wx.Choice drops its list on click, no need to click down arrow + # which is far to the right because of wx.EXPAND + self.widget = wx.Choice(parent, -1, choices = self.choices) + self.widget.GetValue = lambda: self.choices[self.widget.Selection] + self.widget.SetValue = lambda v: self.widget.SetSelection(self.choices.index(v)) + self.widget.SetValue(self.value) + else: + self.widget = wx.ComboBox(parent, -1, str(self.value), choices = self.choices, style = wx.CB_DROPDOWN) return self.widget class SpinSetting(wxSetting): def __init__(self, name, default, min, max, label = None, help = None, group = None, increment = 0.1): - super(SpinSetting, self).__init__(name, default, label, help, group) + super().__init__(name, default, label, help, group) self.min = min self.max = max self.increment = increment def get_specific_widget(self, parent): - from wx.lib.agw.floatspin import FloatSpin - self.widget = FloatSpin(parent, -1, min_val = self.min, max_val = self.max, digits = 0) + import wx + self.widget = wx.SpinCtrlDouble(parent, -1, min = self.min, max = self.max) + self.widget.SetDigits(0) self.widget.SetValue(self.value) orig = self.widget.GetValue self.widget.GetValue = lambda: int(orig()) return self.widget +def MySpin(parent, digits, *args, **kw): + # in GTK 3.[01], spinner is not large enough to fit text + # Could be a class, but use function to avoid load errors if wx + # not installed + # If native wx.SpinCtrlDouble has problems in different platforms + # try agw + # from wx.lib.agw.floatspin import FloatSpin + import wx + sp = wx.SpinCtrlDouble(parent, *args, **kw) + # sp = FloatSpin(parent) + sp.SetDigits(digits) + # sp.SetValue(kw['initial']) + def fitValue(ev): + text = '%%.%df'% digits % sp.Max + # native wx.SpinCtrlDouble does not return good size + # in GTK 3.0 + tex = sp.GetTextExtent(text) + tsz = sp.GetSizeFromTextSize(tex.x) + + if sp.MinSize.x < tsz.x: + # print('fitValue', getattr(sp, 'setting', None), sp.Value, sp.Digits, tsz.x) + sp.MinSize = tsz + # sp.Size = tsz + # sp.Bind(wx.EVT_TEXT, fitValue) + fitValue(None) + return sp + class FloatSpinSetting(SpinSetting): def get_specific_widget(self, parent): - from wx.lib.agw.floatspin import FloatSpin - self.widget = FloatSpin(parent, -1, value = self.value, min_val = self.min, max_val = self.max, increment = self.increment, digits = 2) + self.widget = MySpin(parent, 2, initial = self.value, min = self.min, max = self.max, inc = self.increment) return self.widget class BooleanSetting(wxSetting): @@ -216,9 +278,12 @@ import wx build_dimensions = parse_build_dimensions(self.value) self.widgets = [] - w = lambda val, m, M: self.widgets.append(FloatSpin(parent, -1, value = val, min_val = m, max_val = M, digits = 2)) - addlabel = lambda name, pos: self.widget.Add(wx.StaticText(parent, -1, name), pos = pos, flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border = 5) - addwidget = lambda *pos: self.widget.Add(self.widgets[-1], pos = pos, flag = wx.RIGHT, border = 5) + def w(val, m, M): + self.widgets.append(MySpin(parent, 2, initial = val, min = m, max = M)) + def addlabel(name, pos): + self.widget.Add(wx.StaticText(parent, -1, name), pos = pos, flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border = 5) + def addwidget(*pos): + self.widget.Add(self.widgets[-1], pos = pos, flag = wx.RIGHT | wx.EXPAND, border = 5) self.widget = wx.GridBagSizer() addlabel(_("Width"), (0, 0)) w(build_dimensions[0], 0, 2000) @@ -240,20 +305,20 @@ addwidget(1, 5) addlabel(_("X home pos."), (2, 0)) w(build_dimensions[6], -2000, 2000) - self.widget.Add(self.widgets[-1], pos = (2, 1)) + addwidget(2, 1) addlabel(_("Y home pos."), (2, 2)) w(build_dimensions[7], -2000, 2000) - self.widget.Add(self.widgets[-1], pos = (2, 3)) + addwidget(2, 3) addlabel(_("Z home pos."), (2, 4)) w(build_dimensions[8], -2000, 2000) - self.widget.Add(self.widgets[-1], pos = (2, 5)) + addwidget(2, 5) return self.widget def update(self): values = [float(w.GetValue()) for w in self.widgets] self.value = "%.02fx%.02fx%.02f%+.02f%+.02f%+.02f%+.02f%+.02f%+.02f" % tuple(values) -class Settings(object): +class Settings: def __baudrate_list(self): return ["2400", "9600", "19200", "38400", "57600", "115200", "250000"] def __init__(self, root): @@ -264,15 +329,25 @@ 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) self._add(BooleanSetting("rpc_server", True, _("RPC server"), _("Enable RPC server to allow remotely querying print status")), root.update_rpc_server) self._add(BooleanSetting("dtr", True, _("DTR"), _("Disabling DTR would prevent Arduino (RAMPS) from resetting upon connection"), "Printer")) - self._add(SpinSetting("bedtemp_abs", 110, 0, 400, _("Bed temperature for ABS"), _("Heated Build Platform temp for ABS (deg C)"), "Printer")) - self._add(SpinSetting("bedtemp_pla", 60, 0, 400, _("Bed temperature for PLA"), _("Heated Build Platform temp for PLA (deg C)"), "Printer")) - self._add(SpinSetting("temperature_abs", 230, 0, 400, _("Extruder temperature for ABS"), _("Extruder temp for ABS (deg C)"), "Printer")) - self._add(SpinSetting("temperature_pla", 185, 0, 400, _("Extruder temperature for PLA"), _("Extruder temp for PLA (deg C)"), "Printer")) + if sys.platform != "win32": + self._add(StringSetting("devicepath", "", _("Device name pattern"), _("Custom device pattern: for example /dev/3DP_* "), "Printer")) + 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) + 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) + self._add(SpinSetting("temperature_abs", 230, 0, 400, _("Extruder temperature for ABS"), _("Extruder temp for ABS (deg C)"), "Printer"), root.set_temp_preset) + self._add(SpinSetting("temperature_pla", 185, 0, 400, _("Extruder temperature for PLA"), _("Extruder temp for PLA (deg C)"), "Printer"), root.set_temp_preset) self._add(SpinSetting("xy_feedrate", 3000, 0, 50000, _("X && Y manual feedrate"), _("Feedrate for Control Panel Moves in X and Y (mm/min)"), "Printer")) self._add(SpinSetting("z_feedrate", 100, 0, 50000, _("Z manual feedrate"), _("Feedrate for Control Panel Moves in Z (mm/min)"), "Printer")) self._add(SpinSetting("e_feedrate", 100, 0, 1000, _("E manual feedrate"), _("Feedrate for Control Panel Moves in Extrusions (mm/min)"), "Printer")) - self._add(StringSetting("slicecommand", "python skeinforge/skeinforge_application/skeinforge_utilities/skeinforge_craft.py $s", _("Slice command"), _("Slice command"), "External")) - self._add(StringSetting("sliceoptscommand", "python skeinforge/skeinforge_application/skeinforge.py", _("Slicer options command"), _("Slice settings command"), "External")) + defaultslicerpath = "" + if getattr(sys, 'frozen', False): + if sys.platform == "darwin": + defaultslicerpath = "/Applications/Slic3r.app/Contents/MacOS/" + elif sys.platform == "win32": + defaultslicerpath = ".\\slic3r\\" + self._add(StringSetting("slicecommandpath", defaultslicerpath, _("Path to slicer"), _("Path to slicer"), "External")) + slicer = 'slic3r-console' if sys.platform == 'win32' else 'slic3r' + self._add(StringSetting("slicecommand", slicer + ' $s --output $o', _("Slice command"), _("Slice command"), "External")) + self._add(StringSetting("sliceoptscommand", "slic3r", _("Slicer options command"), _("Slice settings command"), "External")) self._add(StringSetting("start_command", "", _("Start command"), _("Executable to run when the print is started"), "External")) self._add(StringSetting("final_command", "", _("Final command"), _("Executable to run when the print is finished"), "External")) self._add(StringSetting("error_command", "", _("Error command"), _("Executable to run when an error occurs"), "External")) @@ -296,6 +371,7 @@ self._add(HiddenSetting("default_extrusion", 5.0)) self._add(HiddenSetting("last_extrusion", 5.0)) self._add(HiddenSetting("total_filament_used", 0.0)) + self._add(HiddenSetting("spool_list", "")) _settings = [] @@ -316,13 +392,11 @@ return object.__getattribute__(self, name) return getattr(self, "_" + name).value - def _add(self, setting, callback = None, validate = None, + def _add(self, setting, callback = None, alias = None, autocomplete_list = None): setattr(self, setting.name, setting) if callback: setattr(self, "__" + setting.name + "_cb", callback) - if validate: - setattr(self, "__" + setting.name + "_validate", validate) if alias: setattr(self, "__" + setting.name + "_alias", alias) if autocomplete_list: @@ -335,20 +409,16 @@ pass except AttributeError: pass - try: - getattr(self, "__%s_validate" % key)(value) - except AttributeError: - pass + setting = getattr(self, '_'+key) + setting.validate(value) t = type(getattr(self, key)) - if t == bool and value == "False": setattr(self, key, False) - else: setattr(self, key, t(value)) + if t == bool and value == "False": + value = False + setattr(self, key, t(value)) try: - cb = None - try: - cb = getattr(self, "__%s_cb" % key) - except AttributeError: - pass - if cb is not None: cb(key, value) + cb = getattr(self, "__%s_cb" % key, None) + if cb is not None: + cb(key, value) except: logging.warning((_("Failed to run callback after setting \"%s\":") % key) + "\n" + traceback.format_exc()) @@ -360,7 +430,7 @@ except AttributeError: pass try: - return getattr(self, "__%s_alias" % key)().keys() + return list(getattr(self, "__%s_alias" % key)().keys()) except AttributeError: pass return []