Ejemplo n.º 1
0
    def modelMatrixToMatrices(self, modelMat):
        """ Decompose a model matrix into individual matrices.
            Args:
                modelMat (QMatrix4x4): model matrix to decompose
            Returns:
                QVariant: object containing position, rotation and scale matrices + rotation quaternion
        """
        decomposition = self.decomposeModelMatrix(modelMat)

        posMat = QMatrix4x4()
        posMat.translate(decomposition.get("translation"))

        rotMat = self.quaternionToRotationMatrix(
            decomposition.get("quaternion"))

        scaleMat = QMatrix4x4()
        scaleMat.scale(decomposition.get("scale"))

        return {
            "position": posMat,
            "rotation": rotMat,
            "scale": scaleMat,
            "quaternion": decomposition.get("quaternion")
        }
Ejemplo n.º 2
0
    def qtransform(self) -> QTransform:
        """
        Creates a QTransform based on the full chain of transforms this component points to.
        Where T_1 depends on T_2 which depends on T_3:
        the final transformation T_f = T_3*T_2*T_1

        :return: QTransform of final transformation
        """
        transform_matrix = QMatrix4x4()  # Identity matrix
        for transform in self.transforms_full_chain:
            # Left multiply each new matrix
            transform_matrix = transform.qmatrix * transform_matrix
        transformation = Qt3DCore.QTransform()
        transformation.setMatrix(transform_matrix)
        return transformation
def test_GIVEN_offset_and_distance_WHEN_calling_update_matrix_THEN_correct_matrix_created(
):

    x_offset = 2
    y_offset = 2
    distance = 2
    expected_matrix = QMatrix4x4(0.1, 0, 0, 2, 0, 0.1, 0, 2, 0, 0, 0.1, 2, 0,
                                 0, 0, 1)

    neutron_animation_controller = NeutronAnimationController(
        x_offset, y_offset, None)
    neutron_animation_controller._distance = distance
    neutron_animation_controller.update_matrix()

    assert neutron_animation_controller._matrix == expected_matrix
Ejemplo n.º 4
0
def test_GIVEN_length_WHEN_calling_create_cylinder_matrices_THEN_correct_matrices_returned(
):

    length = 8

    expected_x = QMatrix4x4()
    expected_y = QMatrix4x4()
    expected_z = QMatrix4x4()

    half_length = length * 0.5

    expected_x.rotate(270, QVector3D(0, 0, 1))
    expected_x.translate(QVector3D(0, half_length, 0))

    expected_y.translate(QVector3D(0, half_length, 0))

    expected_z.rotate(90, QVector3D(1, 0, 0))
    expected_z.translate(QVector3D(0, half_length, 0))

    actual_x, actual_y, actual_z = Gnomon.create_cylinder_matrices(length)

    assert expected_x == actual_x
    assert expected_y == actual_y
    assert expected_z == actual_z
Ejemplo n.º 5
0
def test_GIVEN_view_matrix_and_vector_WHEN_calling_create_billboard_transformation_THEN_corect_matrix_returned(
):

    view_matrix = QMatrix4x4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
                             16)
    text_vector = QVector3D(10, 10, 10)

    expected_matrix = view_matrix.transposed()
    expected_matrix.setRow(3, QVector4D())
    expected_matrix.setColumn(3, QVector4D(text_vector, 1))

    actual_matrix = Gnomon.create_billboard_transformation(
        view_matrix, text_vector)

    assert expected_matrix == actual_matrix
Ejemplo n.º 6
0
def test_GIVEN_cylinder_transform_WHEN_calling_set_beam_transform_THEN_matrix_set(
):

    neutron_animation_length = 15

    expected_matrix = QMatrix4x4()
    expected_matrix.rotate(90, QVector3D(1, 0, 0))
    expected_matrix.translate(QVector3D(0, neutron_animation_length * 0.5, 0))

    mock_cylinder_transform = Mock()
    mock_cylinder_transform.setMatrix = Mock()

    Gnomon.set_beam_transform(mock_cylinder_transform,
                              neutron_animation_length)

    assert mock_cylinder_transform.setMatrix.call_args[0][0] == expected_matrix
