Ejemplo n.º 1
0
 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)
Ejemplo n.º 2
0
 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)
Ejemplo n.º 3
0
 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()
Ejemplo n.º 4
0
 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
Ejemplo n.º 5
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 ) 
Ejemplo n.º 6
0
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 )
Ejemplo n.º 7
0
    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 ]