Tue, 26 Sep 2017 19:08:02 +0200
SVG options: offset, original scale
SVG Bugfix: "scale" does now really fit to dimensions
15 | 1 | # This file is part of the Printrun suite. |
2 | # | |
3 | # Printrun is free software: you can redistribute it and/or modify | |
4 | # it under the terms of the GNU General Public License as published by | |
5 | # the Free Software Foundation, either version 3 of the License, or | |
6 | # (at your option) any later version. | |
7 | # | |
8 | # Printrun is distributed in the hope that it will be useful, | |
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | # GNU General Public License for more details. | |
12 | # | |
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/>. | |
15 | ||
16 | import logging | |
17 | import traceback | |
18 | ||
19 | from functools import wraps | |
20 | ||
21 | from .utils import parse_build_dimensions | |
22 | ||
23 | def setting_add_tooltip(func): | |
24 | @wraps(func) | |
25 | def decorator(self, *args, **kwargs): | |
26 | widget = func(self, *args, **kwargs) | |
27 | helptxt = self.help or "" | |
28 | sep, deftxt = "", "" | |
29 | if len(helptxt): | |
30 | sep = "\n" | |
31 | if helptxt.find("\n") >= 0: | |
32 | sep = "\n\n" | |
33 | if self.default is not "": | |
34 | deftxt = _("Default: ") | |
35 | resethelp = _("(Control-doubleclick to reset to default value)") | |
36 | if len(repr(self.default)) > 10: | |
37 | deftxt += "\n " + repr(self.default).strip("'") + "\n" + resethelp | |
38 | else: | |
39 | deftxt += repr(self.default) + " " + resethelp | |
40 | helptxt += sep + deftxt | |
41 | if len(helptxt): | |
42 | widget.SetToolTipString(helptxt) | |
43 | return widget | |
44 | return decorator | |
45 | ||
46 | class Setting(object): | |
47 | ||
48 | DEFAULT_GROUP = "Printer" | |
49 | ||
50 | hidden = False | |
51 | ||
52 | def __init__(self, name, default, label = None, help = None, group = None): | |
53 | self.name = name | |
54 | self.default = default | |
55 | self._value = default | |
56 | self.label = label | |
57 | self.help = help | |
58 | self.group = group if group else Setting.DEFAULT_GROUP | |
59 | ||
60 | def _get_value(self): | |
61 | return self._value | |
62 | ||
63 | def _set_value(self, value): | |
64 | raise NotImplementedError | |
65 | value = property(_get_value, _set_value) | |
66 | ||
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 | |
77 | def get_label(self, parent): | |
78 | import wx | |
79 | widget = wx.StaticText(parent, -1, self.label or self.name) | |
80 | widget.set_default = self.set_default | |
81 | return widget | |
82 | ||
83 | @setting_add_tooltip | |
84 | def get_widget(self, parent): | |
85 | return self.get_specific_widget(parent) | |
86 | ||
87 | def get_specific_widget(self, parent): | |
88 | raise NotImplementedError | |
89 | ||
90 | def update(self): | |
91 | raise NotImplementedError | |
92 | ||
93 | def __str__(self): | |
94 | return self.name | |
95 | ||
96 | def __repr__(self): | |
97 | return self.name | |
98 | ||
99 | class HiddenSetting(Setting): | |
100 | ||
101 | hidden = True | |
102 | ||
103 | def _set_value(self, value): | |
104 | self._value = value | |
105 | value = property(Setting._get_value, _set_value) | |
106 | ||
107 | class wxSetting(Setting): | |
108 | ||
109 | widget = None | |
110 | ||
111 | def _set_value(self, value): | |
112 | self._value = value | |
113 | if self.widget: | |
114 | self.widget.SetValue(value) | |
115 | value = property(Setting._get_value, _set_value) | |
116 | ||
117 | def update(self): | |
118 | self.value = self.widget.GetValue() | |
119 | ||
120 | class StringSetting(wxSetting): | |
121 | ||
122 | def get_specific_widget(self, parent): | |
123 | import wx | |
124 | self.widget = wx.TextCtrl(parent, -1, str(self.value)) | |
125 | return self.widget | |
126 | ||
127 | class ComboSetting(wxSetting): | |
128 | ||
129 | def __init__(self, name, default, choices, label = None, help = None, group = None): | |
130 | super(ComboSetting, self).__init__(name, default, label, help, group) | |
131 | self.choices = choices | |
132 | ||
133 | def get_specific_widget(self, parent): | |
134 | import wx | |
135 | self.widget = wx.ComboBox(parent, -1, str(self.value), choices = self.choices, style = wx.CB_DROPDOWN) | |
136 | return self.widget | |
137 | ||
138 | class SpinSetting(wxSetting): | |
139 | ||
140 | 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) | |
142 | self.min = min | |
143 | self.max = max | |
144 | self.increment = increment | |
145 | ||
146 | def get_specific_widget(self, parent): | |
147 | from wx.lib.agw.floatspin import FloatSpin | |
148 | self.widget = FloatSpin(parent, -1, min_val = self.min, max_val = self.max, digits = 0) | |
149 | self.widget.SetValue(self.value) | |
150 | orig = self.widget.GetValue | |
151 | self.widget.GetValue = lambda: int(orig()) | |
152 | return self.widget | |
153 | ||
154 | class FloatSpinSetting(SpinSetting): | |
155 | ||
156 | def get_specific_widget(self, parent): | |
157 | from wx.lib.agw.floatspin import FloatSpin | |
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 | |
160 | ||
161 | class BooleanSetting(wxSetting): | |
162 | ||
163 | def _get_value(self): | |
164 | return bool(self._value) | |
165 | ||
166 | def _set_value(self, value): | |
167 | self._value = value | |
168 | if self.widget: | |
169 | self.widget.SetValue(bool(value)) | |
170 | ||
171 | value = property(_get_value, _set_value) | |
172 | ||
173 | def get_specific_widget(self, parent): | |
174 | import wx | |
175 | self.widget = wx.CheckBox(parent, -1) | |
176 | self.widget.SetValue(bool(self.value)) | |
177 | return self.widget | |
178 | ||
179 | class StaticTextSetting(wxSetting): | |
180 | ||
181 | def __init__(self, name, label = " ", text = "", help = None, group = None): | |
182 | super(StaticTextSetting, self).__init__(name, "", label, help, group) | |
183 | self.text = text | |
184 | ||
185 | def update(self): | |
186 | pass | |
187 | ||
188 | def _get_value(self): | |
189 | return "" | |
190 | ||
191 | def _set_value(self, value): | |
192 | pass | |
193 | ||
194 | def get_specific_widget(self, parent): | |
195 | import wx | |
196 | self.widget = wx.StaticText(parent, -1, self.text) | |
197 | return self.widget | |
198 | ||
199 | class BuildDimensionsSetting(wxSetting): | |
200 | ||
201 | widgets = None | |
202 | ||
203 | def _set_value(self, value): | |
204 | self._value = value | |
205 | if self.widgets: | |
206 | self._set_widgets_values(value) | |
207 | value = property(wxSetting._get_value, _set_value) | |
208 | ||
209 | def _set_widgets_values(self, value): | |
210 | build_dimensions_list = parse_build_dimensions(value) | |
211 | for i in range(len(self.widgets)): | |
212 | self.widgets[i].SetValue(build_dimensions_list[i]) | |
213 | ||
214 | def get_widget(self, parent): | |
215 | from wx.lib.agw.floatspin import FloatSpin | |
216 | import wx | |
217 | build_dimensions = parse_build_dimensions(self.value) | |
218 | self.widgets = [] | |
219 | w = lambda val, m, M: self.widgets.append(FloatSpin(parent, -1, value = val, min_val = m, max_val = M, digits = 2)) | |
220 | addlabel = lambda name, pos: self.widget.Add(wx.StaticText(parent, -1, name), pos = pos, flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border = 5) | |
221 | addwidget = lambda *pos: self.widget.Add(self.widgets[-1], pos = pos, flag = wx.RIGHT, border = 5) | |
222 | self.widget = wx.GridBagSizer() | |
223 | addlabel(_("Width"), (0, 0)) | |
224 | w(build_dimensions[0], 0, 2000) | |
225 | addwidget(0, 1) | |
226 | addlabel(_("Depth"), (0, 2)) | |
227 | w(build_dimensions[1], 0, 2000) | |
228 | addwidget(0, 3) | |
229 | addlabel(_("Height"), (0, 4)) | |
230 | w(build_dimensions[2], 0, 2000) | |
231 | addwidget(0, 5) | |
232 | addlabel(_("X offset"), (1, 0)) | |
233 | w(build_dimensions[3], -2000, 2000) | |
234 | addwidget(1, 1) | |
235 | addlabel(_("Y offset"), (1, 2)) | |
236 | w(build_dimensions[4], -2000, 2000) | |
237 | addwidget(1, 3) | |
238 | addlabel(_("Z offset"), (1, 4)) | |
239 | w(build_dimensions[5], -2000, 2000) | |
240 | addwidget(1, 5) | |
241 | addlabel(_("X home pos."), (2, 0)) | |
242 | w(build_dimensions[6], -2000, 2000) | |
243 | self.widget.Add(self.widgets[-1], pos = (2, 1)) | |
244 | addlabel(_("Y home pos."), (2, 2)) | |
245 | w(build_dimensions[7], -2000, 2000) | |
246 | self.widget.Add(self.widgets[-1], pos = (2, 3)) | |
247 | addlabel(_("Z home pos."), (2, 4)) | |
248 | w(build_dimensions[8], -2000, 2000) | |
249 | self.widget.Add(self.widgets[-1], pos = (2, 5)) | |
250 | return self.widget | |
251 | ||
252 | def update(self): | |
253 | values = [float(w.GetValue()) for w in self.widgets] | |
254 | self.value = "%.02fx%.02fx%.02f%+.02f%+.02f%+.02f%+.02f%+.02f%+.02f" % tuple(values) | |
255 | ||
256 | class Settings(object): | |
257 | def __baudrate_list(self): return ["2400", "9600", "19200", "38400", "57600", "115200", "250000"] | |
258 | ||
259 | def __init__(self, root): | |
260 | # defaults here. | |
261 | # the initial value determines the type | |
262 | 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"))) | |
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) | |
265 | 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")) | |
267 | self._add(SpinSetting("bedtemp_abs", 110, 0, 400, _("Bed temperature for ABS"), _("Heated Build Platform temp for ABS (deg C)"), "Printer")) | |
268 | self._add(SpinSetting("bedtemp_pla", 60, 0, 400, _("Bed temperature for PLA"), _("Heated Build Platform temp for PLA (deg C)"), "Printer")) | |
269 | self._add(SpinSetting("temperature_abs", 230, 0, 400, _("Extruder temperature for ABS"), _("Extruder temp for ABS (deg C)"), "Printer")) | |
270 | self._add(SpinSetting("temperature_pla", 185, 0, 400, _("Extruder temperature for PLA"), _("Extruder temp for PLA (deg C)"), "Printer")) | |
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")) | |
272 | 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")) | |
274 | self._add(StringSetting("slicecommand", "python skeinforge/skeinforge_application/skeinforge_utilities/skeinforge_craft.py $s", _("Slice command"), _("Slice command"), "External")) | |
275 | self._add(StringSetting("sliceoptscommand", "python skeinforge/skeinforge_application/skeinforge.py", _("Slicer options command"), _("Slice settings command"), "External")) | |
276 | 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")) | |
278 | 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")) | |
280 | ||
281 | self._add(HiddenSetting("project_offset_x", 0.0)) | |
282 | self._add(HiddenSetting("project_offset_y", 0.0)) | |
283 | self._add(HiddenSetting("project_interval", 2.0)) | |
284 | self._add(HiddenSetting("project_pause", 2.5)) | |
285 | self._add(HiddenSetting("project_scale", 1.0)) | |
286 | self._add(HiddenSetting("project_x", 1024)) | |
287 | self._add(HiddenSetting("project_y", 768)) | |
288 | self._add(HiddenSetting("project_projected_x", 150.0)) | |
289 | self._add(HiddenSetting("project_direction", "Top Down")) | |
290 | self._add(HiddenSetting("project_overshoot", 3.0)) | |
291 | self._add(HiddenSetting("project_z_axis_rate", 200)) | |
292 | self._add(HiddenSetting("project_layer", 0.1)) | |
293 | self._add(HiddenSetting("project_prelift_gcode", "")) | |
294 | self._add(HiddenSetting("project_postlift_gcode", "")) | |
295 | self._add(HiddenSetting("pause_between_prints", True)) | |
296 | self._add(HiddenSetting("default_extrusion", 5.0)) | |
297 | self._add(HiddenSetting("last_extrusion", 5.0)) | |
298 | self._add(HiddenSetting("total_filament_used", 0.0)) | |
299 | ||
300 | _settings = [] | |
301 | ||
302 | def __setattr__(self, name, value): | |
303 | if name.startswith("_"): | |
304 | return object.__setattr__(self, name, value) | |
305 | if isinstance(value, Setting): | |
306 | if not value.hidden: | |
307 | self._settings.append(value) | |
308 | object.__setattr__(self, "_" + name, value) | |
309 | elif hasattr(self, "_" + name): | |
310 | getattr(self, "_" + name).value = value | |
311 | else: | |
312 | setattr(self, name, StringSetting(name = name, default = value)) | |
313 | ||
314 | def __getattr__(self, name): | |
315 | if name.startswith("_"): | |
316 | return object.__getattribute__(self, name) | |
317 | return getattr(self, "_" + name).value | |
318 | ||
319 | def _add(self, setting, callback = None, validate = None, | |
320 | alias = None, autocomplete_list = None): | |
321 | setattr(self, setting.name, setting) | |
322 | if callback: | |
323 | setattr(self, "__" + setting.name + "_cb", callback) | |
324 | if validate: | |
325 | setattr(self, "__" + setting.name + "_validate", validate) | |
326 | if alias: | |
327 | setattr(self, "__" + setting.name + "_alias", alias) | |
328 | if autocomplete_list: | |
329 | setattr(self, "__" + setting.name + "_list", autocomplete_list) | |
330 | ||
331 | def _set(self, key, value): | |
332 | try: | |
333 | value = getattr(self, "__%s_alias" % key)()[value] | |
334 | except KeyError: | |
335 | pass | |
336 | except AttributeError: | |
337 | pass | |
338 | try: | |
339 | getattr(self, "__%s_validate" % key)(value) | |
340 | except AttributeError: | |
341 | pass | |
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: | |
353 | logging.warning((_("Failed to run callback after setting \"%s\":") % key) + | |
354 | "\n" + traceback.format_exc()) | |
355 | return value | |
356 | ||
357 | def _tabcomplete(self, key): | |
358 | try: | |
359 | return getattr(self, "__%s_list" % key)() | |
360 | except AttributeError: | |
361 | pass | |
362 | try: | |
363 | return getattr(self, "__%s_alias" % key)().keys() | |
364 | except AttributeError: | |
365 | pass | |
366 | return [] | |
367 | ||
368 | def _all_settings(self): | |
369 | return self._settings |