Ejemplo n.º 7
0
    def relativeLocalTranslate(self, transformQtInstance, initialPosMat,
                               initialRotMat, initialScaleMat, translateVec):
        """ Translate the QTransform in its local space relatively to an initial state.
            Args:
                transformQtInstance (QTransform): reference to the Transform to modify
                initialPosMat (QMatrix4x4): initial position matrix
                initialRotMat (QMatrix4x4): initial rotation matrix
                initialScaleMat (QMatrix4x4): initial scale matrix
                translateVec (QVector3D): vector used for the local translation
        """
        # Compute the translation transformation matrix
        translationMat = QMatrix4x4()
        translationMat.translate(translateVec)

        # Compute the new model matrix (POSITION * ROTATION * TRANSLATE * SCALE) and set it to the Transform
        mat = initialPosMat * initialRotMat * translationMat * initialScaleMat
        transformQtInstance.setMatrix(mat)
Ejemplo n.º 8
0
    def testMatrix4x4(self):
        self.assertRaises(TypeError, QMatrix4x4, [0.0, 1.0, 2.0, 3.0])
        self.assertRaises(TypeError, QMatrix4x4, [0.0, 1.0, 2.0, 'I',
                                                  4.0, 5.0, 6.0, 7.0,
                                                  8.0, 9.0, 'N', 11.0,
                                                  12.0, 'd', 14.0, 'T'])

        my_data = [0.0, 1.0, 2.0, 3.0,
                   4.0, 5.0, 6.0, 7.0,
                   8.0, 9.0, 10.0, 11.0,
                   12.0, 13.0, 14.0, 15.0]
        my_datac = [0.0, 4.0, 8.0, 12.0,
                    1.0, 5.0, 9.0, 13.0,
                    2.0, 6.0, 10.0, 14.0,
                    3.0, 7.0, 11.0, 15.0]

        m = QMatrix4x4(my_data)
        d = m.data()
        self.assert_(my_datac, d)

        d = m.copyDataTo()
        self.assert_(my_data == list(d))
Ejemplo n.º 9
0
 def refine_bbox(self):
     if self.is_bbox_refined:
         return
     refined_bbox_max = QVector3D(0.0, 0.0, 0.0)
     refined_bbox_min = QVector3D(0.0, 0.0, 0.0)
     model_matrix = self.translation_matrix * self.rotation_matrix * self.scale_matrix
     for idx in range(int(len(self.vertices_list) / 3)):
         qv = model_matrix.map(
             QVector3D(self.vertices_list[3 * idx],
                       self.vertices_list[3 * idx + 1],
                       self.vertices_list[3 * idx + 2]))
         if idx > 0:
             min_temp = np.minimum(refined_bbox_min.toTuple(), qv.toTuple())
             max_temp = np.maximum(refined_bbox_max.toTuple(), qv.toTuple())
             refined_bbox_min = QVector3D(min_temp[0], min_temp[1],
                                          min_temp[2])
             refined_bbox_max = QVector3D(max_temp[0], max_temp[1],
                                          max_temp[2])
         else:
             refined_bbox_max = qv
             refined_bbox_min = qv
     self.transformed_bbox_max = refined_bbox_max
     self.transformed_bbox_min = refined_bbox_min
     self.bbox_translation_matrix = QMatrix4x4()
     self.bbox_translation_matrix.translate(0.0,
                                            -self.transformed_bbox_min.y(),
                                            0.0)
     self.bbox_width_mm = (self.transformed_bbox_max.x() -
                           self.transformed_bbox_min.x())
     self.bbox_depth_mm = (self.transformed_bbox_max.z() -
                           self.transformed_bbox_min.z())
     self.bbox_height_mm = (self.transformed_bbox_max.y() -
                            self.transformed_bbox_min.y())
     self.update_physical_size.emit(self.bbox_width_mm, self.bbox_depth_mm,
                                    self.bbox_height_mm)
     self.is_bbox_refined = True
     self.__update_model_matrix__()
