diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6de8bb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.bak +*~ +*.pyc diff --git a/README.md b/README.md index 7cc3829..c152109 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,25 @@ + +## FMMT666's Changes: + +* Model can now be moved up and down +* The scene now rotates around the center of the display, not the object. +* Implemented complete mouse and track-/touchpad functionality. Compatible with Mac. + +Future changes [tm] might be available from here: [yagv-ng][1]. + + +Have fun +FMMT666(ASkr) + +--- + # yagv - Yet Another Gcode Viewer, v0.4 A fast 3D Gcode Viewer for Reprap-style 3D printers, in Python and OpenGL (via pyglet) Designed in Ubuntu Linux; Expected to work in any Linux, Windows or OS X + ## Requires: * python 2.x (2.7.3 tested) @@ -36,3 +52,8 @@ yagv [file.gcode] * Some gcodes unsupported, in particular: * G20: Set Units to Inches (usage unknown) * Arcs (G2 & G3 ?) + +--- + +[1]: https://github.com/FMMT666/yagv-ng + diff --git a/yagv b/yagv index 8d96f65..1326f64 100755 --- a/yagv +++ b/yagv @@ -1,6 +1,6 @@ #!/usr/bin/env python -YAGV_VERSION = "0.4" +YAGV_VERSION = "0.5" import pyglet @@ -19,9 +19,31 @@ import time class App: def __init__(self): - self.RX = 0.0 - self.RZ = 0.0 - self.zoom = 1.0 + + # viewport variables + self.RX = 0.0 + self.RZ = 0.0 + self.zoom = 1.0 + self.viewHeight = 0.0 + + # flags and variables for mouse operation "rotate" + self.rotateDrag = False # dragging in progess + self.rotateDragStartRX = None # not necessary, only here for visibility + self.rotateDragStartRZ = None + self.rotateDragStartX = None + self.rotateDragStartY = None + + # flags and variables for mouse operation "layer change" + self.layerDrag = False # dragging in progess + self.layerDragStartLayer = None # not necessary, only here for visibility + self.layerDragStartX = None + self.layerDragStartY = None + + # flags for keyboard modifiers + self.pressedShift = False + self.pressedCtrl = False + self.pressedAlt = False # Command for Mac ('Alt' has wrong key code in OS X pyglet) + def main(self): @@ -209,64 +231,57 @@ class App: t2 = time.time() print "end generateGraphics in %0.3f ms" % ((t2-t1)*1000.0, ) - - - def rotate_drag_start(self, x, y, button, modifiers): - self.rotateDragStartRX = self.RX - self.rotateDragStartRZ = self.RZ - self.rotateDragStartX = x - self.rotateDragStartY = y def rotate_drag_do(self, x, y, dx, dy, buttons, modifiers): - # deltas - deltaX = x - self.rotateDragStartX - deltaY = y - self.rotateDragStartY - # rotate! - self.RZ = self.rotateDragStartRZ + deltaX/5.0 # mouse X bound to model Z - self.RX = self.rotateDragStartRX + deltaY/5.0 # mouse Y bound to model X + if self.rotateDrag: + # deltas + deltaX = x - self.rotateDragStartX + deltaY = y - self.rotateDragStartY + # rotate! + self.RZ = self.rotateDragStartRZ + deltaX/5.0 # mouse X bound to model Z + self.RX = self.rotateDragStartRX + deltaY/5.0 # mouse Y bound to model X + else: + self.rotateDragStartRX = self.RX + self.rotateDragStartRZ = self.RZ + self.rotateDragStartX = x + self.rotateDragStartY = y + self.rotateDrag = True def rotate_drag_end(self, x, y, button, modifiers): - self.rotateDragStartRX = None - self.rotateDragStartRZ = None - self.rotateDragStartX = None - self.rotateDragStartY = None - - - def layer_drag_start(self, x, y, button, modifiers): - self.layerDragStartLayer = self.layerIdx - self.layerDragStartX = x - self.layerDragStartY = y + self.rotateDrag = False + + def layer_next_up(self, delta = 1): + self.layerIdx = max(min(self.layerIdx + delta, self.model.topLayer), 0) + def layer_next_down(self, delta = 1): + self.layerIdx = max(min(self.layerIdx - delta, self.model.topLayer), 0) def layer_drag_do(self, x, y, dx, dy, buttons, modifiers): - # sum x & y - delta = x - self.layerDragStartX + y - self.layerDragStartY - # new theoretical layer - self.layerIdx = int(self.layerDragStartLayer + delta/5) - # clamp layer to 0-max - self.layerIdx = max(min(self.layerIdx, self.model.topLayer), 0) - - self.window.layerLabel.text = "layer %d"%self.layerIdx - - # # clamp layer to 0-max, with origin slip - # if (self.layerIdx < 0): - # self.layerIdx = 0 - # self.layerDragStartLayer = 0 - # self.layerDragStartX = x - # self.layerDragStartY = y - # if (self.layerIdx > len(self.model.layers)-1): - # self.layerIdx = len(self.model.layers)-1 - # self.layerDragStartLayer = len(self.model.layers)-1 - # self.layerDragStartX = x - # self.layerDragStartY = y - + if self.layerDrag: + # sum x & y + delta = x - self.layerDragStartX + y - self.layerDragStartY + # new theoretical layer + self.layerIdx = int(self.layerDragStartLayer + delta/5) + # clamp layer to 0-max + self.layerIdx = max(min(self.layerIdx, self.model.topLayer), 0) + + self.window.layerLabel.text = "layer %d"%self.layerIdx + + else: + self.layerDragStartLayer = self.layerIdx + self.layerDragStartX = x + self.layerDragStartY = y + self.layerDrag = True + def layer_drag_end(self, x, y, button, modifiers): - self.layerDragStartLayer = None - self.layerDragStartX = None - self.layerDragStartY = None + self.layerDrag = False + + + def move_view_z(self, dz): + self.viewHeight += dz class MyWindow(pyglet.window.Window): @@ -288,20 +303,17 @@ class MyWindow(pyglet.window.Window): # help self.helpText = [ - "Ctrl-R to reload file", - "Right-click & drag (any direction) to change layer", - "Scroll to zoom", - "Left-click & drag to rotate view"] + "reload - ctrl + r", + "layer - mouse left + alt/command; scroll + alt", + "zoom - mouse left + ctrl; scroll + ctrl", + "up/down - mouse left + shift; scroll + shift", + "rotate - mouse left"] for txt in self.helpText: - self.blLabels.append( - pyglet.text.Label( txt, - font_size=12) ) + self.blLabels.append( pyglet.text.Label( txt, font_size=10, font_name="courier" ) ) # statistics ## model stats - self.statsLabel = pyglet.text.Label( "", - font_size=12, - anchor_y='top') + self.statsLabel = pyglet.text.Label( "", font_size=12, anchor_y='top' ) filename = os.path.basename(self.app.path) self.statsLabel.text = "%s: %d segments, %d layers."%(filename, len(self.app.model.segments), len(self.app.model.layers)) @@ -331,37 +343,101 @@ class MyWindow(pyglet.window.Window): return pyglet.event.EVENT_HANDLED + def on_mouse_press(self, x, y, button, modifiers): #print "on_mouse_press(x=%d, y=%d, button=%s, modifiers=%s)"%(x, y, button, modifiers) - if button & mouse.LEFT: - self.app.rotate_drag_start(x, y, button, modifiers) - if button & mouse.RIGHT: - self.app.layer_drag_start(x, y, button, modifiers) + # ASkr: removed both of these to have a "mouse click" for other stuff. + #if button & mouse.LEFT: + # self.app.rotate_drag_start(x, y, button, modifiers) + #if button & mouse.RIGHT: + # self.app.layer_drag_start(x, y, button, modifiers) + + pass def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): - #print "on_mouse_drag(x=%d, y=%d, dx=%d, dy=%d, buttons=%s, modifiers=%s)"%(x, y, dx, dy, buttons, modifiers) - if buttons & mouse.LEFT: - self.app.rotate_drag_do(x, y, dx, dy, buttons, modifiers) - - if buttons & mouse.RIGHT: - self.app.layer_drag_do(x, y, dx, dy, buttons, modifiers) + + # ASkr: Suppress unwanted modifiers. Aka.: NUM_LOCK might ruin everything :) + modifiers &= ( key.MOD_SHIFT | key.MOD_CTRL | key.MOD_ALT | key.MOD_COMMAND ) + + # --- NO MODIFIERS + if modifiers == 0: + # left drag rotates view + if buttons & mouse.LEFT: + self.app.rotate_drag_do(x, y, dx, dy, buttons, modifiers) + # right drag moves view up/down + if buttons & mouse.RIGHT: + self.app.move_view_z( -dy / 10.0 ) + + # --- SHIFT + elif modifiers == key.MOD_SHIFT: + # left drag + SHIFT moves view up/down + if buttons & mouse.LEFT: + self.app.move_view_z( -dy / 10.0 ) + + # --- CTRL + elif modifiers == key.MOD_CTRL: + # left drag + CTRL zoom + if buttons & mouse.LEFT: + z = 1.1 if dy>0 else 1/1.1 + self.app.zoom = max(1.0, self.app.zoom * z) + + # --- ALT/COMMAND + # Little annoying under Linux because Alt+drag moves windows (o_O), + # but the mouse's scroll wheel function coveres that... + elif modifiers == key.MOD_ALT or modifiers == key.MOD_COMMAND: + # left drag + ALT selects layer + if buttons & mouse.LEFT: + if dy > 0: + self.app.layer_next_up() + else: + self.app.layer_next_down() def on_mouse_release(self, x, y, button, modifiers): #print "on_mouse_release(x=%d, y=%d, button=%s, modifiers=%s)"%(x, y, button, modifiers) if button & mouse.LEFT: - self.app.rotate_drag_end(x, y, button, modifiers) + if self.app.rotateDrag == True: + self.app.rotate_drag_end(x, y, button, modifiers) if button & mouse.RIGHT: self.app.layer_drag_end(x, y, button, modifiers) + + def on_key_press(self, symbol, modifiers): + if symbol == key.LSHIFT or symbol == key.RSHIFT: + print("SHIFT") + self.app.pressedShift = True; + + if symbol == key.LCTRL or symbol == key.RCTRL: + print("CTRL") + self.app.pressedCtrl = True; + + if symbol == key.LALT or symbol == key.RALT or symbol == key.LCOMMAND or symbol == key.RCOMMAND: + print("ALT") + self.app.pressedAlt = True; + + def on_key_release(self, symbol, modifiers): print "pressed key: %s, mod: %s"%(symbol, modifiers) #print "pressed key: %s, mod: %s"%(pyglet.window.key.R, pyglet.window.key.MOD_CTRL) - if symbol==pyglet.window.key.R and modifiers & pyglet.window.key.MOD_CTRL: + + if symbol == key.LSHIFT or symbol == key.RSHIFT: + # This, of course, won't work if the windows loses the focus... + self.app.pressedShift = False; + + if symbol == key.LCTRL or symbol == key.RCTRL: + # same focus problem like above + self.app.pressedCtrl = False; + + if symbol == key.LALT or symbol == key.RALT or symbol == key.LCOMMAND or symbol == key.RCOMMAND: + # same here + self.app.pressedAlt = False; + + if symbol==key.R and modifiers & key.MOD_CTRL: self.app.reload() + def placeLabels(self, width, height): x = 5 @@ -394,10 +470,34 @@ class MyWindow(pyglet.window.Window): def on_mouse_scroll(self, x, y, dx, dy): - # zoom on mouse scroll delta = dx + dy - z = 1.2 if delta>0 else 1/1.2 - self.app.zoom = max(1.0, self.app.zoom * z) + + # scroll with SHIFT only: translate height + if self.app.pressedShift and not self.app.pressedCtrl and not self.app.pressedAlt: + if delta > 0: + self.app.move_view_z( 2.0 ) + elif delta < 0: + self.app.move_view_z( -2.0 ) + + # scroll with ALT/COMMAND only: select next layer (slow) + elif not self.app.pressedShift and not self.app.pressedCtrl and self.app.pressedAlt: + if delta > 0: + self.app.layer_next_up() + elif delta < 0: + self.app.layer_next_down() + + # scroll with ALT/COMMAND and SHIFT: select next layer (fast) + elif self.app.pressedShift and not self.app.pressedCtrl and self.app.pressedAlt: + if delta > 0: + self.app.layer_next_up( 10 ) + elif delta < 0: + self.app.layer_next_down( 10 ) + + # scroll: zoom in/out + else: + z = 1.2 if delta>0 else 1/1.2 + self.app.zoom = max(1.0, self.app.zoom * z) + def on_draw(self): #print "draw" @@ -413,21 +513,8 @@ class MyWindow(pyglet.window.Window): # setup camera glMatrixMode(GL_MODELVIEW) glLoadIdentity() - gluLookAt(0,1.5,2,0,0,0,0,1,0) - - # enable alpha blending - glEnable(GL_BLEND) - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - - # rotate axes to match reprap style - glRotated(-90, 1,0,0) - # user rotate model - glRotated(-self.app.RX, 1,0,0) - glRotated(self.app.RZ, 0,0,1) - - # Todo check this - glTranslated(0,0,-0.5) - + gluLookAt( 0, 1.5, 2, 0 ,0 ,0 , 0 ,1 ,0 ) + # fit & user zoom model max_width = max( self.app.model.bbox.dx(), @@ -436,10 +523,27 @@ class MyWindow(pyglet.window.Window): ) scale = self.app.zoom / max_width glScaled(scale, scale, scale) + + # adjust camera height + gluLookAt( 0, self.app.viewHeight, 2, 0 ,self.app.viewHeight ,0 , 0 ,1 , 0 ) - glTranslated(-self.app.model.bbox.cx(), -self.app.model.bbox.cy(), -self.app.model.bbox.cz()) + # enable alpha blending + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + + # rotate axes to match reprap style + glRotated(-90, 1,0,0) + + glTranslated(0,0,self.app.viewHeight) + # user rotate model + glRotated(-self.app.RX, 1,0,0) + glRotated(self.app.RZ, 0,0,1) + + # modify the height + glTranslated(0,0,-self.app.viewHeight) + glTranslated(-self.app.model.bbox.cx(), -self.app.model.bbox.cy(), -self.app.model.bbox.cz()) # draw axes glBegin(GL_LINES)