def setContext(cls, major_version: int, minor_version: int, core=False, profile=None): """Set OpenGL context, given major, minor version + core using QOpenGLContext Unfortunately, what you get back does not have to be the requested version. """ new_format = QSurfaceFormat() new_format.setMajorVersion(major_version) new_format.setMinorVersion(minor_version) if core: profile_ = QSurfaceFormat.CoreProfile else: profile_ = QSurfaceFormat.CompatibilityProfile if profile is not None: profile_ = profile new_format.setProfile(profile_) new_context = QOpenGLContext() new_context.setFormat(new_format) success = new_context.create() if success: return new_context else: Logger.log( "e", "Failed creating OpenGL context (%d, %d, core=%s)" % (major_version, minor_version, core)) return None
def setContext(cls, major_version: int, minor_version: int, core=False, profile=None): new_format = QSurfaceFormat() new_format.setMajorVersion(major_version) new_format.setMinorVersion(minor_version) if core: profile_ = QSurfaceFormat.CoreProfile else: profile_ = QSurfaceFormat.CompatibilityProfile if profile is not None: profile_ = profile new_format.setProfile(profile_) new_context = QOpenGLContext() new_context.setFormat(new_format) success = new_context.create() if success: return new_context else: Logger.log( "e", "Failed creating OpenGL context (%d, %d, core=%s)" % (major_version, minor_version, core)) return None
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()
def offscreen_context(format): """Provide a temporary QOpenGLContext with the given QSurfaceFormat on an QOffscreenSurface.""" surface = QOffscreenSurface() surface.create() context = QOpenGLContext() context.setFormat(format) context.create() context.makeCurrent(surface) try: yield context finally: context.doneCurrent()
class GLWindow(QWindow): def __init__(self): super(GLWindow, self).__init__() self.title= "GLWindow" self.setGeometry(0, 0, 640, 480) self.setupUI() def setupUI(self): self.setSurfaceType(QWindow.OpenGLSurface) self.renderctxt = QOpenGLContext(self) self.renderctxt.setFormat(self.requestedFormat()) self.renderctxt.create() self.renderctxt.makeCurrent(self) self.glfunc = self.renderctxt.versionFunctions() self.glfunc.initializeOpenGLFunctions()
def setContext(cls, major_version, minor_version, core = False, profile = None): new_format = QSurfaceFormat() new_format.setMajorVersion(major_version) new_format.setMinorVersion(minor_version) if core: profile_ = QSurfaceFormat.CoreProfile else: profile_ = QSurfaceFormat.CompatibilityProfile if profile is not None: profile_ = profile new_format.setProfile(profile_) new_context = QOpenGLContext() new_context.setFormat(new_format) success = new_context.create() if success: return new_context else: Logger.log("e", "Failed creating OpenGL context (%d, %d, core=%s)" % (major_version, minor_version, core)) return None
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()
def detectBestOpenGLVersion(cls): Logger.log("d", "Trying OpenGL context 4.1...") ctx = cls.setContext(4, 1, core=True) if ctx is not None: fmt = ctx.format() profile = fmt.profile() # First test: we hope for this if ((fmt.majorVersion() == 4 and fmt.minorVersion() >= 1) or (fmt.majorVersion() > 4)) and profile == QSurfaceFormat.CoreProfile: Logger.log( "d", "Yay, we got at least OpenGL 4.1 core: %s", cls.versionAsText(fmt.majorVersion(), fmt.minorVersion(), profile)) # https://riverbankcomputing.com/pipermail/pyqt/2017-January/038640.html # PyQt currently only implements 2.0, 2.1 and 4.1Core # If eg 4.5Core would be detected and used here, PyQt would not be able to handle it. major_version = 4 minor_version = 1 # CURA-6092: Check if we're not using software backed 4.1 context; A software 4.1 context # is much slower than a hardware backed 2.0 context gl_window = QWindow() gl_window.setSurfaceType(QWindow.OpenGLSurface) gl_window.showMinimized() gl_format = QSurfaceFormat() gl_format.setMajorVersion(major_version) gl_format.setMinorVersion(minor_version) gl_format.setProfile(profile) gl_context = QOpenGLContext() gl_context.setFormat(gl_format) gl_context.create() gl_context.makeCurrent(gl_window) gl_profile = QOpenGLVersionProfile() gl_profile.setVersion(major_version, minor_version) gl_profile.setProfile(profile) gl = gl_context.versionFunctions( gl_profile ) # type: Any #It's actually a protected class in PyQt that depends on the requested profile and the implementation of your graphics card. gpu_type = "Unknown" # type: str result = None if gl: result = gl.initializeOpenGLFunctions() if not result: Logger.log("e", "Could not initialize OpenGL to get gpu type") else: # WORKAROUND: Cura/#1117 Cura-packaging/12 # Some Intel GPU chipsets return a string, which is not undecodable via PyQt5. # This workaround makes the code fall back to a "Unknown" renderer in these cases. try: gpu_type = gl.glGetString(gl.GL_RENDERER) #type: str except UnicodeDecodeError: Logger.log( "e", "DecodeError while getting GL_RENDERER via glGetString!" ) Logger.log("d", "OpenGL renderer type for this OpenGL version: %s", gpu_type) if "software" in gpu_type.lower(): Logger.log( "w", "Unfortunately OpenGL 4.1 uses software rendering") else: return major_version, minor_version, QSurfaceFormat.CoreProfile else: Logger.log("d", "Failed to create OpenGL context 4.1.") # Fallback: check min spec Logger.log("d", "Trying OpenGL context 2.0...") ctx = cls.setContext(2, 0, profile=QSurfaceFormat.NoProfile) if ctx is not None: fmt = ctx.format() profile = fmt.profile() if fmt.majorVersion() >= 2 and fmt.minorVersion() >= 0: Logger.log( "d", "We got at least OpenGL context 2.0: %s", cls.versionAsText(fmt.majorVersion(), fmt.minorVersion(), profile)) return 2, 0, QSurfaceFormat.NoProfile else: Logger.log( "d", "Current OpenGL context is too low: %s" % cls.versionAsText(fmt.majorVersion(), fmt.minorVersion(), profile)) return None, None, None else: Logger.log("d", "Failed to create OpenGL context 2.0.") return None, None, None
class OffscreenModelRendererThread(QThread): TEX_SIZE = 128 renderedTexture = pyqtSignal(str, QImage) def __init__(self, parent_screen): super().__init__() self.parent_screen = parent_screen self.started.connect(self.started_) self.workspace = None self.offscreen_surface = QOffscreenSurface() self.offscreen_surface.requestedFormat().setVersion(4, 3) self.offscreen_surface.requestedFormat().setProfile( QSurfaceFormat.CoreProfile) self.offscreen_surface.requestedFormat().setDepthBufferSize(24) self.offscreen_surface.setFormat( self.offscreen_surface.requestedFormat()) self.offscreen_surface.create() @pyqtSlot() def started_(self): self.ctx = QOpenGLContext(self.offscreen_surface) self.ctx.setFormat(self.offscreen_surface.requestedFormat()) self.ctx.create() self.fbo = -1 self.tex = -1 self.rbuf = -1 self.setup_fbo() self.renderer = ModelRenderer(self.workspace, self.offscreen_surface) @pyqtSlot(Workspace) def setWorkspace(self, w): if hasattr(self, "renderer"): self.renderer.set_workspace(w) else: self.workspace = w def setup_fbo(self): self.ctx.makeCurrent(self.offscreen_surface) self.tex = GL.glGenTextures(1) self.fbo = GL.glGenFramebuffers(1) self.rbuf = GL.glGenRenderbuffers(1) GL.glBindTexture(GL.GL_TEXTURE_2D, self.tex) GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST) GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST) GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, None) GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, self.rbuf) GL.glRenderbufferStorage(GL.GL_RENDERBUFFER, GL.GL_DEPTH_COMPONENT24, OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE) GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, self.fbo) GL.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_DEPTH_ATTACHMENT, GL.GL_RENDERBUFFER, self.rbuf) GL.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D, self.tex, 0) if GL.glCheckFramebufferStatus( GL.GL_FRAMEBUFFER) != GL.GL_FRAMEBUFFER_COMPLETE: raise RuntimeError("Framebuffer is not complete!") GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0) GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0) @pyqtSlot(str, BlockModel) def queue_render_order(self, order_name, model): """ Render a block model in item format. Subscribing (connecting) to the renderedTexture signal allows you to get the rendered texture back. Pass in an order name so you know which image you got back :param order_name: the order name. passed to renderedTexture :param model: the blockmodel :return: """ self.ctx.makeCurrent(self.offscreen_surface) GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, self.fbo) GL.glViewport(0, 0, OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE) GL.glClearColor(0, 0, 0, 0) GL.glClear(GL.GL_DEPTH_BUFFER_BIT | GL.GL_COLOR_BUFFER_BIT) self.renderer.setup_data_for_block_model(model) self.renderer.resize(OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE) self.renderer.draw_loaded_model( glm.lookAt(glm.vec3(15, 5, 5), glm.vec3(5, 5, 5), glm.vec3(0, 1, 0)), "gui", glm.ortho(-10, 10, 10, -10, 0.1, 50)) tex_str = GL.glReadPixels(0, 0, OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, outputType=bytes) GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0) qimage = QImage(tex_str, OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE, OffscreenModelRendererThread.TEX_SIZE * 4, QImage.Format_RGBA8888) qimage = qimage.mirrored(vertical=True) self.renderedTexture.emit(order_name, qimage)
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()
format = QSurfaceFormat() format.setSwapBehavior(QSurfaceFormat.DoubleBuffer) format.setSamples(16) format.setStencilBufferSize(8) if core: format.setProfile(QSurfaceFormat.CoreProfile) window.setSurfaceType(QWindow.OpenGLSurface) window.setFormat(format) window.setTitle(name) pixel_ratio = window.devicePixelRatio() window.resize(*(u/pixel_ratio for u in window_size)) gl_context = QOpenGLContext(window) gl_context.setFormat(window.requestedFormat()) gl_context.create() # asynchronous redisplay def redisplay(): gl_display(scene, feedback) gl_context.swapBuffers(window) waiting_redisplay = False def post_redisplay(): global waiting_redisplay if not waiting_redisplay: waiting_redisplay = True app.postEvent(app, QEvent(QEvent.UpdateRequest))