Ejemplo n.º 10
0
 def __update_bbox__(self):
     model_matrix = self.translation_matrix * self.rotation_matrix * self.scale_matrix
     x_a = (model_matrix.column(0).toVector3D() *
            self.bbox_min.x()).toTuple()
     y_a = (model_matrix.column(1).toVector3D() *
            self.bbox_min.y()).toTuple()
     z_a = (model_matrix.column(2).toVector3D() *
            self.bbox_min.z()).toTuple()
     x_b = (model_matrix.column(0).toVector3D() *
            self.bbox_max.x()).toTuple()
     y_b = (model_matrix.column(1).toVector3D() *
            self.bbox_max.y()).toTuple()
     z_b = (model_matrix.column(2).toVector3D() *
            self.bbox_max.z()).toTuple()
     min_temp = np.minimum(x_a, x_b) + np.minimum(y_a, y_b) + np.minimum(
         z_a, z_b)
     max_temp = np.maximum(x_a, x_b) + np.maximum(y_a, y_b) + np.maximum(
         z_a, z_b)
     self.transformed_bbox_min = QVector3D(
         min_temp[0], min_temp[1],
         min_temp[2]) + model_matrix.column(3).toVector3D()
     self.transformed_bbox_max = QVector3D(
         max_temp[0], max_temp[1],
         max_temp[2]) + model_matrix.column(3).toVector3D()
     self.bbox_translation_matrix = QMatrix4x4()
     self.bbox_translation_matrix.translate(0.0,
                                            -self.transformed_bbox_min.y(),
                                            0.0)
     self.bbox_width_mm = (self.transformed_bbox_max.x() -
                           self.transformed_bbox_min.x())
     self.bbox_depth_mm = (self.transformed_bbox_max.z() -
                           self.transformed_bbox_min.z())
     self.bbox_height_mm = (self.transformed_bbox_max.y() -
                            self.transformed_bbox_min.y())
     self.update_physical_size.emit(self.bbox_width_mm, self.bbox_depth_mm,
                                    self.bbox_height_mm)
Ejemplo n.º 11
0
 def __init__(self, parent):
     super(OrbitTransformController, self).__init__(parent)
     self._target = None
     self._matrix = QMatrix4x4()
     self._radius = 1
     self._angle = 0
Ejemplo n.º 12
0
    def initializeGL(self):
        print('gl initial')
        print(self.getGlInfo())

        # create context and make it current
        self.context.create()
        self.context.aboutToBeDestroyed.connect(self.cleanUpGl)

        # initialize functions
        funcs = self.context.functions()
        funcs.initializeOpenGLFunctions()
        funcs.glClearColor(0.0, 0.4, 0.4, 0)
        funcs.glEnable(pygl.GL_DEPTH_TEST)
        funcs.glEnable(pygl.GL_TEXTURE_2D)

        # create uniform values for shaders
        # deal with shaders

        # cube shader
        self.program = QOpenGLShaderProgram(self.context)
        vshader = self.loadVertexShader("cube")
        fshader = self.loadFragmentShader("cube")
        self.program.addShader(vshader)  # adding vertex shader
        self.program.addShader(fshader)  # adding fragment shader
        self.program.bindAttributeLocation("aPos", 0)
        self.program.bindAttributeLocation("aTexCoord", 1)

        isLinked = self.program.link()
        print("cube shader program is linked: ", isLinked)
        # bind the program
        self.program.bind()

        # set projection matrix
        projectionMatrix = QMatrix4x4()
        projectionMatrix.perspective(self.camera.zoom,
                                     self.width() / self.height(), 0.2, 100.0)

        self.program.setUniformValue('projection', projectionMatrix)

        # set view/camera matrix
        viewMatrix = self.camera.getViewMatrix()
        self.program.setUniformValue('view', viewMatrix)
        self.program.setUniformValue('myTexture1', self.texUnit1)
        self.program.setUniformValue('myTexture2', self.texUnit2)
        #
        # deal with vaos and vbo
        # vbo
        isVbo = self.vbo.create()
        isVboBound = self.vbo.bind()

        floatSize = ctypes.sizeof(ctypes.c_float)

        # allocate space on vbo buffer
        self.vbo.allocate(self.cubeVertices.tobytes(),
                          floatSize * self.cubeVertices.size)
        self.vao.create()
        vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao)
        funcs.glEnableVertexAttribArray(0)  # viewport
        funcs.glVertexAttribPointer(0, 3, int(pygl.GL_FLOAT),
                                    int(pygl.GL_FALSE), 5 * floatSize,
                                    VoidPtr(0))
        funcs.glEnableVertexAttribArray(1)
        funcs.glVertexAttribPointer(1, 2, int(pygl.GL_FLOAT),
                                    int(pygl.GL_FALSE), 5 * floatSize,
                                    VoidPtr(3 * floatSize))
        # deal with textures
        # first texture
        self.texture1 = QOpenGLTexture(QOpenGLTexture.Target2D)
        self.texture1.create()
        self.texture1.bind(self.texUnit1)
        self.texture1.setData(self.image1)
        self.texture1.setMinMagFilters(QOpenGLTexture.Nearest,
                                       QOpenGLTexture.Nearest)
        self.texture1.setWrapMode(QOpenGLTexture.DirectionS,
                                  QOpenGLTexture.Repeat)
        self.texture1.setWrapMode(QOpenGLTexture.DirectionT,
                                  QOpenGLTexture.Repeat)

        # second texture
        self.texture2 = QOpenGLTexture(QOpenGLTexture.Target2D)
        self.texture2.create()
        self.texture2.bind(self.texUnit2)
        self.texture2.setData(self.image2)
        self.texture2.setMinMagFilters(QOpenGLTexture.Linear,
                                       QOpenGLTexture.Linear)
        self.texture2.setWrapMode(QOpenGLTexture.DirectionS,
                                  QOpenGLTexture.Repeat)
        self.texture2.setWrapMode(QOpenGLTexture.DirectionT,
                                  QOpenGLTexture.Repeat)

        self.vbo.release()
        vaoBinder = None
        print("gl initialized")
