--- a/printrun-src/printrun/gcview.py Tue Jan 19 20:45:09 2021 +0100 +++ b/printrun-src/printrun/gcview.py Wed Jan 20 10:15:13 2021 +0100 @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # This file is part of the Printrun suite. # @@ -51,8 +51,8 @@ if hasattr(root, root_fieldname): setattr(model, field, getattr(root, root_fieldname)) -def recreate_platform(self, build_dimensions, circular): - self.platform = actors.Platform(build_dimensions, circular = circular) +def recreate_platform(self, build_dimensions, circular, grid): + self.platform = actors.Platform(build_dimensions, circular = circular, grid = grid) self.objects[0].model = self.platform wx.CallAfter(self.Refresh) @@ -66,32 +66,40 @@ has_changed = True return has_changed +# E selected for Up because is above D +LAYER_UP_KEYS = ord('U'), ord('E'), wx.WXK_UP +LAYER_DOWN_KEYS = ord('D'), wx.WXK_DOWN +ZOOM_IN_KEYS = wx.WXK_PAGEDOWN, 388, wx.WXK_RIGHT, ord('=') +ZOOM_OUT_KEYS = wx.WXK_PAGEUP, 390, wx.WXK_LEFT, ord('-') +FIT_KEYS = [ord('F')] +CURRENT_LAYER_KEYS = [ord('C')] +RESET_KEYS = [ord('R')] + class GcodeViewPanel(wxGLPanel): - def __init__(self, parent, id = wx.ID_ANY, - build_dimensions = None, realparent = None, - antialias_samples = 0): - super(GcodeViewPanel, self).__init__(parent, id, wx.DefaultPosition, + def __init__(self, parent, + build_dimensions = (200, 200, 100, 0, 0, 0), + realparent = None, antialias_samples = 0): + super().__init__(parent, wx.DefaultPosition, wx.DefaultSize, 0, antialias_samples = antialias_samples) self.canvas.Bind(wx.EVT_MOUSE_EVENTS, self.move) self.canvas.Bind(wx.EVT_LEFT_DCLICK, self.double) - self.canvas.Bind(wx.EVT_KEY_DOWN, self.keypress) + # self.canvas.Bind(wx.EVT_KEY_DOWN, self.keypress) + # in Windows event inspector shows only EVT_CHAR_HOOK events + self.canvas.Bind(wx.EVT_CHAR_HOOK, self.keypress) self.initialized = 0 self.canvas.Bind(wx.EVT_MOUSEWHEEL, self.wheel) - self.parent = realparent if realparent else parent + self.parent = realparent or parent self.initpos = None - if build_dimensions: - self.build_dimensions = build_dimensions - else: - self.build_dimensions = [200, 200, 100, 0, 0, 0] - self.dist = max(self.build_dimensions[0], self.build_dimensions[1]) + self.build_dimensions = build_dimensions + self.dist = max(self.build_dimensions[:2]) self.basequat = [0, 0, 0, 1] self.mousepos = [0, 0] def inject(self): l = self.parent.model.num_layers_to_draw - filtered = [k for k, v in self.parent.model.layer_idxs_map.iteritems() if v == l] + filtered = [k for k, v in self.parent.model.layer_idxs_map.items() if v == l] if filtered: injector(self.parent.model.gcode, l, filtered[0]) else: @@ -99,7 +107,7 @@ def editlayer(self): l = self.parent.model.num_layers_to_draw - filtered = [k for k, v in self.parent.model.layer_idxs_map.iteritems() if v == l] + filtered = [k for k, v in self.parent.model.layer_idxs_map.items() if v == l] if filtered: injector_edit(self.parent.model.gcode, l, filtered[0]) else: @@ -109,13 +117,13 @@ pass def OnInitGL(self, *args, **kwargs): - super(GcodeViewPanel, self).OnInitGL(*args, **kwargs) - if hasattr(self.parent, "filenames") and self.parent.filenames: - for filename in self.parent.filenames: + super().OnInitGL(*args, **kwargs) + filenames = getattr(self.parent, 'filenames', None) + if filenames: + for filename in filenames: self.parent.load_file(filename) self.parent.autoplate() - if hasattr(self.parent, "loadcb"): - self.parent.loadcb() + getattr(self.parent, 'loadcb', bool)() self.parent.filenames = None def create_objects(self): @@ -125,7 +133,7 @@ obj.model.init() def update_object_resize(self): - '''called when the window recieves only if opengl is initialized''' + '''called when the window receives only if opengl is initialized''' pass def draw_objects(self): @@ -142,9 +150,14 @@ for obj in self.parent.objects: if not obj.model \ - or not obj.model.loaded \ - or not obj.model.initialized: + or not obj.model.loaded: continue + # Skip (comment out) initialized check, which safely causes empty + # model during progressive load. This can cause exceptions/garbage + # render, but seems fine for now + # May need to lock init() and draw_objects() together + # if not obj.model.initialized: + # continue glPushMatrix() glTranslatef(*(obj.offsets)) glRotatef(obj.rot, 0.0, 0.0, 1.0) @@ -175,8 +188,7 @@ return mvmat def double(self, event): - if hasattr(self.parent, "clickcb") and self.parent.clickcb: - self.parent.clickcb(event) + getattr(self.parent, 'clickcb', bool)(event) def move(self, event): """react to mouse actions: @@ -188,22 +200,18 @@ self.canvas.SetFocus() event.Skip() return - if event.Dragging() and event.LeftIsDown(): - self.handle_rotation(event) - elif event.Dragging() and event.RightIsDown(): - self.handle_translation(event) - elif event.LeftUp(): + if event.Dragging(): + if event.LeftIsDown(): + self.handle_rotation(event) + elif event.RightIsDown(): + self.handle_translation(event) + self.Refresh(False) + elif event.LeftUp() or event.RightUp(): self.initpos = None - elif event.RightUp(): - self.initpos = None - else: - event.Skip() - return event.Skip() - wx.CallAfter(self.Refresh) def layerup(self): - if not hasattr(self.parent, "model") or not self.parent.model: + if not getattr(self.parent, 'model', False): return max_layers = self.parent.model.max_layers current_layer = self.parent.model.num_layers_to_draw @@ -216,7 +224,7 @@ wx.CallAfter(self.Refresh) def layerdown(self): - if not hasattr(self.parent, "model") or not self.parent.model: + if not getattr(self.parent, 'model', False): return current_layer = self.parent.model.num_layers_to_draw new_layer = max(1, current_layer - 1) @@ -224,7 +232,14 @@ self.parent.setlayercb(new_layer) wx.CallAfter(self.Refresh) + wheelTimestamp = None def handle_wheel(self, event): + if self.wheelTimestamp == event.Timestamp: + # filter duplicate event delivery in Ubuntu, Debian issue #1110 + return + + self.wheelTimestamp = event.Timestamp + delta = event.GetWheelRotation() factor = 1.05 if event.ControlDown(): @@ -237,7 +252,7 @@ if delta > 0: self.layerup() else: self.layerdown() return - x, y = event.GetPositionTuple() + x, y = event.GetPosition() x, y, _ = self.mouse_to_3d(x, y) if delta > 0: self.zoom(factor, (x, y)) @@ -269,35 +284,34 @@ wx.CallAfter(self.Refresh) def keypress(self, event): - """gets keypress events and moves/rotates acive shape""" - step = 1.1 - if event.ControlDown(): - step = 1.05 - kup = [85, 315] # Up keys - kdo = [68, 317] # Down Keys - kzi = [wx.WXK_PAGEDOWN, 388, 316, 61] # Zoom In Keys - kzo = [wx.WXK_PAGEUP, 390, 314, 45] # Zoom Out Keys - kfit = [70] # Fit to print keys - kshowcurrent = [67] # Show only current layer keys - kreset = [82] # Reset keys + """gets keypress events and moves/rotates active shape""" + if event.HasModifiers(): + # let alt+c bubble up + event.Skip() + return + step = event.ControlDown() and 1.05 or 1.1 key = event.GetKeyCode() - if key in kup: + if key in LAYER_UP_KEYS: self.layerup() - if key in kdo: + return # prevent shifting focus to other controls + elif key in LAYER_DOWN_KEYS: self.layerdown() - x, y, _ = self.mouse_to_3d(self.width / 2, self.height / 2) - if key in kzi: + return + # x, y, _ = self.mouse_to_3d(self.width / 2, self.height / 2) + elif key in ZOOM_IN_KEYS: self.zoom_to_center(step) - if key in kzo: + return + elif key in ZOOM_OUT_KEYS: self.zoom_to_center(1 / step) - if key in kfit: + return + elif key in FIT_KEYS: self.fit() - if key in kshowcurrent: + elif key in CURRENT_LAYER_KEYS: if not self.parent.model or not self.parent.model.loaded: return self.parent.model.only_current = not self.parent.model.only_current wx.CallAfter(self.Refresh) - if key in kreset: + elif key in RESET_KEYS: self.resetview() event.Skip() @@ -307,7 +321,7 @@ self.basequat = [0, 0, 0, 1] wx.CallAfter(self.Refresh) -class GCObject(object): +class GCObject: def __init__(self, model): self.offsets = [0, 0, 0] @@ -317,7 +331,7 @@ self.scale = [1.0, 1.0, 1.0] self.model = model -class GcodeViewLoader(object): +class GcodeViewLoader: path_halfwidth = 0.2 path_halfheight = 0.15 @@ -332,24 +346,25 @@ set_model_colors(self.model, self.root) if gcode is not None: generator = self.model.load_data(gcode) - generator_output = generator.next() + generator_output = next(generator) while generator_output is not None: yield generator_output - generator_output = generator.next() + generator_output = next(generator) wx.CallAfter(self.Refresh) yield None def addfile(self, gcode = None, showall = False): generator = self.addfile_perlayer(gcode, showall) - while generator.next() is not None: + while next(generator) is not None: continue def set_gcview_params(self, path_width, path_height): return set_gcview_params(self, path_width, path_height) -class GcodeViewMainWrapper(GcodeViewLoader): +from printrun.gviz import BaseViz +class GcodeViewMainWrapper(GcodeViewLoader, BaseViz): - def __init__(self, parent, build_dimensions, root, circular, antialias_samples): + def __init__(self, parent, build_dimensions, root, circular, antialias_samples, grid): self.root = root self.glpanel = GcodeViewPanel(parent, realparent = self, build_dimensions = build_dimensions, @@ -361,13 +376,21 @@ self.widget = self.glpanel self.refresh_timer = wx.CallLater(100, self.Refresh) self.p = self # Hack for backwards compatibility with gviz API - self.platform = actors.Platform(build_dimensions, circular = circular) + self.grid = grid + self.platform = actors.Platform(build_dimensions, circular = circular, grid = grid) self.model = None self.objects = [GCObject(self.platform), GCObject(None)] def __getattr__(self, name): return getattr(self.glpanel, name) + def on_settings_change(self, changed_settings): + if self.model: + for s in changed_settings: + if s.name.startswith('gcview_color_'): + self.model.update_colors() + break + def set_current_gline(self, gline): if gline.is_move and gline.gcview_end_vertex is not None \ and self.model and self.model.loaded: @@ -375,11 +398,8 @@ if not self.refresh_timer.IsRunning(): self.refresh_timer.Start() - def recreate_platform(self, build_dimensions, circular): - return recreate_platform(self, build_dimensions, circular) - - def addgcodehighlight(self, *a): - pass + def recreate_platform(self, build_dimensions, circular, grid): + return recreate_platform(self, build_dimensions, circular, grid) def setlayer(self, layer): if layer in self.model.layer_idxs_map: @@ -398,7 +418,8 @@ def __init__(self, parent, ID, title, build_dimensions, objects = None, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE, root = None, circular = False, - antialias_samples = 0): + antialias_samples = 0, + grid = (1, 10)): GvizBaseFrame.__init__(self, parent, ID, title, pos, size, style) self.root = root @@ -408,15 +429,12 @@ self.refresh_timer = wx.CallLater(100, self.Refresh) self.p = self # Hack for backwards compatibility with gviz API self.clonefrom = objects - self.platform = actors.Platform(build_dimensions, circular = circular) - if objects: - self.model = objects[1].model - else: - self.model = None + self.platform = actors.Platform(build_dimensions, circular = circular, grid = grid) + self.model = objects[1].model if objects else None self.objects = [GCObject(self.platform), GCObject(None)] fit_image = wx.Image(imagefile('fit.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap() - self.toolbar.InsertLabelTool(6, 8, " " + _("Fit to plate"), fit_image, + self.toolbar.InsertTool(6, 8, " " + _("Fit to plate"), fit_image, shortHelp = _("Fit to plate [F]"), longHelp = '') self.toolbar.Realize() @@ -441,7 +459,7 @@ def update_status(self, extra): layer = self.model.num_layers_to_draw - filtered = [k for k, v in self.model.layer_idxs_map.iteritems() if v == layer] + filtered = [k for k, v in self.model.layer_idxs_map.items() if v == layer] if filtered: true_layer = filtered[0] z = self.model.gcode.all_layers[true_layer].z @@ -465,8 +483,8 @@ if not self.refresh_timer.IsRunning(): self.refresh_timer.Start() - def recreate_platform(self, build_dimensions, circular): - return recreate_platform(self, build_dimensions, circular) + def recreate_platform(self, build_dimensions, circular, grid): + return recreate_platform(self, build_dimensions, circular, grid) def addfile(self, gcode = None): if self.clonefrom: