Wed, 20 Jan 2021 11:37:03 +0100
reimplemented lasercutter changes
46 | 1 | #!/usr/bin/env python3 |
15 | 2 | |
3 | # This file is part of the Printrun suite. | |
4 | # | |
5 | # Printrun is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU General Public License as published by | |
7 | # the Free Software Foundation, either version 3 of the License, or | |
8 | # (at your option) any later version. | |
9 | # | |
10 | # Printrun is distributed in the hope that it will be useful, | |
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | # GNU General Public License for more details. | |
14 | # | |
15 | # You should have received a copy of the GNU General Public License | |
16 | # along with Printrun. If not, see <http://www.gnu.org/licenses/>. | |
17 | ||
18 | import logging | |
19 | import wx | |
20 | ||
21 | from . import gcoder | |
22 | from .gl.panel import wxGLPanel | |
23 | from .gl.trackball import build_rotmatrix | |
24 | from .gl.libtatlin import actors | |
25 | from .injectgcode import injector, injector_edit | |
26 | ||
27 | from pyglet.gl import glPushMatrix, glPopMatrix, \ | |
28 | glTranslatef, glRotatef, glScalef, glMultMatrixd, \ | |
29 | glGetDoublev, GL_MODELVIEW_MATRIX, GLdouble | |
30 | ||
31 | from .gviz import GvizBaseFrame | |
32 | ||
33 | from .utils import imagefile, install_locale, get_home_pos | |
34 | install_locale('pronterface') | |
35 | ||
36 | def create_model(light): | |
37 | if light: | |
38 | return actors.GcodeModelLight() | |
39 | else: | |
40 | return actors.GcodeModel() | |
41 | ||
42 | def gcode_dims(g): | |
43 | return ((g.xmin, g.xmax, g.width), | |
44 | (g.ymin, g.ymax, g.depth), | |
45 | (g.zmin, g.zmax, g.height)) | |
46 | ||
47 | def set_model_colors(model, root): | |
48 | for field in dir(model): | |
49 | if field.startswith("color_"): | |
50 | root_fieldname = "gcview_" + field | |
51 | if hasattr(root, root_fieldname): | |
52 | setattr(model, field, getattr(root, root_fieldname)) | |
53 | ||
46 | 54 | def recreate_platform(self, build_dimensions, circular, grid): |
55 | self.platform = actors.Platform(build_dimensions, circular = circular, grid = grid) | |
15 | 56 | self.objects[0].model = self.platform |
57 | wx.CallAfter(self.Refresh) | |
58 | ||
59 | def set_gcview_params(self, path_width, path_height): | |
60 | self.path_halfwidth = path_width / 2 | |
61 | self.path_halfheight = path_height / 2 | |
62 | has_changed = False | |
63 | for obj in self.objects[1:]: | |
64 | if isinstance(obj.model, actors.GcodeModel): | |
65 | obj.model.set_path_size(self.path_halfwidth, self.path_halfheight) | |
66 | has_changed = True | |
67 | return has_changed | |
68 | ||
46 | 69 | # E selected for Up because is above D |
70 | LAYER_UP_KEYS = ord('U'), ord('E'), wx.WXK_UP | |
71 | LAYER_DOWN_KEYS = ord('D'), wx.WXK_DOWN | |
72 | ZOOM_IN_KEYS = wx.WXK_PAGEDOWN, 388, wx.WXK_RIGHT, ord('=') | |
73 | ZOOM_OUT_KEYS = wx.WXK_PAGEUP, 390, wx.WXK_LEFT, ord('-') | |
74 | FIT_KEYS = [ord('F')] | |
75 | CURRENT_LAYER_KEYS = [ord('C')] | |
76 | RESET_KEYS = [ord('R')] | |
77 | ||
15 | 78 | class GcodeViewPanel(wxGLPanel): |
79 | ||
46 | 80 | def __init__(self, parent, |
81 | build_dimensions = (200, 200, 100, 0, 0, 0), | |
82 | realparent = None, antialias_samples = 0): | |
83 | super().__init__(parent, wx.DefaultPosition, | |
15 | 84 | wx.DefaultSize, 0, |
85 | antialias_samples = antialias_samples) | |
86 | self.canvas.Bind(wx.EVT_MOUSE_EVENTS, self.move) | |
87 | self.canvas.Bind(wx.EVT_LEFT_DCLICK, self.double) | |
46 | 88 | # self.canvas.Bind(wx.EVT_KEY_DOWN, self.keypress) |
89 | # in Windows event inspector shows only EVT_CHAR_HOOK events | |
90 | self.canvas.Bind(wx.EVT_CHAR_HOOK, self.keypress) | |
15 | 91 | self.initialized = 0 |
92 | self.canvas.Bind(wx.EVT_MOUSEWHEEL, self.wheel) | |
46 | 93 | self.parent = realparent or parent |
15 | 94 | self.initpos = None |
46 | 95 | self.build_dimensions = build_dimensions |
96 | self.dist = max(self.build_dimensions[:2]) | |
15 | 97 | self.basequat = [0, 0, 0, 1] |
98 | self.mousepos = [0, 0] | |
99 | ||
100 | def inject(self): | |
101 | l = self.parent.model.num_layers_to_draw | |
46 | 102 | filtered = [k for k, v in self.parent.model.layer_idxs_map.items() if v == l] |
15 | 103 | if filtered: |
104 | injector(self.parent.model.gcode, l, filtered[0]) | |
105 | else: | |
106 | logging.error(_("Invalid layer for injection")) | |
107 | ||
108 | def editlayer(self): | |
109 | l = self.parent.model.num_layers_to_draw | |
46 | 110 | filtered = [k for k, v in self.parent.model.layer_idxs_map.items() if v == l] |
15 | 111 | if filtered: |
112 | injector_edit(self.parent.model.gcode, l, filtered[0]) | |
113 | else: | |
114 | logging.error(_("Invalid layer for edition")) | |
115 | ||
116 | def setlayercb(self, layer): | |
117 | pass | |
118 | ||
119 | def OnInitGL(self, *args, **kwargs): | |
46 | 120 | super().OnInitGL(*args, **kwargs) |
121 | filenames = getattr(self.parent, 'filenames', None) | |
122 | if filenames: | |
123 | for filename in filenames: | |
15 | 124 | self.parent.load_file(filename) |
125 | self.parent.autoplate() | |
46 | 126 | getattr(self.parent, 'loadcb', bool)() |
15 | 127 | self.parent.filenames = None |
128 | ||
129 | def create_objects(self): | |
130 | '''create opengl objects when opengl is initialized''' | |
131 | for obj in self.parent.objects: | |
132 | if obj.model and obj.model.loaded and not obj.model.initialized: | |
133 | obj.model.init() | |
134 | ||
135 | def update_object_resize(self): | |
46 | 136 | '''called when the window receives only if opengl is initialized''' |
15 | 137 | pass |
138 | ||
139 | def draw_objects(self): | |
140 | '''called in the middle of ondraw after the buffer has been cleared''' | |
141 | self.create_objects() | |
142 | ||
143 | glPushMatrix() | |
144 | # Rotate according to trackball | |
145 | glMultMatrixd(build_rotmatrix(self.basequat)) | |
146 | # Move origin to bottom left of platform | |
147 | platformx0 = -self.build_dimensions[3] - self.parent.platform.width / 2 | |
148 | platformy0 = -self.build_dimensions[4] - self.parent.platform.depth / 2 | |
149 | glTranslatef(platformx0, platformy0, 0) | |
150 | ||
151 | for obj in self.parent.objects: | |
152 | if not obj.model \ | |
46 | 153 | or not obj.model.loaded: |
15 | 154 | continue |
46 | 155 | # Skip (comment out) initialized check, which safely causes empty |
156 | # model during progressive load. This can cause exceptions/garbage | |
157 | # render, but seems fine for now | |
158 | # May need to lock init() and draw_objects() together | |
159 | # if not obj.model.initialized: | |
160 | # continue | |
15 | 161 | glPushMatrix() |
162 | glTranslatef(*(obj.offsets)) | |
163 | glRotatef(obj.rot, 0.0, 0.0, 1.0) | |
164 | glTranslatef(*(obj.centeroffset)) | |
165 | glScalef(*obj.scale) | |
166 | ||
167 | obj.model.display() | |
168 | glPopMatrix() | |
169 | glPopMatrix() | |
170 | ||
171 | # ========================================================================== | |
172 | # Utils | |
173 | # ========================================================================== | |
174 | def get_modelview_mat(self, local_transform): | |
175 | mvmat = (GLdouble * 16)() | |
176 | if local_transform: | |
177 | glPushMatrix() | |
178 | # Rotate according to trackball | |
179 | glMultMatrixd(build_rotmatrix(self.basequat)) | |
180 | # Move origin to bottom left of platform | |
181 | platformx0 = -self.build_dimensions[3] - self.parent.platform.width / 2 | |
182 | platformy0 = -self.build_dimensions[4] - self.parent.platform.depth / 2 | |
183 | glTranslatef(platformx0, platformy0, 0) | |
184 | glGetDoublev(GL_MODELVIEW_MATRIX, mvmat) | |
185 | glPopMatrix() | |
186 | else: | |
187 | glGetDoublev(GL_MODELVIEW_MATRIX, mvmat) | |
188 | return mvmat | |
189 | ||
190 | def double(self, event): | |
46 | 191 | getattr(self.parent, 'clickcb', bool)(event) |
15 | 192 | |
193 | def move(self, event): | |
194 | """react to mouse actions: | |
195 | no mouse: show red mousedrop | |
196 | LMB: rotate viewport | |
197 | RMB: move viewport | |
198 | """ | |
199 | if event.Entering(): | |
200 | self.canvas.SetFocus() | |
201 | event.Skip() | |
202 | return | |
46 | 203 | if event.Dragging(): |
204 | if event.LeftIsDown(): | |
205 | self.handle_rotation(event) | |
206 | elif event.RightIsDown(): | |
207 | self.handle_translation(event) | |
208 | self.Refresh(False) | |
209 | elif event.LeftUp() or event.RightUp(): | |
15 | 210 | self.initpos = None |
211 | event.Skip() | |
212 | ||
213 | def layerup(self): | |
46 | 214 | if not getattr(self.parent, 'model', False): |
15 | 215 | return |
216 | max_layers = self.parent.model.max_layers | |
217 | current_layer = self.parent.model.num_layers_to_draw | |
218 | # accept going up to max_layers + 1 | |
219 | # max_layers means visualizing the last layer differently, | |
220 | # max_layers + 1 means visualizing all layers with the same color | |
221 | new_layer = min(max_layers + 1, current_layer + 1) | |
222 | self.parent.model.num_layers_to_draw = new_layer | |
223 | self.parent.setlayercb(new_layer) | |
224 | wx.CallAfter(self.Refresh) | |
225 | ||
226 | def layerdown(self): | |
46 | 227 | if not getattr(self.parent, 'model', False): |
15 | 228 | return |
229 | current_layer = self.parent.model.num_layers_to_draw | |
230 | new_layer = max(1, current_layer - 1) | |
231 | self.parent.model.num_layers_to_draw = new_layer | |
232 | self.parent.setlayercb(new_layer) | |
233 | wx.CallAfter(self.Refresh) | |
234 | ||
46 | 235 | wheelTimestamp = None |
15 | 236 | def handle_wheel(self, event): |
46 | 237 | if self.wheelTimestamp == event.Timestamp: |
238 | # filter duplicate event delivery in Ubuntu, Debian issue #1110 | |
239 | return | |
240 | ||
241 | self.wheelTimestamp = event.Timestamp | |
242 | ||
15 | 243 | delta = event.GetWheelRotation() |
244 | factor = 1.05 | |
245 | if event.ControlDown(): | |
246 | factor = 1.02 | |
247 | if hasattr(self.parent, "model") and event.ShiftDown(): | |
248 | if not self.parent.model: | |
249 | return | |
250 | count = 1 if not event.ControlDown() else 10 | |
251 | for i in range(count): | |
252 | if delta > 0: self.layerup() | |
253 | else: self.layerdown() | |
254 | return | |
46 | 255 | x, y = event.GetPosition() |
15 | 256 | x, y, _ = self.mouse_to_3d(x, y) |
257 | if delta > 0: | |
258 | self.zoom(factor, (x, y)) | |
259 | else: | |
260 | self.zoom(1 / factor, (x, y)) | |
261 | ||
262 | def wheel(self, event): | |
263 | """react to mouse wheel actions: | |
264 | without shift: set max layer | |
265 | with shift: zoom viewport | |
266 | """ | |
267 | self.handle_wheel(event) | |
268 | wx.CallAfter(self.Refresh) | |
269 | ||
270 | def fit(self): | |
271 | if not self.parent.model or not self.parent.model.loaded: | |
272 | return | |
273 | self.canvas.SetCurrent(self.context) | |
274 | dims = gcode_dims(self.parent.model.gcode) | |
275 | self.reset_mview(1.0) | |
276 | center_x = (dims[0][0] + dims[0][1]) / 2 | |
277 | center_y = (dims[1][0] + dims[1][1]) / 2 | |
278 | center_x = self.build_dimensions[0] / 2 - center_x | |
279 | center_y = self.build_dimensions[1] / 2 - center_y | |
280 | if self.orthographic: | |
281 | ratio = float(self.dist) / max(dims[0][2], dims[1][2]) | |
282 | glScalef(ratio, ratio, 1) | |
283 | glTranslatef(center_x, center_y, 0) | |
284 | wx.CallAfter(self.Refresh) | |
285 | ||
286 | def keypress(self, event): | |
46 | 287 | """gets keypress events and moves/rotates active shape""" |
288 | if event.HasModifiers(): | |
289 | # let alt+c bubble up | |
290 | event.Skip() | |
291 | return | |
292 | step = event.ControlDown() and 1.05 or 1.1 | |
15 | 293 | key = event.GetKeyCode() |
46 | 294 | if key in LAYER_UP_KEYS: |
15 | 295 | self.layerup() |
46 | 296 | return # prevent shifting focus to other controls |
297 | elif key in LAYER_DOWN_KEYS: | |
15 | 298 | self.layerdown() |
46 | 299 | return |
300 | # x, y, _ = self.mouse_to_3d(self.width / 2, self.height / 2) | |
301 | elif key in ZOOM_IN_KEYS: | |
15 | 302 | self.zoom_to_center(step) |
46 | 303 | return |
304 | elif key in ZOOM_OUT_KEYS: | |
15 | 305 | self.zoom_to_center(1 / step) |
46 | 306 | return |
307 | elif key in FIT_KEYS: | |
15 | 308 | self.fit() |
46 | 309 | elif key in CURRENT_LAYER_KEYS: |
15 | 310 | if not self.parent.model or not self.parent.model.loaded: |
311 | return | |
312 | self.parent.model.only_current = not self.parent.model.only_current | |
313 | wx.CallAfter(self.Refresh) | |
46 | 314 | elif key in RESET_KEYS: |
15 | 315 | self.resetview() |
316 | event.Skip() | |
317 | ||
318 | def resetview(self): | |
319 | self.canvas.SetCurrent(self.context) | |
320 | self.reset_mview(0.9) | |
321 | self.basequat = [0, 0, 0, 1] | |
322 | wx.CallAfter(self.Refresh) | |
323 | ||
46 | 324 | class GCObject: |
15 | 325 | |
326 | def __init__(self, model): | |
327 | self.offsets = [0, 0, 0] | |
328 | self.centeroffset = [0, 0, 0] | |
329 | self.rot = 0 | |
330 | self.curlayer = 0.0 | |
331 | self.scale = [1.0, 1.0, 1.0] | |
332 | self.model = model | |
333 | ||
46 | 334 | class GcodeViewLoader: |
15 | 335 | |
336 | path_halfwidth = 0.2 | |
337 | path_halfheight = 0.15 | |
338 | ||
339 | def addfile_perlayer(self, gcode = None, showall = False): | |
340 | self.model = create_model(self.root.settings.light3d | |
341 | if self.root else False) | |
342 | if isinstance(self.model, actors.GcodeModel): | |
343 | self.model.set_path_size(self.path_halfwidth, self.path_halfheight) | |
344 | self.objects[-1].model = self.model | |
345 | if self.root: | |
346 | set_model_colors(self.model, self.root) | |
347 | if gcode is not None: | |
348 | generator = self.model.load_data(gcode) | |
46 | 349 | generator_output = next(generator) |
15 | 350 | while generator_output is not None: |
351 | yield generator_output | |
46 | 352 | generator_output = next(generator) |
15 | 353 | wx.CallAfter(self.Refresh) |
354 | yield None | |
355 | ||
356 | def addfile(self, gcode = None, showall = False): | |
357 | generator = self.addfile_perlayer(gcode, showall) | |
46 | 358 | while next(generator) is not None: |
15 | 359 | continue |
360 | ||
361 | def set_gcview_params(self, path_width, path_height): | |
362 | return set_gcview_params(self, path_width, path_height) | |
363 | ||
46 | 364 | from printrun.gviz import BaseViz |
365 | class GcodeViewMainWrapper(GcodeViewLoader, BaseViz): | |
15 | 366 | |
46 | 367 | def __init__(self, parent, build_dimensions, root, circular, antialias_samples, grid): |
15 | 368 | self.root = root |
369 | self.glpanel = GcodeViewPanel(parent, realparent = self, | |
370 | build_dimensions = build_dimensions, | |
371 | antialias_samples = antialias_samples) | |
372 | self.glpanel.SetMinSize((150, 150)) | |
373 | if self.root and hasattr(self.root, "gcview_color_background"): | |
374 | self.glpanel.color_background = self.root.gcview_color_background | |
375 | self.clickcb = None | |
376 | self.widget = self.glpanel | |
377 | self.refresh_timer = wx.CallLater(100, self.Refresh) | |
378 | self.p = self # Hack for backwards compatibility with gviz API | |
46 | 379 | self.grid = grid |
380 | self.platform = actors.Platform(build_dimensions, circular = circular, grid = grid) | |
15 | 381 | self.model = None |
382 | self.objects = [GCObject(self.platform), GCObject(None)] | |
383 | ||
384 | def __getattr__(self, name): | |
385 | return getattr(self.glpanel, name) | |
386 | ||
46 | 387 | def on_settings_change(self, changed_settings): |
388 | if self.model: | |
389 | for s in changed_settings: | |
390 | if s.name.startswith('gcview_color_'): | |
391 | self.model.update_colors() | |
392 | break | |
393 | ||
15 | 394 | def set_current_gline(self, gline): |
395 | if gline.is_move and gline.gcview_end_vertex is not None \ | |
396 | and self.model and self.model.loaded: | |
397 | self.model.printed_until = gline.gcview_end_vertex | |
398 | if not self.refresh_timer.IsRunning(): | |
399 | self.refresh_timer.Start() | |
400 | ||
46 | 401 | def recreate_platform(self, build_dimensions, circular, grid): |
402 | return recreate_platform(self, build_dimensions, circular, grid) | |
15 | 403 | |
404 | def setlayer(self, layer): | |
405 | if layer in self.model.layer_idxs_map: | |
406 | viz_layer = self.model.layer_idxs_map[layer] | |
407 | self.parent.model.num_layers_to_draw = viz_layer | |
408 | wx.CallAfter(self.Refresh) | |
409 | ||
410 | def clear(self): | |
411 | self.model = None | |
412 | self.objects[-1].model = None | |
413 | wx.CallAfter(self.Refresh) | |
414 | ||
415 | class GcodeViewFrame(GvizBaseFrame, GcodeViewLoader): | |
416 | '''A simple class for using OpenGL with wxPython.''' | |
417 | ||
418 | def __init__(self, parent, ID, title, build_dimensions, objects = None, | |
419 | pos = wx.DefaultPosition, size = wx.DefaultSize, | |
420 | style = wx.DEFAULT_FRAME_STYLE, root = None, circular = False, | |
46 | 421 | antialias_samples = 0, |
422 | grid = (1, 10)): | |
15 | 423 | GvizBaseFrame.__init__(self, parent, ID, title, |
424 | pos, size, style) | |
425 | self.root = root | |
426 | ||
427 | panel, vbox = self.create_base_ui() | |
428 | ||
429 | self.refresh_timer = wx.CallLater(100, self.Refresh) | |
430 | self.p = self # Hack for backwards compatibility with gviz API | |
431 | self.clonefrom = objects | |
46 | 432 | self.platform = actors.Platform(build_dimensions, circular = circular, grid = grid) |
433 | self.model = objects[1].model if objects else None | |
15 | 434 | self.objects = [GCObject(self.platform), GCObject(None)] |
435 | ||
436 | fit_image = wx.Image(imagefile('fit.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap() | |
46 | 437 | self.toolbar.InsertTool(6, 8, " " + _("Fit to plate"), fit_image, |
15 | 438 | shortHelp = _("Fit to plate [F]"), |
439 | longHelp = '') | |
440 | self.toolbar.Realize() | |
441 | self.glpanel = GcodeViewPanel(panel, | |
442 | build_dimensions = build_dimensions, | |
443 | realparent = self, | |
444 | antialias_samples = antialias_samples) | |
445 | vbox.Add(self.glpanel, 1, flag = wx.EXPAND) | |
446 | ||
447 | self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.zoom_to_center(1.2), id = 1) | |
448 | self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.zoom_to_center(1 / 1.2), id = 2) | |
449 | self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.layerup(), id = 3) | |
450 | self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.layerdown(), id = 4) | |
451 | self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.resetview(), id = 5) | |
452 | self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.fit(), id = 8) | |
453 | self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.inject(), id = 6) | |
454 | self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.editlayer(), id = 7) | |
455 | ||
456 | def setlayercb(self, layer): | |
457 | self.layerslider.SetValue(layer) | |
458 | self.update_status("") | |
459 | ||
460 | def update_status(self, extra): | |
461 | layer = self.model.num_layers_to_draw | |
46 | 462 | filtered = [k for k, v in self.model.layer_idxs_map.items() if v == layer] |
15 | 463 | if filtered: |
464 | true_layer = filtered[0] | |
465 | z = self.model.gcode.all_layers[true_layer].z | |
466 | message = _("Layer %d -%s Z = %.03f mm") % (layer, extra, z) | |
467 | else: | |
468 | message = _("Entire object") | |
469 | wx.CallAfter(self.SetStatusText, message, 0) | |
470 | ||
471 | def process_slider(self, event): | |
472 | new_layer = self.layerslider.GetValue() | |
473 | new_layer = min(self.model.max_layers + 1, new_layer) | |
474 | new_layer = max(1, new_layer) | |
475 | self.model.num_layers_to_draw = new_layer | |
476 | self.update_status("") | |
477 | wx.CallAfter(self.Refresh) | |
478 | ||
479 | def set_current_gline(self, gline): | |
480 | if gline.is_move and gline.gcview_end_vertex is not None \ | |
481 | and self.model and self.model.loaded: | |
482 | self.model.printed_until = gline.gcview_end_vertex | |
483 | if not self.refresh_timer.IsRunning(): | |
484 | self.refresh_timer.Start() | |
485 | ||
46 | 486 | def recreate_platform(self, build_dimensions, circular, grid): |
487 | return recreate_platform(self, build_dimensions, circular, grid) | |
15 | 488 | |
489 | def addfile(self, gcode = None): | |
490 | if self.clonefrom: | |
491 | self.model = self.clonefrom[-1].model.copy() | |
492 | self.objects[-1].model = self.model | |
493 | else: | |
494 | GcodeViewLoader.addfile(self, gcode) | |
495 | self.layerslider.SetRange(1, self.model.max_layers + 1) | |
496 | self.layerslider.SetValue(self.model.max_layers + 1) | |
497 | wx.CallAfter(self.SetStatusText, _("Entire object"), 0) | |
498 | wx.CallAfter(self.Refresh) | |
499 | ||
500 | def clear(self): | |
501 | self.model = None | |
502 | self.objects[-1].model = None | |
503 | wx.CallAfter(self.Refresh) | |
504 | ||
505 | if __name__ == "__main__": | |
506 | import sys | |
507 | app = wx.App(redirect = False) | |
508 | build_dimensions = [200, 200, 100, 0, 0, 0] | |
509 | title = 'Gcode view, shift to move view, mousewheel to set layer' | |
510 | frame = GcodeViewFrame(None, wx.ID_ANY, title, size = (400, 400), | |
511 | build_dimensions = build_dimensions) | |
512 | gcode = gcoder.GCode(open(sys.argv[1]), get_home_pos(build_dimensions)) | |
513 | frame.addfile(gcode) | |
514 | ||
515 | first_move = None | |
516 | for i in range(len(gcode.lines)): | |
517 | if gcode.lines[i].is_move: | |
518 | first_move = gcode.lines[i] | |
519 | break | |
520 | last_move = None | |
521 | for i in range(len(gcode.lines) - 1, -1, -1): | |
522 | if gcode.lines[i].is_move: | |
523 | last_move = gcode.lines[i] | |
524 | break | |
525 | nsteps = 20 | |
526 | steptime = 500 | |
527 | lines = [first_move] + [gcode.lines[int(float(i) * (len(gcode.lines) - 1) / nsteps)] for i in range(1, nsteps)] + [last_move] | |
528 | current_line = 0 | |
529 | ||
530 | def setLine(): | |
531 | global current_line | |
532 | frame.set_current_gline(lines[current_line]) | |
533 | current_line = (current_line + 1) % len(lines) | |
534 | timer.Start() | |
535 | timer = wx.CallLater(steptime, setLine) | |
536 | timer.Start() | |
537 | ||
538 | frame.Show(True) | |
539 | app.MainLoop() | |
540 | app.Destroy() |