Ejemplo n.º 13
0
 def get_normal_matrix_array(self, matrix=QMatrix4x4()):
     normal_matrix = (matrix * self.model_matrix).normalMatrix()
     normal_matrix_array = np.asarray(normal_matrix.data(), np.float32)
     return normal_matrix_array
Ejemplo n.º 14
0
 def getViewMatrix(self):
     "Obtain view matrix for camera"
     view = QMatrix4x4()
     view.lookAt(self.position, self.position + self.front, self.up)
     return view
Ejemplo n.º 15
0
 def getTranslation(self):
     r = QMatrix4x4()
     r.rotate(self.vertRot, QVector3D(1.0, 0.0, 0.0))
     r.rotate(self.horRot, QVector3D(0.0, 1.0, 0.0))
     return self.eyeLoc * r + self.scene.getFocusObject().getTranslation()
Ejemplo n.º 16
0
 def getProjectionMatrix(self):
     projection = QMatrix4x4()
     projection.setToIdentity()
     projection.perspective(self.fovY, self.width / self.height, 0.1, 100.0)
     return projection
Ejemplo n.º 17
0
def computePerspectiveQt(fieldOfView: float, aspect: float, zNear: float,
                         zFar: float):
    "matrice"
    mat = QMatrix4x4(*[0.0 for i in range(16)])
    return mat.perspective(fieldOfView, aspect, zNear, zFar)
Ejemplo n.º 18
0
 def copyMatrix4x4(self, mat):
     """ Make a deep copy of a QMatrix4x4. """
     newMat = QMatrix4x4()
     for i in range(4):
         newMat.setRow(i, mat.row(i))
     return newMat
Ejemplo n.º 19
0
 def setMatrix4x4(self, name, m):
     if self.enabled <= 0:
         raise Exception("shader must be enabled")
     self.program.setUniformValue(name, QMatrix4x4(*m.flat))
Ejemplo n.º 20
0
 def _get_sphere_transformation_matrix(offset: np.ndarray) -> QMatrix4x4:
     matrix = QMatrix4x4()
     matrix.translate(QVector3D(offset[0], offset[1], offset[2]))
     return matrix
Ejemplo n.º 21
0
 def get_normal_matrix_array(self, matrix=QMatrix4x4()):
     normal_matrix = (matrix * self.model_matrix).normalMatrix()
     return normal_matrix
Ejemplo n.º 22
0
 def _get_cylinder_transformation_matrix() -> QMatrix4x4:
     matrix = QMatrix4x4()
     matrix.rotate(90, QVector3D(1, 0, 0))
     return matrix
Ejemplo n.º 23
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 )