def cylindersFromArrows(self): for arrow in self.arrows: xyz1 = vec3d(arrow.x1, arrow.y1, arrow.z1) axis = vec3d(arrow.x2, arrow.y2, arrow.z2) - xyz1 axis *= arrow.rho xyz2 = xyz1 + axis yield Cylinder(arrow.color, xyz1.x(), xyz1.y(), xyz1.z(), xyz2.x(), xyz2.y(), xyz2.z(), arrow.r1)
def conesFromArrows(self): for arrow in self.arrows: xyz1 = vec3d(arrow.x1, arrow.y1, arrow.z1) end = vec3d(arrow.x2, arrow.y2, arrow.z2) axis = xyz1 - end axis *= 1.0 - arrow.rho base = end + axis yield Cone(arrow.color, base.x(), base.y(), base.z(), end.x(), end.y(), end.z(), arrow.r2)
def reset(self): if( self.mesh is None ): return self.camCenter = self.aabb.center camDistance = 2 * self.bounding_radius if( self.mesh.dimensions == 2 ): self.rightVector = vec3d(1, 0, 0) self.camLoc = self.camCenter + vec3d( 0, 0, camDistance ) else: self.rightVector = vec3d( 0, 1, 0 ) self.camLoc = self.camCenter + vec3d( camDistance, 0, 0 ) self._apply() self._setProjection()
def __init__(self, geom): if hasattr(geom, 'allVertices'): # Something lke bildparser.OutputDecorations it = geom.allVertices() else: # assume it's a qt3d geometry vertices = getQAttribute( geom, att_name=Qt3DRender.QAttribute.defaultPositionAttributeName()) it = iterAttr(vertices) v0 = next(it) self.min = vec3d(v0[0], v0[1], v0[2]) self.max = vec3d(self.min) for v in it: self.min.setX(min(self.min.x(), v[0])) self.min.setY(min(self.min.y(), v[1])) self.min.setZ(min(self.min.z(), v[2])) self.max.setX(max(self.max.x(), v[0])) self.max.setY(max(self.max.y(), v[1])) self.max.setZ(max(self.max.z(), v[2])) self.center = (self.min + self.max) / 2.0
def setLightOrientation( self, value ): if( value != self.lightOrientation ): scaled_value = float(value) new_value = geom.rotateAround( vec3d(0,0,100), vec3d(0,1,0), scaled_value ) self.setLightPosition( new_value )
class AthenaViewer(Qt3DExtras.Qt3DWindow, metaclass=_metaParameters): # Define parameters and their defaults - consumed by _metaParameters metaclass # and used to auto-generate various attributes and methods for AthenaViewer _qparameters = { 'alpha': 1.0, 'face_enable': 1.0, 'wire_enable': 1.0, 'proj_orthographic': 1.0, 'dpi' : 100.0, 'flat_color': QColor( 215, 72, 215), 'cool_color': QColor( 0, 0, 127 ), 'warm_color': QColor( 255, 0, 255), 'line.width': 2.5, 'line.color': QColor( 0, 0, 255), 'light.position': vec3d( 0, 0, 100), 'athena_viewport': QMatrix4x4() # see function resizeViewport() for explanation } def _qmlLoad( self, qmlfile ): engine = QQmlEngine() main_qml = Path(ATHENA_SRC_DIR) / 'qml' / qmlfile component = QQmlComponent(engine, main_qml.as_uri() ) if ( component.status() != QQmlComponent.Ready ): print ("Error loading QML:") print(component.errorString()) result = component.create() # Need to hold a reference in python to the QQmlComponent, or else # PySide2 will helpfully delete the material object along with it # after this function ends. self._qtrefs.append(component) return result def _athenaMaterial( self, qmlfile, vert_shader, frag_shader, geom_shader=None ): material = self._qmlLoad( qmlfile ) shader_path = Path(ATHENA_SRC_DIR) / 'shaders' vert_shader = shader_path / vert_shader frag_shader = shader_path / frag_shader if( geom_shader ): geom_shader = shader_path / geom_shader def loadShader( s ): return Qt3DRender.QShaderProgram.loadSource( s.as_uri() ) shader = Qt3DRender.QShaderProgram(material) shader.setVertexShaderCode( loadShader( vert_shader ) ) if( geom_shader): shader.setGeometryShaderCode( loadShader( geom_shader ) ) shader.setFragmentShaderCode( loadShader( frag_shader ) ) for rpass in material.effect().techniques()[0].renderPasses(): rpass.setShaderProgram( shader ) return material def _plyMeshMaterial( self, flavor ): material = self._athenaMaterial( 'meshmaterial.qml', 'wireframe.vert', flavor+'_wireframe.frag', 'wireframe.geom' ) material.addParameter( self._alphaParam ) material.addParameter( self._dpiParam ) material.addParameter( self._faceEnableParam ) material.addParameter( self._wireEnableParam ) material.addParameter( self._lineWidthParam ) material.addParameter( self._lineColorParam ) material.addParameter( self._athenaViewportParam ) return material def _imposterMaterial(self, flavor): flavor_str = flavor + '_imposter' material = self._athenaMaterial( 'imposter.qml', flavor_str + '.vert', flavor_str + '.frag', flavor_str + '.geom' ) material.addParameter( self._projOrthographicParam ) return material def _overlayMaterial( self ): material = self._athenaMaterial( 'overlay.qml', 'overlay.vert', 'overlay.frag' ) return material def __init__(self): super(AthenaViewer, self).__init__() self._qtrefs = [] self.framegraph = AthenaFrameGraph(self) self.setActiveFrameGraph(self.framegraph.root) self.resetBackgroundColor() self.lightOrientation = int(0) # Internal integer controlling light.position attribute self.renderSettings().setRenderPolicy(self.renderSettings().OnDemand) self.rootEntity = Qt3DCore.QEntity() self.initParameters() # defined in metaclass self.faceEnableChanged.connect( self.handleFaceRenderChange ) self.lightPositionChanged.connect( self.handleLightPositionChange ) self.wireEnableChanged.connect( self.handleWireframeRenderChange ) self.sphere_material = self._imposterMaterial('sphere') self.cylinder_material = self._imposterMaterial('cylinder') self.cone_material = self._imposterMaterial('cone') self.flat_material = self._plyMeshMaterial( 'flat' ) self.flat_material.addParameter( self._flatColorParam ) self.gooch_material = self._plyMeshMaterial( 'gooch' ) self.gooch_material.addParameter( self._coolColorParam ) self.gooch_material.addParameter( self._warmColorParam ) self.gooch_material.addParameter( self._lightPositionParam ) # The vertical split line enabled for split-screen view self.overlay_material = self._overlayMaterial() self.splitLineEntity = decorations.LineDecoration( self.rootEntity, [0,-1,0], [0,1,0], [1,1,1,1] ) self.splitLineEntity.addComponent( self.overlay_material ) self.splitLineEntity.setEnabled(False) # Each time a mesh is loaded, we create a new Plymesh and add a material as a component. # Old meshes are deleteLater()-ed. A problem with this approach is that the deleted QEntities # also delete their components (and this seems true even if we try to remove the component first). # The workaround we use here is to also add the materials as components of the root entity, # which keeps Qt3D from deleting them. I don't know if this is the best approach, but it works. self.rootEntity.addComponent(self.flat_material) self.rootEntity.addComponent(self.gooch_material) self.rootEntity.addComponent(self.sphere_material) self.rootEntity.addComponent(self.cylinder_material) self.rootEntity.addComponent(self.cone_material) self.setRootEntity(self.rootEntity) self.meshEntityParent = Qt3DCore.QEntity( self.rootEntity ) self.meshEntity = None class DecorationEntity(Qt3DCore.QEntity): def __init__(self, parent): super().__init__(parent) self.spheres = None self.cylinders = None self.cones = None self.cylModelEntity = DecorationEntity( self.rootEntity ) self.routModelEntities = [ DecorationEntity( self.rootEntity ) for x in range(2) ] self.atomModelEntities = [ DecorationEntity( self.rootEntity ) for x in range(2) ] self.lastpos = None self.mouseTool = 'rotate' self.setDpi( self.screen().physicalDotsPerInch() ) self.camControl = OrthoCamController(self,self.camera(),None,False) #import IPython #IPython.embed() backgroundColorChanged = Signal( QColor ) # Override the automatically generated function to reset the viewport matrix parameter, # because it should never be reset def resetAthenaViewport(self): pass def resetBackgroundColor( self ): self.setBackgroundColor( QColor(0,0,0) ) def backgroundColor( self ): return self.framegraph.clearBuffers.clearColor() def setBackgroundColor( self, color ): self.framegraph.clearBuffers.setClearColor( color ) self.backgroundColorChanged.emit(color) faceRenderingEnabledChanged = Signal( bool ) def faceRenderingEnabled( self ): return self._faceEnableParam.value() > 0.0 def toggleFaceRendering( self, boolvalue ): self.setFaceEnable( 1.0 if boolvalue else 0.0 ) def handleFaceRenderChange( self, floatvalue ): self.faceRenderingEnabledChanged.emit( True if floatvalue > 0.0 else False ) wireframeRenderingEnabledChanged = Signal( bool ) def wireframeRenderingEnabled( self ): return self._wireEnableParam.value() > 0.0 def toggleWireframeRendering( self, boolvalue ): self.setWireEnable( 1.0 if boolvalue else 0.0 ) def handleWireframeRenderChange( self, floatvalue ): self.wireframeRenderingEnabledChanged.emit( True if floatvalue > 0.0 else False ) lightOrientationChanged = Signal( int ) def setLightOrientation( self, value ): if( value != self.lightOrientation ): scaled_value = float(value) new_value = geom.rotateAround( vec3d(0,0,100), vec3d(0,1,0), scaled_value ) self.setLightPosition( new_value ) def handleLightPositionChange( self, new_posn ): new_orientation = math.degrees( math.atan2( new_posn.x(), new_posn.z() )) self.lightOrientationChanged.emit( int(new_orientation) ) def setSplitViewEnabled( self, enabled ): if( enabled ): self.framegraph.viewport.setNormalizedRect( QRectF( 0.5, 0, 0.5, 1.0) ) self.framegraph.viewport2.setNormalizedRect( QRectF( 0, 0, 0.5, 1.0 ) ) self.splitLineEntity.setEnabled( True ) else: whole_screen = QRectF( 0, 0, 1, 1 ) self.framegraph.viewport.setNormalizedRect( whole_screen ) self.framegraph.viewport2.setNormalizedRect( whole_screen ) self.splitLineEntity.setEnabled( False ) self.camControl.split = enabled self.camControl.resize() self.resizeViewport() def resetCamera(self): # FIXME camControl.reset() *should* work here, but something is amiss # and this is the more reliable method right now. Ugh. camclass = self.camControl.__class__ self.camControl = camclass( self, self.camera(), self.meshEntity, self.camControl.split ) def clearAllGeometry( self ): if( self.meshEntity ): self.meshEntity.deleteLater() self.meshEntity = None self.clearDecorations() def clearDecorations( self ): for ent in [self.cylModelEntity] + self.routModelEntities + self.atomModelEntities: if( ent.spheres ): ent.spheres.deleteLater() ent.spheres = None if (ent.cylinders ): ent.cylinders.deleteLater() ent.cylinders = None if (ent.cones ): ent.cones.deleteLater() ent.cones = None def reloadGeom(self, filepath): self.meshFilepath = filepath self.plydata = PlyData.read(filepath) self.clearAllGeometry() self.meshEntity = plymesh.PlyMesh(self.meshEntityParent, self.plydata) mesh_3d = self.meshEntity.dimensions == 3 self.camControl.newMesh(self.meshEntity) if( mesh_3d ): self.meshEntity.addComponent(self.gooch_material) else: self.meshEntity.addComponent(self.flat_material) self.camControl.reset() self.requestUpdate() return mesh_3d def setPerspectiveCam(self): self.setProjOrthographic(0.0) self.camControl = PerspectiveCamController.createFrom( self.camControl) def setOrthoCam(self): self.setProjOrthographic(1.0) self.camControl = OrthoCamController.createFrom( self.camControl) def setRotateTool(self): self.mouseTool = 'rotate' def setPanTool(self): self.mouseTool = 'pan' def setZoomTool(self): self.mouseTool = 'zoom' def requestScreenshot(self, size, dpi=None): ratio = size.width() / size.height() def cleanup(): self.framegraph.setOnscreenRendering() self.setActiveFrameGraph(self.framegraph.root) self.setDpi( self.screen().physicalDotsPerInch() ) self.camControl.resize() self.resizeViewport() self.requestUpdate() if dpi: self.setDpi(dpi) self.camControl.resize(size) self.resizeViewport( size ) self.framegraph.setOffscreenRendering(size) self.setActiveFrameGraph(self.framegraph.root) request = self.framegraph.renderCapture.requestCapture() request.completed.connect( cleanup ) # Now ensure a frame redraw occurs so that the capture can go forward. # A nicer way would be to call renderSettings().sendCommand('InvalidateFrame'), # but PySide2 does not expose QNode.sendCommand(). # c.f. the implementation of Qt3DWindow::event() self.requestUpdate() return request def mouseMoveEvent(self, event): if( self.meshEntity and self.lastpos ): delta = event.pos()-self.lastpos if( event.buttons() == Qt.LeftButton ): tool = getattr(self.camControl, self.mouseTool) tool( delta.x(), delta.y() ) self.lastpos = event.pos() def wheelEvent( self, event ): self.camControl.zoom( 0, event.angleDelta().y() ) def _physicalPixelSize( self, size = None ): if size is None: size = self.size() factor = self.screen().devicePixelRatio() return QSize( size.width() * factor, size.height() * factor ) def resizeViewport( self, size = None ): # the athena_viewport QParameter exists because the Qt3D ViewportMatrix shader # uniform is unreliable: it doesn't seem to be consistently updated before a draw # operation occurs under a new viewport matrix. This messes up shader calculations, # especially in screenshots (where only a single frame is captured under new # rendering dimensions), which in turn wrecks the wireframe renderer. # # This function manually recreates the viewport matrix that Qt3D uses and keeps # it updated in the athena_viewport parameter. # Note that it's important to use physical pixel sizes in this calculation, so # always multiply on-screen sizes by self.screen().devicePixelRatio() viewport_matrix = QMatrix4x4() # The only renderer to use the athena_viewport parameter is the wireframe renderer, # which is always under framegraph.viewport2 viewport = self.framegraph.viewport2.normalizedRect() if ( size == None ): size = self._physicalPixelSize() # c.f. Qt3DRender::SubmissionContext::setViewport() viewport_matrix.viewport( viewport.x() * size.width(), (1.0 - viewport.y() - viewport.height()) * size.height(), viewport.width() * size.width(), viewport.height() * size.height() ); self.setAthenaViewport( viewport_matrix ) def resizeEvent( self, event ): size = event.size() self.resizeViewport( self._physicalPixelSize(size) ) self.camControl.resize( size ) def newDecoration(self, parent, bild_results, decoration_aabb = None): if decoration_aabb is None: decoration_aabb = geom.AABB( bild_results ) geom_aabb = self.camControl.aabb T = geom.transformBetween( decoration_aabb, geom_aabb ) if( bild_results.spheres ): parent.spheres = decorations.SphereDecorations(parent, bild_results, T) parent.spheres.addComponent( self.sphere_material ) if( bild_results.cylinders ): parent.cylinders = decorations.CylinderDecorations(parent, bild_results, T) parent.cylinders.addComponent( self.cylinder_material ) if( bild_results.arrows ): parent.cones = decorations.ConeDecorations(parent, bild_results, T) parent.cones.addComponent( self.cone_material ) def setCylDisplay(self, bild_results, map_aabb): self.newDecoration( self.cylModelEntity, bild_results, map_aabb ) def setRoutDisplay(self, bild_results, map_aabb, variant): self.newDecoration( self.routModelEntities[variant], bild_results, map_aabb ) def setAtomDisplay(self, bild_results, map_aabb, variant): self.newDecoration( self.atomModelEntities[variant], bild_results, map_aabb ) def toggleCylDisplay(self, value): self.cylModelEntity.setEnabled( value ) def toggleRoutDisplay(self, value, variant): self.routModelEntities[variant].setEnabled( value ) def toggleAtomDisplay(self, value, variant): self.atomModelEntities[variant].setEnabled( value )
def __init__(self, window): self.window = window self.offscreenSurface = QOffscreenSurface() sformat = QSurfaceFormat.defaultFormat() sformat.setDepthBufferSize(32) self.offscreenSurface.setFormat( sformat ) self.offscreenSurface.create() self.overlayCamera = Qt3DRender.QCamera() self.overlayCamera.setViewCenter( vec3d() ) self.overlayCamera.setPosition( vec3d( 0, 0, -1 ) ) self.overlayCamera.setUpVector( vec3d( 0, 1, 0 ) ) self.overlayCamera.lens().setOrthographicProjection( -1, 1, -1, 1, -1, 1 ) # Framegraph root #1 -- onscreen rendering self.surfaceSelector = Qt3DRender.QRenderSurfaceSelector() self.surfaceSelector.setSurface(window) self.root = self.surfaceSelector # Framgraph root #2 -- Used for offscreen renders, not in the graph by default # During screenshots, offscreenSurfaceSelector becomes the root # and the branch roots will become a child of renderTargetSelector self.offscreenSurfaceSelector = Qt3DRender.QRenderSurfaceSelector() self.offscreenSurfaceSelector.setSurface(self.offscreenSurface) self.renderTargetSelector = Qt3DRender.QRenderTargetSelector(self.offscreenSurfaceSelector) self.targetTexture = OffscreenRenderTarget(self.renderTargetSelector) self.renderTargetSelector.setTarget(self.targetTexture) self.noDraw2 = Qt3DRender.QNoDraw(self.renderTargetSelector) # Branch 1: clear buffers self.clearBuffers = Qt3DRender.QClearBuffers(self.surfaceSelector) self.clearBuffers.setBuffers(Qt3DRender.QClearBuffers.ColorDepthBuffer) self.clearBuffers.setClearColor(Qt.white) self.noDraw = Qt3DRender.QNoDraw(self.clearBuffers) # Branch 2: main drawing branches using the Athena camera self.cameraSelector = Qt3DRender.QCameraSelector(self.surfaceSelector) self.cameraSelector.setCamera(window.camera()) # Branch 2A: solid objects self.viewport = Qt3DRender.QViewport(self.cameraSelector) self.viewport.setNormalizedRect(QRectF(0, 0, 1.0, 1.0)) self.qfilt = Qt3DRender.QTechniqueFilter(self.viewport) self.solidPassFilter = Qt3DRender.QFilterKey(self.qfilt) self.solidPassFilter.setName('pass') self.solidPassFilter.setValue('solid') self.qfilt.addMatch(self.solidPassFilter) # Branch 2B: transparent objects self.viewport2 = Qt3DRender.QViewport(self.cameraSelector) self.viewport2.setNormalizedRect(QRectF(0, 0, 1.0, 1.0)) self.qfilt2 = Qt3DRender.QTechniqueFilter(self.viewport2) self.transPassFilter = Qt3DRender.QFilterKey(self.qfilt2) self.transPassFilter.setName('pass') self.transPassFilter.setValue('transp') self.qfilt2.addMatch(self.transPassFilter) # Branch 3: 2D screen overlays self.cameraSelector2 = Qt3DRender.QCameraSelector(self.surfaceSelector) self.cameraSelector2.setCamera(self.overlayCamera) self.viewport3 = Qt3DRender.QViewport(self.cameraSelector2) self.viewport3.setNormalizedRect(QRectF(0, 0, 1.0, 1.0)) self.qfilt3 = Qt3DRender.QTechniqueFilter(self.viewport3) self.overlayPassFilter = Qt3DRender.QFilterKey(self.viewport3) self.overlayPassFilter.setName('pass') self.overlayPassFilter.setValue('overlay') self.qfilt3.addMatch(self.overlayPassFilter) # Branch 4: render capture branch for taking screenshots self.renderCapture = Qt3DRender.QRenderCapture(self.surfaceSelector) self.noDraw3 = Qt3DRender.QNoDraw(self.renderCapture) # Branch roots are the bits that need reparenting when switching between # offscreen and onscreen rendering self.branchRoots = [ self.clearBuffers, self.cameraSelector, self.cameraSelector2, self.renderCapture ]