class WindowGL(QWindow): def __init__(self): super().__init__() fmt = QSurfaceFormat() fmt.setVersion(2, 0) fmt.setProfile(QSurfaceFormat.CompatibilityProfile) fmt.setStereo(False) fmt.setSwapBehavior(QSurfaceFormat.DoubleBuffer) self.setSurfaceType(QWindow.OpenGLSurface) self.context = QOpenGLContext() self.context.setFormat(fmt) if not self.context.create(): raise Exception("Unable to create context") self.create() self.setTitle("OpenGL") def exposeEvent(self, e): e.accept() if self.isExposed() and self.isVisible(): self.update() def makeCurrent(self): self.context.makeCurrent(self) def swapBuffers(self): self.context.swapBuffers(self) def resize(self, w, h): super().resize(w, h) self.makeCurrent() GL.glViewport(0, 0, w, h) def update(self): tri_size = 0.7 self.makeCurrent() GL.glClearColor(0, 0, 0, 0) GL.glClearDepth(1) GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) GL.glBegin(GL.GL_TRIANGLES) GL.glVertex2f(0, tri_size) GL.glColor3f(1, 0, 0) GL.glVertex2f(-tri_size, -tri_size) GL.glColor3f(0, 1, 0) GL.glVertex2f(tri_size, -tri_size) GL.glColor3f(0, 0, 1) GL.glEnd() GL.glFlush() self.swapBuffers()
class OpenGLWindow(QWindow): def __init__(self, parent=None): super(OpenGLWindow, self).__init__(parent) self.m_update_pending = False self.m_animating = False self.m_context = None self.m_gl = None self.setSurfaceType(QWindow.OpenGLSurface) def initialize(self): pass def setAnimating(self, animating): self.m_animating = animating if animating: self.renderLater() def renderLater(self): if not self.m_update_pending: self.m_update_pending = True QGuiApplication.postEvent(self, QEvent(QEvent.UpdateRequest)) def renderNow(self): if not self.isExposed(): return self.m_update_pending = False needsInitialize = False if self.m_context is None: self.m_context = QOpenGLContext(self) self.m_context.setFormat(self.requestedFormat()) self.m_context.create() needsInitialize = True self.m_context.makeCurrent(self) if needsInitialize: version_profile = QOpenGLVersionProfile() version_profile.setVersion(2, 0) self.m_gl = self.m_context.versionFunctions(version_profile) self.m_gl.initializeOpenGLFunctions() self.initialize() self.render(self.m_gl) self.m_context.swapBuffers(self) if self.m_animating: self.renderLater() def event(self, event): if event.type() == QEvent.UpdateRequest: self.renderNow() return True return super(OpenGLWindow, self).event(event) def exposeEvent(self, event): self.renderNow() def resizeEvent(self, event): self.renderNow()
class Window(QWindow): """ A QWindow for rendering of WindowProxy Attributes ---------- _context : QOpenGLContext Context used to render the window contents _window_proxy_id : int The WindowProxy responsible for drawing _saved_size : QSize If a resize event occurs before WindowProxy is started, then the size is saved here so that it can be set when WindowProxy is started _proxy_started_once : bool True after the first time that _of has been started """ def __init__(self, nrow=1, ncol=1, id=0): """ Create an instance of OFWindow Attributes ---------- nrow : int, optional Number of rows in WindowProxy grid, default = 1 ncol : int, optional Number of columns in WindowProxy grid, default = 1 id : int, optional Identifier for the WindowProxy (each WindowProxy should have a unique identifier), default = 0 """ super().__init__() self._context = None self._proxy_started = False self._saved_size = None self.setSurfaceType(QWindow.OpenGLSurface) self._window_proxy_id = id ofwin_createproxy(0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT, nrow, ncol, True, self._window_proxy_id, False) ofwin_setmakecurrentfunction(self.make_current) ofwin_setupdatecontextfunction(self.make_current) ofwin_setswapbuffersfunction(self.swap_buffers) def exposeEvent(self, event): """ Overrides QWindow.exposeEvent() """ if not self._proxy_started: self._proxy_started = True ofwin_activate(self._window_proxy_id) ofwin_start() if self._saved_size is not None: ofwin_resizewindow(0, 0, self._saved_size.width(), self._saved_size.height()) self._saved_size = None def hideEvent(self, event): """ Overrides QWindow.exposeEvent() """ ofwin_activate(self._window_proxy_id) ofwin_signalstop() while ofwin_isrunning() == 1: QCoreApplication.processEvents(QEventLoop.AllEvents, 100) self._proxy_started = False def resizeEvent(self, event): """ Overrides QWindow.resizeEvent() """ ofwin_activate(self._window_proxy_id) if ofwin_isrunning() == 1: ofwin_resizewindow(0, 0, event.size().width(), event.size().height()) else: self._saved_size = event.size() def mousePressEvent(self, event): """ Overrides QWindow.mousePressEvent() """ ofwin_activate(self._window_proxy_id) if ofwin_isrunning() == 1: button = Window._map_qt_button_to_of_button(event.button()) if button != 0: ofwin_buttonpress(event.x(), event.y(), button) def mouseReleaseEvent(self, event): """ Overrides QWindow.mouseReleaseEvent() """ ofwin_activate(self._window_proxy_id) if ofwin_isrunning() == 1: button = Window._map_qt_button_to_of_button(event.button()) if button != 0: ofwin_buttonrelease(event.x(), event.y(), button) def mouseMoveEvent(self, event): """ Overrides QWindow.mouseMoveEvent() """ ofwin_activate(self._window_proxy_id) if ofwin_isrunning() == 1: ofwin_mousemotion(event.x(), event.y()) def keyPressEvent(self, event): """ Overrides QWindow.keyPressEvent() """ ofwin_activate(self._window_proxy_id) if ofwin_isrunning() == 1: key = Window._map_qt_key_event_to_osg_key(event) ofwin_keypress(key) # TODO call glGetError() to print any errors that may have occurred def make_current(self): """ Makes _context current for the surface of this window Returns ------- bool True if successful False if an error occurs """ success = False if self._context is None: self._context = QOpenGLContext() self._context.create() success = self._context.makeCurrent(self) if success: # self.initializeOpenGLFunctions() self._context.doneCurrent() else: return success if self._context is not None: success = self._context.makeCurrent(self) # err = glGetError() return success def swap_buffers(self): """ Swaps the buffer from _context to the surface of this window """ if self._context is not None: self._context.swapBuffers(self) @staticmethod def _map_qt_button_to_of_button(qt_button): """ Maps a Qt.MouseButton enumeration to an int for OpenFrames Parameters ---------- qt_button : Qt.MouseButton The button to map Returns ------- int The corresponding button for OpenFrames """ if qt_button == Qt.LeftButton: return 1 elif qt_button == Qt.RightButton: return 3 elif qt_button == Qt.MiddleButton: return 2 elif qt_button == Qt.BackButton: return 6 elif qt_button == Qt.ForwardButton: return 7 else: return 0 @staticmethod def _map_qt_key_event_to_osg_key(event): """ Maps a QKeyEvent to an int for OpenFrames Parameters ---------- event : PyQt5.QtGui.QKeyEvent.QKeyEvent The key event to map Returns ------- int The corresponding key code for OpenFrames """ if Qt.Key_A <= event.key() <= Qt.Key_Z: if event.modifiers() & Qt.ShiftModifier: key = event.key() else: key = event.key() + 0x20 else: key = event.key() return key
class OpenGLWindow(QWindow): def __init__(self, parent=None): super(OpenGLWindow, self).__init__(parent) self.m_update_pending = False self.m_animating = False self.m_context = None self.m_gl = None self.setSurfaceType(QWindow.OpenGLSurface) def initialize(self): pass def setAnimating(self, animating): self.m_animating = animating if animating: self.renderLater() def renderLater(self): if not self.m_update_pending: self.m_update_pending = True QGuiApplication.postEvent(self, QEvent(QEvent.UpdateRequest)) def renderNow(self): if not self.isExposed(): return self.m_update_pending = False needsInitialize = False if self.m_context is None: self.m_context = QOpenGLContext(self) self.m_context.setFormat(self.requestedFormat()) self.m_context.create() needsInitialize = True self.m_context.makeCurrent(self) if needsInitialize: self.m_gl = self.m_context.versionFunctions() self.m_gl.initializeOpenGLFunctions() self.initialize() self.render(self.m_gl) self.m_context.swapBuffers(self) if self.m_animating: self.renderLater() def event(self, event): if event.type() == QEvent.UpdateRequest: self.renderNow() return True return super(OpenGLWindow, self).event(event) def exposeEvent(self, event): self.renderNow() def resizeEvent(self, event): self.renderNow()
class OpenGLWindow(QWindow): def __init__(self, parent=None): super(OpenGLWindow, self).__init__(parent) self.m_update_pending = False self.m_animating = False self.m_context = None self.m_device = None self.m_gl = None self.logger = None self.setSurfaceType(QWindow.OpenGLSurface) def initialize(self, gl): pass def setAnimating(self, animating): self.m_animating = animating if animating: self.renderLater() def renderLater(self): if not self.m_update_pending: self.m_update_pending = True QGuiApplication.postEvent(self, QEvent(QEvent.UpdateRequest)) def paint(self, painter): pass def render(self, gl): pass def addGlFunctuins(self, GL, functions): for function, arguments in functions.items(): GL[function].restype = None GL[function].argtypes = arguments setattr(self.m_gl, function, GL[function]) @exitOnKeyboardInterrupt def renderNow(self): if not self.isExposed(): return self.m_update_pending = False needsInitialize = False if self.m_context is None: self.m_context = QOpenGLContext(self) self.m_context.setFormat(self.requestedFormat()) self.m_context.create() needsInitialize = True self.m_context.makeCurrent(self) if needsInitialize: # Sorry, no support for higher versions for now. profile = QOpenGLVersionProfile() profile.setVersion(2, 0) self.m_gl = self.m_context.versionFunctions(profile) self.m_gl.initializeOpenGLFunctions() #print(self.m_context.hasExtension('GL_EXT_framebuffer_object')) #print(self.m_context.hasExtension('GL_ARB_texture_float')) #print(*sorted(self.m_context.extensions()), sep='\n') # Small hack. Guess noone mind? import ctypes import ctypes.util GL = ctypes.CDLL(ctypes.util.find_library('GL')) self.addGlFunctuins(GL, { 'glFramebufferTexture2D': (ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_int) }) self.logger = QOpenGLDebugLogger() self.logger.initialize() self.logger.loggedMessages() self.logger.messageLogged.connect(self.handleLoggedMassage) self.logger.startLogging() self.initialize(self.m_gl) if not self.m_device: self.m_device = QOpenGLPaintDevice() self.m_gl.glClear(self.m_gl.GL_COLOR_BUFFER_BIT | self.m_gl.GL_DEPTH_BUFFER_BIT); self.m_device.setSize(self.size()) painter = QPainter(self.m_device) painter.beginNativePainting() self.render(self.m_gl) painter.endNativePainting() self.paint(painter) self.m_context.swapBuffers(self) if self.m_animating: self.renderLater() def handleLoggedMassage(self, message): # This three really annoyng and brings no useful info =\ if not (message.message().find('Use glDrawRangeElements() to avoid this.') > -1 or message.message().find('CPU mapping a busy miptree') > -1 or message.message().find('Flushing before mapping a referenced bo.') > -1 ): print(message.message().strip()) def event(self, event): if event.type() == QEvent.UpdateRequest: self.renderNow() return True return super(OpenGLWindow, self).event(event) def exposeEvent(self, event): self.renderNow() def resizeEvent(self, event): self.renderNow()
class ViewerWindow(QWindow): instructions = """ --Key controls 0-9 - toggle data layers r - reset view parameters c - clip view t - view from top l - light/dark background = - increase point size - - decrease point size --Mouse controls drag shift + move - translate dataset scroll - scale dataset shift + scroll - change field of view ctrl + scroll - move far clipping plane alt + scroll - move near clipping plane ctrl + alt + scroll - move far and near clipping plane simultaniously """ def __init__(self, parent=None, **kwargs): super(ViewerWindow, self).__init__(parent) self.setSurfaceType(QWindow.OpenGLSurface) format = QSurfaceFormat() format.setVersion(3, 3) format.setProfile(QSurfaceFormat.CoreProfile) format.setStereo(False) format.setSwapBehavior(QSurfaceFormat.DoubleBuffer) format.setDepthBufferSize(24) format.setSamples(16) self.context = QOpenGLContext(self) self.context.setFormat(format) if not self.context.create(): raise Exception('self.context.create() failed') self.create() size = 720, 720 self.resize(*size) self.context.makeCurrent(self) self.hud_program = CrossHairProgram() self.default_view = np.eye(4, dtype=np.float32) self.view = self.default_view self.model = np.eye(4, dtype=np.float32) self.projection = np.eye(4, dtype=np.float32) self.layer_manager = LayerManager() self.visibility_toggle_listeners = [] self.multiview = True self.rotation = q.quaternion() self.scale = 0.6 self.translation = np.zeros(3) self.radius = 0.5 * min(*size) self.fov = 5. self.camera_position = -12. self.near_clip = .1 if 'near_clip' in kwargs: self.near_clip = kwargs['near_clip'] self.far_clip = 100. if 'far_clip' in kwargs: self.far_clip = kwargs['far_clip'] self.projection_mode = 'perspective' # 'orthographic' self.size = size self.bg_white = False self.viewpoint_dict = {} print((self.instructions)) def add_layer(self, layer): self.layer_manager.add_layer(layer) return layer # keeping this compatibility with older scripts def add_data_source(self, name, opts, points, normals=None, radii=None, intensity=None, category=None, zrange=None, **kwargs): if len(self.layer_manager.layers) == 0: self.layer_manager.add_layer(Layer(name='Default')) self.layer_manager.layers['Default'].add_data_source(name, opts, points, normals=normals, radii=radii, intensity=intensity, category=category, zrange=zrange, **kwargs) def add_data_source_line(self, name, coords_start, coords_end, **args): if len(self.layer_manager.layers) == 0: self.layer_manager.add_layer(Layer(name='Default')) self.layer_manager.layers['Default'].add_data_source_line(name, coords_start, coords_end, **args) def add_data_source_triangle(self, name, coords, normals, **args): if len(self.layer_manager.layers) == 0: self.layer_manager.add_layer(Layer(name='Default')) self.layer_manager.layers['Default'].add_data_source_triangle(name, coords, normals, **args) def run(self): self.initialize() self.show() def set_layer_visibility(self, name, is_visible): for listener in self.visibility_toggle_listeners: listener(name, is_visible) def center_view(self, center=None): if center is None: center = self.layer_manager.first.get_center() data_range = self.layer_manager.first.data_range self.data_width = data_range[0] self.data_height = data_range[2] self.translation = np.zeros(3) self.model = np.eye(4, dtype=np.float32) translate(self.model, -center[0], -center[1], -center[2]) for program in self.layer_manager.programs(): program.setUniform('u_model', self.model) self.update_view_matrix() def initialize(self): self.context.makeCurrent(self) gl.glDepthMask(gl.GL_TRUE) gl.glEnable(gl.GL_DEPTH_TEST) gl.glDepthFunc(gl.GL_LESS) self.set_bg() view_width, view_height = [x/self.radius for x in self.size] self.center_view() for program in self.layer_manager.programs(): program.setUniform('u_model', self.model) program.setUniform('u_view', self.view) program.setUniform('u_projection', self.projection) self.modelscale = .6* 2*min(2.*view_width/self.data_width, 2.*view_height/self.data_height) self.scale = self.modelscale self.update_view_matrix() self.last_mouse_pos = 0,0 self.on_resize(*self.size) def render(self): if not self.isExposed(): return self.context.makeCurrent(self) bits = 0 bits |= gl.GL_COLOR_BUFFER_BIT bits |= gl.GL_DEPTH_BUFFER_BIT bits |= gl.GL_STENCIL_BUFFER_BIT gl.glClear(bits) gl.glEnable(gl.GL_PROGRAM_POINT_SIZE) for program in self.layer_manager.programs(): if program.do_blending: if self.bg_white: gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_SRC_ALPHA) else: gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_ONE, gl.GL_SRC_ALPHA) else: gl.glDisable(gl.GL_BLEND) self.layer_manager.draw() if self.hud_program.is_visible: self.hud_program.draw() self.context.swapBuffers(self) def event(self, event): # print event.type() if event.type() == QEvent.UpdateRequest: self.render() return True return super(ViewerWindow, self).event(event) def exposeEvent(self, event): self.render() def resizeEvent(self, event): size = event.size() self.on_resize(size.width(), size.height()) self.render() def update_view_matrix(self): self.view = np.eye(4, dtype=np.float32) translate(self.view, self.translation[0], self.translation[1], self.translation[2] ) scale(self.view, self.scale, self.scale, self.scale) self.view = self.view.dot( np.array(q.matrix(self.rotation), dtype=np.float32) ) # translate(self.view, -self.translation[0], -self.translation[1], -self.translation[2] ) translate(self.view, 0,0, self.camera_position) for program in self.layer_manager.programs(): program.setUniform('u_view', self.view) if program.draw_type == 'points': program.setUniform('u_model_scale', self.scale) def update_projection_matrix(self): view_width, view_height = [x/self.radius for x in self.size] if self.projection_mode == 'orthographic': self.projection = ortho(-view_width, view_width, -view_height, view_height, self.near_clip, self.far_clip) elif self.projection_mode == 'perspective': self.projection = perspective(self.fov, view_width / float(view_height), self.near_clip, self.far_clip) for program in self.layer_manager.programs(): program.setUniform('u_projection', self.projection) def screen2view(self, x,y): width, height = self.size # print (x-width/2.)/self.radius, ((height-y)-height/2.)/self.radius return (x-width/2.)/self.radius, ((height-y)-height/2.)/self.radius def set_bg(self): gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) if self.bg_white: gl.glClearColor(0.95,0.95,0.95,1) # gloo.set_state('translucent', clear_color=np.array([1,1,1,1]) ) else: gl.glClearColor(0.05,0.05,0.05,1) # gloo.set_state('translucent', clear_color=np.array([0.15,0.15,0.15,1]) ) def keyPressEvent(self, event): key = event.key() repeat = event.isAutoRepeat() if key == Qt.Key_R: self.view = np.eye(4, dtype=np.float32) self.rotation = q.quaternion() self.scale = self.modelscale self.camera_position = -12. self.near_clip = 2. self.far_clip = 100. self.center_view() self.update_projection_matrix() elif key == Qt.Key_Minus: for layer in self.layer_manager: if layer.is_visible: for program in layer: if program.draw_type == 'points': if (program.is_visible and self.multiview) or not self.multiview: program.setUniform('u_point_size', program.uniforms['u_point_size']/1.2) elif key == Qt.Key_Equal: for layer in self.layer_manager: if layer.is_visible: for program in layer: if program.draw_type == 'points': if (program.is_visible and self.multiview) or not self.multiview: program.setUniform('u_point_size', program.uniforms['u_point_size']*1.2) elif key == Qt.Key_B: for program in self.layer_manager.programs(): if program.is_visible and program.draw_type == 'points': program.do_blending = not program.do_blending elif key == Qt.Key_C: self.near_clip = -self.camera_position - 0.1 self.far_clip = -self.camera_position + 0.1 self.update_projection_matrix() elif key == Qt.Key_T: self.rotation = q.quaternion() self.update_view_matrix() elif key == Qt.Key_P: if self.projection_mode == 'perspective': self.projection_mode = 'orthographic' else: self.projection_mode = 'perspective' self.update_projection_matrix() elif key == Qt.Key_L: self.bg_white = not self.bg_white self.set_bg() elif Qt.Key_0 <= key <= Qt.Key_9: i = int(chr(key))-1 layers = [item for item in self.layer_manager.layers.items()] if i < len(layers): name, layer = layers[i] self.set_layer_visibility(name,layer.toggle()) # if self.multiview: # self.set_layer_visibility(list(self.data_programs.keys())[i], not list(self.data_programs.values())[i].is_visible) # else: # for pi, prog in enumerate(self.data_programs.values()): # prog.is_visible = False # if pi == i: # prog.is_visible = True self.update() def on_resize(self, size_x, size_y): gl.glViewport(int(0), int(0), int(size_x), int(size_y)) self.radius = 0.5 * min(size_x, size_y) self.size = size_x, size_y self.update_projection_matrix() def wheelEvent(self, event): # def on_mouse_wheel(self, window, offset_x, offset_y): ticks = float(event.angleDelta().y()+event.angleDelta().x())/50 modifiers = event.modifiers() if modifiers == Qt.ControlModifier | Qt.AltModifier: # if modifiers == Qt.ShiftModifier: ticks /= 30 self.near_clip -= ticks self.far_clip -= ticks self.update_projection_matrix() elif modifiers == Qt.AltModifier: # if modifiers == Qt.ShiftModifier: ticks /= 30 new = max(0.1,self.near_clip - ticks) if new <= self.far_clip: self.near_clip = new self.update_projection_matrix() elif modifiers == Qt.ControlModifier: # if modifiers == Qt.ShiftModifier: ticks /= 30 new = min(1000,self.far_clip - ticks) if new >= self.near_clip: self.far_clip = new self.update_projection_matrix() elif modifiers == Qt.ShiftModifier: if self.projection_mode == 'perspective': old_fov = self.fov # do `dolly zooming` so that world appears at same size after canging fov self.fov = max(5.,self.fov + ticks) self.fov = min(120.,self.fov) self.camera_position = self.camera_position * (math.tan(math.radians(old_fov)/2.)) / (math.tan(math.radians(self.fov)/2.)) self.update_projection_matrix() self.update_view_matrix() elif modifiers == Qt.MetaModifier: self.camera_position += ticks/10 self.update_view_matrix() else: self.scale *= ticks/10 + 1. # self.camera_position += ticks/10 self.update_view_matrix() self.update() def mouseMoveEvent(self, event): modifiers = event.modifiers() buttons = event.buttons() pos_x, pos_y = event.x(), event.y() if Qt.ShiftModifier == modifiers: x0,y0 = self.last_mouse_pos x1,y1 = pos_x, pos_y dx, dy = (x1-x0), (y1-y0) #scale to zero plane in projection frustrum if self.projection_mode == 'perspective': scale = -self.camera_position * math.tan(math.radians(self.fov/2.)) dx, dy = scale*dx, scale*dy #multiply with inverse view matrix and apply translation in world coordinates self.translation += np.array([dx/self.radius, -dy/self.radius, 0., 0.]).dot( np.linalg.inv(self.view)) [:3] elif self.projection_mode == 'orthographic': # this is not fully correct self.translation += self.modelscale * np.array([dx, -dy, 0., 0.]).dot( np.linalg.inv(self.view)) [:3] self.hud_program.is_visible = True elif Qt.LeftButton == buttons: x0,y0 = self.screen2view(*self.last_mouse_pos) x1,y1 = self.screen2view(pos_x, pos_y) v0 = q.arcball(x0, y0) v1 = q.arcball(x1, y1) self.rotation = q.product(v1, v0, self.rotation) self.hud_program.is_visible = True else: self.hud_program.is_visible = False self.update_view_matrix() self.update() self.last_mouse_pos = pos_x, pos_y def update(self): self.render()
class ViewerWindow(QWindow): instructions = """ --Key controls 0-9 - toggle data layers r - reset view parameters c - clip view t - view from top l - light/dark background = - increase point size - - decrease point size --Mouse controls drag shift + move - translate dataset scroll - scale dataset shift + scroll - change field of view ctrl + scroll - move far clipping plane alt + scroll - move near clipping plane ctrl + alt + scroll - move far and near clipping plane simultaniously """ def __init__(self, parent=None, **kwargs): super(ViewerWindow, self).__init__(parent) self.setSurfaceType(QWindow.OpenGLSurface) format = QSurfaceFormat() format.setVersion(3, 3) format.setProfile(QSurfaceFormat.CoreProfile) format.setStereo(False) format.setSwapBehavior(QSurfaceFormat.DoubleBuffer) format.setDepthBufferSize(24) format.setSamples(16) self.context = QOpenGLContext(self) self.context.setFormat(format) if not self.context.create(): raise Exception('self.context.create() failed') self.create() size = 720, 720 self.resize(*size) self.context.makeCurrent(self) self.hud_program = CrossHairProgram() self.default_view = np.eye(4, dtype=np.float32) self.view = self.default_view self.model = np.eye(4, dtype=np.float32) self.projection = np.eye(4, dtype=np.float32) self.layer_manager = LayerManager() self.visibility_toggle_listeners = [] self.multiview = True self.rotation = q.quaternion() self.scale = 0.6 self.translation = np.zeros(3) self.radius = 0.5 * min(*size) self.fov = 5. self.camera_position = -12. self.near_clip = .1 if 'near_clip' in kwargs: self.near_clip = kwargs['near_clip'] self.far_clip = 100. if 'far_clip' in kwargs: self.far_clip = kwargs['far_clip'] self.projection_mode = 'perspective' # 'orthographic' self.size = size self.bg_white = False self.viewpoint_dict = {} print((self.instructions)) def add_layer(self, layer): self.layer_manager.add_layer(layer) return layer # keeping this compatibility with older scripts def add_data_source(self, name, opts, points, normals=None, radii=None, intensity=None, category=None, zrange=None, **kwargs): if len(self.layer_manager.layers) == 0: self.layer_manager.add_layer(Layer(name='Default')) self.layer_manager.layers['Default'].add_data_source( name, opts, points, normals=normals, radii=radii, intensity=intensity, category=category, zrange=zrange, **kwargs) def add_data_source_line(self, name, coords_start, coords_end, **args): if len(self.layer_manager.layers) == 0: self.layer_manager.add_layer(Layer(name='Default')) self.layer_manager.layers['Default'].add_data_source_line( name, coords_start, coords_end, **args) def add_data_source_triangle(self, name, coords, normals, **args): if len(self.layer_manager.layers) == 0: self.layer_manager.add_layer(Layer(name='Default')) self.layer_manager.layers['Default'].add_data_source_triangle( name, coords, normals, **args) def run(self): self.initialize() self.show() def set_layer_visibility(self, name, is_visible): for listener in self.visibility_toggle_listeners: listener(name, is_visible) def center_view(self, center=None): if center is None: center = self.layer_manager.first.get_center() data_range = self.layer_manager.first.data_range self.data_width = data_range[0] self.data_height = data_range[2] self.translation = np.zeros(3) self.model = np.eye(4, dtype=np.float32) translate(self.model, -center[0], -center[1], -center[2]) for program in self.layer_manager.programs(): program.setUniform('u_model', self.model) self.update_view_matrix() def initialize(self): self.context.makeCurrent(self) gl.glDepthMask(gl.GL_TRUE) gl.glEnable(gl.GL_DEPTH_TEST) gl.glDepthFunc(gl.GL_LESS) self.set_bg() view_width, view_height = [x / self.radius for x in self.size] self.center_view() for program in self.layer_manager.programs(): program.setUniform('u_model', self.model) program.setUniform('u_view', self.view) program.setUniform('u_projection', self.projection) self.modelscale = .6 * 2 * min(2. * view_width / self.data_width, 2. * view_height / self.data_height) self.scale = self.modelscale self.update_view_matrix() self.last_mouse_pos = 0, 0 self.on_resize(*self.size) def render(self): if not self.isExposed(): return self.context.makeCurrent(self) bits = 0 bits |= gl.GL_COLOR_BUFFER_BIT bits |= gl.GL_DEPTH_BUFFER_BIT bits |= gl.GL_STENCIL_BUFFER_BIT gl.glClear(bits) gl.glEnable(gl.GL_PROGRAM_POINT_SIZE) for program in self.layer_manager.programs(): if program.do_blending: if self.bg_white: gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_SRC_ALPHA) else: gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_ONE, gl.GL_SRC_ALPHA) else: gl.glDisable(gl.GL_BLEND) self.layer_manager.draw() if self.hud_program.is_visible: self.hud_program.draw() self.context.swapBuffers(self) def event(self, event): # print event.type() if event.type() == QEvent.UpdateRequest: self.render() return True return super(ViewerWindow, self).event(event) def exposeEvent(self, event): self.render() def resizeEvent(self, event): size = event.size() self.on_resize(size.width(), size.height()) self.render() def update_view_matrix(self): self.view = np.eye(4, dtype=np.float32) translate(self.view, self.translation[0], self.translation[1], self.translation[2]) scale(self.view, self.scale, self.scale, self.scale) self.view = self.view.dot( np.array(q.matrix(self.rotation), dtype=np.float32)) # translate(self.view, -self.translation[0], -self.translation[1], -self.translation[2] ) translate(self.view, 0, 0, self.camera_position) for program in self.layer_manager.programs(): program.setUniform('u_view', self.view) if program.draw_type == 'points': program.setUniform('u_model_scale', self.scale) def update_projection_matrix(self): view_width, view_height = [x / self.radius for x in self.size] if self.projection_mode == 'orthographic': self.projection = ortho(-view_width, view_width, -view_height, view_height, self.near_clip, self.far_clip) elif self.projection_mode == 'perspective': self.projection = perspective(self.fov, view_width / float(view_height), self.near_clip, self.far_clip) for program in self.layer_manager.programs(): program.setUniform('u_projection', self.projection) def screen2view(self, x, y): width, height = self.size # print (x-width/2.)/self.radius, ((height-y)-height/2.)/self.radius return (x - width / 2.) / self.radius, ( (height - y) - height / 2.) / self.radius def set_bg(self): gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) if self.bg_white: gl.glClearColor(0.95, 0.95, 0.95, 1) # gloo.set_state('translucent', clear_color=np.array([1,1,1,1]) ) else: gl.glClearColor(0.05, 0.05, 0.05, 1) # gloo.set_state('translucent', clear_color=np.array([0.15,0.15,0.15,1]) ) def keyPressEvent(self, event): key = event.key() repeat = event.isAutoRepeat() if key == Qt.Key_R: self.view = np.eye(4, dtype=np.float32) self.rotation = q.quaternion() self.scale = self.modelscale self.camera_position = -12. self.near_clip = 2. self.far_clip = 100. self.center_view() self.update_projection_matrix() elif key == Qt.Key_Minus: for layer in self.layer_manager: if layer.is_visible: for program in layer: if program.draw_type == 'points': if (program.is_visible and self.multiview) or not self.multiview: program.setUniform( 'u_point_size', program.uniforms['u_point_size'] / 1.2) elif key == Qt.Key_Equal: for layer in self.layer_manager: if layer.is_visible: for program in layer: if program.draw_type == 'points': if (program.is_visible and self.multiview) or not self.multiview: program.setUniform( 'u_point_size', program.uniforms['u_point_size'] * 1.2) elif key == Qt.Key_B: for program in self.layer_manager.programs(): if program.is_visible and program.draw_type == 'points': program.do_blending = not program.do_blending elif key == Qt.Key_C: self.near_clip = -self.camera_position - 0.1 self.far_clip = -self.camera_position + 0.1 self.update_projection_matrix() elif key == Qt.Key_T: self.rotation = q.quaternion() self.update_view_matrix() elif key == Qt.Key_P: if self.projection_mode == 'perspective': self.projection_mode = 'orthographic' else: self.projection_mode = 'perspective' self.update_projection_matrix() elif key == Qt.Key_L: self.bg_white = not self.bg_white self.set_bg() elif Qt.Key_0 <= key <= Qt.Key_9: i = int(chr(key)) - 1 layers = [item for item in self.layer_manager.layers.items()] if i < len(layers): name, layer = layers[i] self.set_layer_visibility(name, layer.toggle()) # if self.multiview: # self.set_layer_visibility(list(self.data_programs.keys())[i], not list(self.data_programs.values())[i].is_visible) # else: # for pi, prog in enumerate(self.data_programs.values()): # prog.is_visible = False # if pi == i: # prog.is_visible = True self.update() def on_resize(self, size_x, size_y): gl.glViewport(int(0), int(0), int(size_x), int(size_y)) self.radius = 0.5 * min(size_x, size_y) self.size = size_x, size_y self.update_projection_matrix() def wheelEvent(self, event): # def on_mouse_wheel(self, window, offset_x, offset_y): ticks = float(event.angleDelta().y() + event.angleDelta().x()) / 50 modifiers = event.modifiers() if modifiers == Qt.ControlModifier | Qt.AltModifier: # if modifiers == Qt.ShiftModifier: ticks /= 30 self.near_clip -= ticks self.far_clip -= ticks self.update_projection_matrix() elif modifiers == Qt.AltModifier: # if modifiers == Qt.ShiftModifier: ticks /= 30 new = max(0.1, self.near_clip - ticks) if new <= self.far_clip: self.near_clip = new self.update_projection_matrix() elif modifiers == Qt.ControlModifier: # if modifiers == Qt.ShiftModifier: ticks /= 30 new = min(1000, self.far_clip - ticks) if new >= self.near_clip: self.far_clip = new self.update_projection_matrix() elif modifiers == Qt.ShiftModifier: if self.projection_mode == 'perspective': old_fov = self.fov # do `dolly zooming` so that world appears at same size after canging fov self.fov = max(5., self.fov + ticks) self.fov = min(120., self.fov) self.camera_position = self.camera_position * (math.tan( math.radians(old_fov) / 2.)) / (math.tan( math.radians(self.fov) / 2.)) self.update_projection_matrix() self.update_view_matrix() elif modifiers == Qt.MetaModifier: self.camera_position += ticks / 10 self.update_view_matrix() else: self.scale *= ticks / 10 + 1. # self.camera_position += ticks/10 self.update_view_matrix() self.update() def mouseMoveEvent(self, event): modifiers = event.modifiers() buttons = event.buttons() pos_x, pos_y = event.x(), event.y() if Qt.ShiftModifier == modifiers: x0, y0 = self.last_mouse_pos x1, y1 = pos_x, pos_y dx, dy = (x1 - x0), (y1 - y0) #scale to zero plane in projection frustrum if self.projection_mode == 'perspective': scale = -self.camera_position * math.tan( math.radians(self.fov / 2.)) dx, dy = scale * dx, scale * dy #multiply with inverse view matrix and apply translation in world coordinates self.translation += np.array( [dx / self.radius, -dy / self.radius, 0., 0.]).dot(np.linalg.inv(self.view))[:3] elif self.projection_mode == 'orthographic': # this is not fully correct self.translation += self.modelscale * np.array( [dx, -dy, 0., 0.]).dot(np.linalg.inv(self.view))[:3] self.hud_program.is_visible = True elif Qt.LeftButton == buttons: x0, y0 = self.screen2view(*self.last_mouse_pos) x1, y1 = self.screen2view(pos_x, pos_y) v0 = q.arcball(x0, y0) v1 = q.arcball(x1, y1) self.rotation = q.product(v1, v0, self.rotation) self.hud_program.is_visible = True else: self.hud_program.is_visible = False self.update_view_matrix() self.update() self.last_mouse_pos = pos_x, pos_y def update(self): self.render()