Exemplo n.º 1
0
    def _updateLocalTransformation(self):
        translation, euler_angle_matrix, scale, shear = self._transformation.decompose(
        )

        self._position = translation
        self._scale = scale
        self._shear = shear
        orientation = Quaternion()
        orientation.setByMatrix(euler_angle_matrix)
        self._orientation = orientation
Exemplo n.º 2
0
class SceneNode:
    class TransformSpace:
        Local = 1  #type: int
        Parent = 2  #type: int
        World = 3  #type: int

    ##  Construct a scene node.
    #   \param parent The parent of this node (if any). Only a root node should have None as a parent.
    #   \param visible Is the SceneNode (and thus, all its children) visible?
    #   \param name Name of the SceneNode.
    def __init__(self,
                 parent: Optional["SceneNode"] = None,
                 visible: bool = True,
                 name: str = "") -> None:
        super().__init__()  # Call super to make multiple inheritance work.

        self._children = []  # type: List[SceneNode]
        self._mesh_data = None  # type: Optional[MeshData]

        # Local transformation (from parent to local)
        self._transformation = Matrix()  # type: Matrix

        # Convenience "components" of the transformation
        self._position = Vector()  # type: Vector
        self._scale = Vector(1.0, 1.0, 1.0)  # type: Vector
        self._shear = Vector(0.0, 0.0, 0.0)  # type: Vector
        self._mirror = Vector(1.0, 1.0, 1.0)  # type: Vector
        self._orientation = Quaternion()  # type: Quaternion

        # World transformation (from root to local)
        self._world_transformation = Matrix()  # type: Matrix

        # Convenience "components" of the world_transformation
        self._derived_position = Vector()  # type: Vector
        self._derived_orientation = Quaternion()  # type: Quaternion
        self._derived_scale = Vector()  # type: Vector

        self._parent = parent  # type: Optional[SceneNode]

        # Can this SceneNode be modified in any way?
        self._enabled = True  # type: bool
        # Can this SceneNode be selected in any way?
        self._selectable = False  # type: bool

        # Should the AxisAlignedBoundingBox be re-calculated?
        self._calculate_aabb = True  # type: bool

        # The AxisAligned bounding box.
        self._aabb = None  # type: Optional[AxisAlignedBox]
        self._bounding_box_mesh = None  # type: Optional[MeshData]

        self._visible = visible  # type: bool
        self._name = name  # type: str
        self._decorators = []  # type: List[SceneNodeDecorator]

        # Store custom settings to be compatible with Savitar SceneNode
        self._settings = {}  # type: Dict[str, Any]

        ## Signals
        #        self.parentChanged.connect(self._onParentChanged)

        if parent:
            parent.addChild(self)

    def __deepcopy__(self, memo: Dict[int, object]) -> "SceneNode":
        copy = self.__class__()
        copy.setTransformation(self.getLocalTransformation())
        copy.setMeshData(self._mesh_data)
        copy._visible = cast(bool, deepcopy(self._visible, memo))
        copy._selectable = cast(bool, deepcopy(self._selectable, memo))
        copy._name = cast(str, deepcopy(self._name, memo))
        for decorator in self._decorators:
            copy.addDecorator(
                cast(SceneNodeDecorator, deepcopy(decorator, memo)))

        for child in self._children:
            copy.addChild(cast(SceneNode, deepcopy(child, memo)))
        self.calculateBoundingBoxMesh()
        return copy

    ##  Set the center position of this node.
    #   This is used to modify it's mesh data (and it's children) in such a way that they are centered.
    #   In most cases this means that we use the center of mass as center (which most objects don't use)
    def setCenterPosition(self, center: Vector) -> None:
        if self._mesh_data:
            m = Matrix()
            m.setByTranslation(-center)
            self._mesh_data = self._mesh_data.getTransformed(m).set(
                center_position=center)
        for child in self._children:
            child.setCenterPosition(center)

    ##  \brief Get the parent of this node. If the node has no parent, it is the root node.
    #   \returns SceneNode if it has a parent and None if it's the root node.
    def getParent(self) -> Optional["SceneNode"]:
        return self._parent

    def getMirror(self) -> Vector:
        return self._mirror

    def setMirror(self, vector) -> None:
        self._mirror = vector

    ##  Get the MeshData of the bounding box
    #   \returns \type{MeshData} Bounding box mesh.
    def getBoundingBoxMesh(self) -> Optional[MeshData]:
        if self._bounding_box_mesh is None:
            self.calculateBoundingBoxMesh()
        return self._bounding_box_mesh

    ##  (re)Calculate the bounding box mesh.
    def calculateBoundingBoxMesh(self) -> None:
        aabb = self.getBoundingBox()
        if aabb:
            bounding_box_mesh = MeshBuilder()
            rtf = aabb.maximum
            lbb = aabb.minimum

            bounding_box_mesh.addVertex(rtf.x, rtf.y,
                                        rtf.z)  # Right - Top - Front
            bounding_box_mesh.addVertex(lbb.x, rtf.y,
                                        rtf.z)  # Left - Top - Front

            bounding_box_mesh.addVertex(lbb.x, rtf.y,
                                        rtf.z)  # Left - Top - Front
            bounding_box_mesh.addVertex(lbb.x, lbb.y,
                                        rtf.z)  # Left - Bottom - Front

            bounding_box_mesh.addVertex(lbb.x, lbb.y,
                                        rtf.z)  # Left - Bottom - Front
            bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                        rtf.z)  # Right - Bottom - Front

            bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                        rtf.z)  # Right - Bottom - Front
            bounding_box_mesh.addVertex(rtf.x, rtf.y,
                                        rtf.z)  # Right - Top - Front

            bounding_box_mesh.addVertex(rtf.x, rtf.y,
                                        lbb.z)  # Right - Top - Back
            bounding_box_mesh.addVertex(lbb.x, rtf.y,
                                        lbb.z)  # Left - Top - Back

            bounding_box_mesh.addVertex(lbb.x, rtf.y,
                                        lbb.z)  # Left - Top - Back
            bounding_box_mesh.addVertex(lbb.x, lbb.y,
                                        lbb.z)  # Left - Bottom - Back

            bounding_box_mesh.addVertex(lbb.x, lbb.y,
                                        lbb.z)  # Left - Bottom - Back
            bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                        lbb.z)  # Right - Bottom - Back

            bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                        lbb.z)  # Right - Bottom - Back
            bounding_box_mesh.addVertex(rtf.x, rtf.y,
                                        lbb.z)  # Right - Top - Back

            bounding_box_mesh.addVertex(rtf.x, rtf.y,
                                        rtf.z)  # Right - Top - Front
            bounding_box_mesh.addVertex(rtf.x, rtf.y,
                                        lbb.z)  # Right - Top - Back

            bounding_box_mesh.addVertex(lbb.x, rtf.y,
                                        rtf.z)  # Left - Top - Front
            bounding_box_mesh.addVertex(lbb.x, rtf.y,
                                        lbb.z)  # Left - Top - Back

            bounding_box_mesh.addVertex(lbb.x, lbb.y,
                                        rtf.z)  # Left - Bottom - Front
            bounding_box_mesh.addVertex(lbb.x, lbb.y,
                                        lbb.z)  # Left - Bottom - Back

            bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                        rtf.z)  # Right - Bottom - Front
            bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                        lbb.z)  # Right - Bottom - Back

            self._bounding_box_mesh = bounding_box_mesh.build()

    ##  Return if the provided bbox collides with the bbox of this SceneNode
    def collidesWithBbox(self, check_bbox: AxisAlignedBox) -> bool:
        bbox = self.getBoundingBox()
        if bbox is not None:
            if check_bbox.intersectsBox(
                    bbox
            ) != AxisAlignedBox.IntersectionResult.FullIntersection:
                return True

        return False

    ##  Handler for the ParentChanged signal
    #   \param node Node from which this event was triggered.
    def _onParentChanged(self, node: Optional["SceneNode"]) -> None:
        for child in self.getChildren():
            child.parentChanged.emit(self)

    ##  Signal for when a \type{SceneNodeDecorator} is added / removed.
# decoratorsChanged = Signal()

##  Add a SceneNodeDecorator to this SceneNode.
#   \param \type{SceneNodeDecorator} decorator The decorator to add.

    def addDecorator(self, decorator: SceneNodeDecorator) -> None:
        if type(decorator) in [type(dec) for dec in self._decorators]:
            #       Logger.log("w", "Unable to add the same decorator type (%s) to a SceneNode twice.", type(decorator))
            return
        try:
            decorator.setNode(self)
        except AttributeError:
            #  Logger.logException("e", "Unable to add decorator.")
            return
        self._decorators.append(decorator)
        self.decoratorsChanged.emit(self)

    ##  Get all SceneNodeDecorators that decorate this SceneNode.
    #   \return list of all SceneNodeDecorators.
    def getDecorators(self) -> List[SceneNodeDecorator]:
        return self._decorators

    ##  Get SceneNodeDecorators by type.
    #   \param dec_type type of decorator to return.
    def getDecorator(self, dec_type: type) -> Optional[SceneNodeDecorator]:
        for decorator in self._decorators:
            if type(decorator) == dec_type:
                return decorator
        return None

    ##  Remove all decorators
    def removeDecorators(self):
        for decorator in self._decorators:
            decorator.clear()
        self._decorators = []
        self.decoratorsChanged.emit(self)

    ##  Remove decorator by type.
    #   \param dec_type type of the decorator to remove.
    def removeDecorator(self, dec_type: type) -> None:
        for decorator in self._decorators:
            if type(decorator) == dec_type:
                decorator.clear()
                self._decorators.remove(decorator)
                self.decoratorsChanged.emit(self)
                break

    ##  Call a decoration of this SceneNode.
    #   SceneNodeDecorators add Decorations, which are callable functions.
    #   \param function The function to be called.
    #   \param *args
    #   \param **kwargs
    def callDecoration(self, function: str, *args, **kwargs) -> Any:
        for decorator in self._decorators:
            if hasattr(decorator, function):
                try:
                    return getattr(decorator, function)(*args, **kwargs)
                except Exception as e:
                    #     Logger.logException("e", "Exception calling decoration %s: %s", str(function), str(e))
                    return None

    ##  Does this SceneNode have a certain Decoration (as defined by a Decorator)
    #   \param \type{string} function the function to check for.
    def hasDecoration(self, function: str) -> bool:
        for decorator in self._decorators:
            if hasattr(decorator, function):
                return True
        return False

    def getName(self) -> str:
        return self._name

    def setName(self, name: str) -> None:
        self._name = name

    ##  How many nodes is this node removed from the root?
    #   \return |tupe{int} Steps from root (0 means it -is- the root).
    def getDepth(self) -> int:
        if self._parent is None:
            return 0
        return self._parent.getDepth() + 1

    ##  \brief Set the parent of this object
    #   \param scene_node SceneNode that is the parent of this object.
    def setParent(self, scene_node: Optional["SceneNode"]) -> None:
        if self._parent:
            self._parent.removeChild(self)

        if scene_node:
            scene_node.addChild(self)

    ##  Emitted whenever the parent changes.
#  parentChanged = Signal()

##  \brief Get the visibility of this node. The parents visibility overrides the visibility.
#   TODO: Let renderer actually use the visibility to decide whether to render or not.

    def isVisible(self) -> bool:
        if self._parent is not None and self._visible:
            return self._parent.isVisible()
        else:
            return self._visible

    ##  Set the visibility of this SceneNode.
    def setVisible(self, visible: bool) -> None:
        self._visible = visible

    ##  \brief Get the (original) mesh data from the scene node/object.
    #   \returns MeshData
    def getMeshData(self) -> Optional[MeshData]:
        return self._mesh_data

    ##  \brief Get the transformed mesh data from the scene node/object, based on the transformation of scene nodes wrt root.
    #          If this node is a group, it will recursively concatenate all child nodes/objects.
    #   \returns MeshData
    def getMeshDataTransformed(self) -> Optional[MeshData]:
        return MeshData(vertices=self.getMeshDataTransformedVertices(),
                        normals=self.getMeshDataTransformedNormals())

    ##  \brief Get the transformed vertices from this scene node/object, based on the transformation of scene nodes wrt root.
    #          If this node is a group, it will recursively concatenate all child nodes/objects.
    #   \return numpy.ndarray
    def getMeshDataTransformedVertices(self) -> numpy.ndarray:
        transformed_vertices = None
        if self.callDecoration("isGroup"):
            for child in self._children:
                tv = child.getMeshDataTransformedVertices()
                if transformed_vertices is None:
                    transformed_vertices = tv
                else:
                    transformed_vertices = numpy.concatenate(
                        (transformed_vertices, tv), axis=0)
        else:
            if self._mesh_data:
                transformed_vertices = self._mesh_data.getTransformed(
                    self.getWorldTransformation()).getVertices()
        return transformed_vertices

    ##  \brief Get the transformed normals from this scene node/object, based on the transformation of scene nodes wrt root.
    #          If this node is a group, it will recursively concatenate all child nodes/objects.
    #   \return numpy.ndarray
    def getMeshDataTransformedNormals(self) -> numpy.ndarray:
        transformed_normals = None
        if self.callDecoration("isGroup"):
            for child in self._children:
                tv = child.getMeshDataTransformedNormals()
                if transformed_normals is None:
                    transformed_normals = tv
                else:
                    transformed_normals = numpy.concatenate(
                        (transformed_normals, tv), axis=0)
        else:
            if self._mesh_data:
                transformed_normals = self._mesh_data.getTransformed(
                    self.getWorldTransformation()).getNormals()
        return transformed_normals

    ##  \brief Set the mesh of this node/object
    #   \param mesh_data MeshData object
    def setMeshData(self, mesh_data: Optional[MeshData]) -> None:
        self._mesh_data = mesh_data
        self._resetAABB()
#        self.meshDataChanged.emit(self)

##  Emitted whenever the attached mesh data object changes.
# meshDataChanged = Signal()

    def _onMeshDataChanged(self) -> None:
        self.meshDataChanged.emit(self)

    ##  \brief Add a child to this node and set it's parent as this node.
    #   \params scene_node SceneNode to add.
    def addChild(self, scene_node: "SceneNode") -> None:
        if scene_node in self._children:
            return

        scene_node.transformationChanged.connect(self.transformationChanged)
        scene_node.childrenChanged.connect(self.childrenChanged)
        scene_node.meshDataChanged.connect(self.meshDataChanged)

        self._children.append(scene_node)
        self._resetAABB()
        self.childrenChanged.emit(self)

        if not scene_node._parent is self:
            scene_node._parent = self
            scene_node._transformChanged()
            scene_node.parentChanged.emit(self)

    ##  \brief remove a single child
    #   \param child Scene node that needs to be removed.
    def removeChild(self, child: "SceneNode") -> None:
        if child not in self._children:
            return

        child.transformationChanged.disconnect(self.transformationChanged)
        child.childrenChanged.disconnect(self.childrenChanged)
        child.meshDataChanged.disconnect(self.meshDataChanged)

        self._children.remove(child)
        child._parent = None
        child._transformChanged()
        child.parentChanged.emit(self)

        self._resetAABB()
        self.childrenChanged.emit(self)

    ##  \brief Removes all children and its children's children.
    def removeAllChildren(self) -> None:
        for child in self._children:
            child.removeAllChildren()
            self.removeChild(child)

        self.childrenChanged.emit(self)

    ##  \brief Get the list of direct children
    #   \returns List of children
    def getChildren(self) -> List["SceneNode"]:
        return self._children

    def hasChildren(self) -> bool:
        return True if self._children else False

    ##  \brief Get list of all children (including it's children children children etc.)
    #   \returns list ALl children in this 'tree'
    def getAllChildren(self) -> List["SceneNode"]:
        children = []
        children.extend(self._children)
        for child in self._children:
            children.extend(child.getAllChildren())
        return children

    ##  \brief Emitted whenever the list of children of this object or any child object changes.
    #   \param object The object that triggered the change.
# childrenChanged = Signal()

##  \brief Computes and returns the transformation from world to local space.
#   \returns 4x4 transformation matrix

    def getWorldTransformation(self) -> Matrix:
        if self._world_transformation is None:
            self._updateWorldTransformation()

        return self._world_transformation.copy()

    ##  \brief Returns the local transformation with respect to its parent. (from parent to local)
    #   \retuns transformation 4x4 (homogenous) matrix
    def getLocalTransformation(self) -> Matrix:
        if self._transformation is None:
            self._updateLocalTransformation()

        return self._transformation.copy()

    def setTransformation(self, transformation: Matrix):
        self._transformation = transformation.copy(
        )  # Make a copy to ensure we never change the given transformation
        self._transformChanged()

    ##  Get the local orientation value.
    def getOrientation(self) -> Quaternion:
        return deepcopy(self._orientation)

    def getWorldOrientation(self) -> Quaternion:
        return deepcopy(self._derived_orientation)

    ##  \brief Rotate the scene object (and thus its children) by given amount
    #
    #   \param rotation \type{Quaternion} A quaternion indicating the amount of rotation.
    #   \param transform_space The space relative to which to rotate. Can be any one of the constants in SceneNode::TransformSpace.
    def rotate(self,
               rotation: Quaternion,
               transform_space: int = TransformSpace.Local) -> None:
        if not self._enabled:
            return

        orientation_matrix = rotation.toMatrix()
        if transform_space == SceneNode.TransformSpace.Local:
            self._transformation.multiply(orientation_matrix)
        elif transform_space == SceneNode.TransformSpace.Parent:
            self._transformation.preMultiply(orientation_matrix)
        elif transform_space == SceneNode.TransformSpace.World:
            self._transformation.multiply(
                self._world_transformation.getInverse())
            self._transformation.multiply(orientation_matrix)
            self._transformation.multiply(self._world_transformation)

        self._transformChanged()

    ##  Set the local orientation of this scene node.
    #
    #   \param orientation \type{Quaternion} The new orientation of this scene node.
    #   \param transform_space The space relative to which to rotate. Can be Local or World from SceneNode::TransformSpace.
    def setOrientation(self,
                       orientation: Quaternion,
                       transform_space: int = TransformSpace.Local) -> None:
        if not self._enabled or orientation == self._orientation:
            return

        if transform_space == SceneNode.TransformSpace.World:
            if self.getWorldOrientation() == orientation:
                return
            new_orientation = orientation * (
                self.getWorldOrientation() *
                self._orientation.getInverse()).invert()
            orientation_matrix = new_orientation.toMatrix()
        else:  # Local
            orientation_matrix = orientation.toMatrix()

        euler_angles = orientation_matrix.getEuler()
        new_transform_matrix = Matrix()
        new_transform_matrix.compose(scale=self._scale,
                                     angles=euler_angles,
                                     translate=self._position,
                                     shear=self._shear)
        self._transformation = new_transform_matrix
        self._transformChanged()

    ##  Get the local scaling value.
    def getScale(self) -> Vector:
        return self._scale

    def getWorldScale(self) -> Vector:
        return self._derived_scale

    ##  Scale the scene object (and thus its children) by given amount
    #
    #   \param scale \type{Vector} A Vector with three scale values
    #   \param transform_space The space relative to which to scale. Can be any one of the constants in SceneNode::TransformSpace.
    def scale(self,
              scale: Vector,
              transform_space: int = TransformSpace.Local) -> None:
        if not self._enabled:
            return

        scale_matrix = Matrix()
        scale_matrix.setByScaleVector(scale)
        if transform_space == SceneNode.TransformSpace.Local:
            self._transformation.multiply(scale_matrix)
        elif transform_space == SceneNode.TransformSpace.Parent:
            self._transformation.preMultiply(scale_matrix)
        elif transform_space == SceneNode.TransformSpace.World:
            self._transformation.multiply(
                self._world_transformation.getInverse())
            self._transformation.multiply(scale_matrix)
            self._transformation.multiply(self._world_transformation)

        self._transformChanged()

    ##  Set the local scale value.
    #
    #   \param scale \type{Vector} The new scale value of the scene node.
    #   \param transform_space The space relative to which to rotate. Can be Local or World from SceneNode::TransformSpace.
    def setScale(self,
                 scale: Vector,
                 transform_space: int = TransformSpace.Local) -> None:
        if not self._enabled or scale == self._scale:
            return
        if transform_space == SceneNode.TransformSpace.Local:
            self.scale(scale / self._scale, SceneNode.TransformSpace.Local)
            return
        if transform_space == SceneNode.TransformSpace.World:
            if self.getWorldScale() == scale:
                return

            self.scale(scale / self._scale, SceneNode.TransformSpace.World)

    ##  Get the local position.
    def getPosition(self) -> Vector:
        return self._position

    ##  Get the position of this scene node relative to the world.
    def getWorldPosition(self) -> Vector:
        return self._derived_position

    ##  Translate the scene object (and thus its children) by given amount.
    #
    #   \param translation \type{Vector} The amount to translate by.
    #   \param transform_space The space relative to which to translate. Can be any one of the constants in SceneNode::TransformSpace.
    def translate(self,
                  translation: Vector,
                  transform_space: int = TransformSpace.Local) -> None:
        if not self._enabled:
            return
        translation_matrix = Matrix()
        translation_matrix.setByTranslation(translation)
        if transform_space == SceneNode.TransformSpace.Local:
            self._transformation.multiply(translation_matrix)
        elif transform_space == SceneNode.TransformSpace.Parent:
            self._transformation.preMultiply(translation_matrix)
        elif transform_space == SceneNode.TransformSpace.World:
            world_transformation = self._world_transformation.copy()
            self._transformation.multiply(
                self._world_transformation.getInverse())
            self._transformation.multiply(translation_matrix)
            self._transformation.multiply(world_transformation)
        self._transformChanged()

    ##  Set the local position value.
    #
    #   \param position The new position value of the SceneNode.
    #   \param transform_space The space relative to which to rotate. Can be Local or World from SceneNode::TransformSpace.
    def setPosition(self,
                    position: Vector,
                    transform_space: int = TransformSpace.Local) -> None:
        if not self._enabled or position == self._position:
            return
        if transform_space == SceneNode.TransformSpace.Local:
            self.translate(position - self._position,
                           SceneNode.TransformSpace.Parent)
        if transform_space == SceneNode.TransformSpace.World:
            if self.getWorldPosition() == position:
                return
            self.translate(position - self._derived_position,
                           SceneNode.TransformSpace.World)

    ##  Signal. Emitted whenever the transformation of this object or any child object changes.
    #   \param object The object that caused the change.
# transformationChanged = Signal()

##  Rotate this scene node in such a way that it is looking at target.
#
#   \param target \type{Vector} The target to look at.
#   \param up \type{Vector} The vector to consider up. Defaults to Vector.Unit_Y, i.e. (0, 1, 0).

    def lookAt(self, target: Vector, up: Vector = Vector.Unit_Y) -> None:
        if not self._enabled:
            return

        eye = self.getWorldPosition()
        f = (target - eye).normalized()
        up = up.normalized()
        s = f.cross(up).normalized()
        u = s.cross(f).normalized()

        m = Matrix([[s.x, u.x, -f.x, 0.0], [s.y, u.y, -f.y, 0.0],
                    [s.z, u.z, -f.z, 0.0], [0.0, 0.0, 0.0, 1.0]])

        self.setOrientation(Quaternion.fromMatrix(m))

    ##  Can be overridden by child nodes if they need to perform special rendering.
    #   If you need to handle rendering in a special way, for example for tool handles,
    #   you can override this method and render the node. Return True to prevent the
    #   view from rendering any attached mesh data.
    #
    #   \param renderer The renderer object to use for rendering.
    #
    #   \return False if the view should render this node, True if we handle our own rendering.
    def render(self, renderer) -> bool:
        return False

    ##  Get whether this SceneNode is enabled, that is, it can be modified in any way.
    def isEnabled(self) -> bool:
        if self._parent is not None and self._enabled:
            return self._parent.isEnabled()
        else:
            return self._enabled

    ##  Set whether this SceneNode is enabled.
    #   \param enable True if this object should be enabled, False if not.
    #   \sa isEnabled
    def setEnabled(self, enable: bool) -> None:
        self._enabled = enable

    ##  Get whether this SceneNode can be selected.
    #
    #   \note This will return false if isEnabled() returns false.
    def isSelectable(self) -> bool:
        return self._enabled and self._selectable

    ##  Set whether this SceneNode can be selected.
    #
    #   \param select True if this SceneNode should be selectable, False if not.
    def setSelectable(self, select: bool) -> None:
        self._selectable = select

    ##  Get the bounding box of this node and its children.
    def getBoundingBox(self) -> Optional[AxisAlignedBox]:
        if not self._calculate_aabb:
            return None
        if self._aabb is None:
            self._calculateAABB()
        return self._aabb

    ##  Set whether or not to calculate the bounding box for this node.
    #
    #   \param calculate True if the bounding box should be calculated, False if not.
    def setCalculateBoundingBox(self, calculate: bool) -> None:
        self._calculate_aabb = calculate

# boundingBoxChanged = Signal()

    def getShear(self) -> Vector:
        return self._shear

    def getSetting(self, key: str, default_value: str = "") -> str:
        return self._settings.get(key, default_value)

    def setSetting(self, key: str, value: str) -> None:
        self._settings[key] = value

    ##  private:
    def _transformChanged(self) -> None:
        self._updateTransformation()
        self._resetAABB()
        self.transformationChanged.emit(self)

        for child in self._children:
            child._transformChanged()

    def _updateLocalTransformation(self):
        translation, euler_angle_matrix, scale, shear = self._transformation.decompose(
        )

        self._position = translation
        self._scale = scale
        self._shear = shear
        orientation = Quaternion()
        orientation.setByMatrix(euler_angle_matrix)
        self._orientation = orientation

    def _updateWorldTransformation(self):
        if self._parent:
            self._world_transformation = self._parent.getWorldTransformation(
            ).multiply(self._transformation)
        else:
            self._world_transformation = self._transformation

        world_translation, world_euler_angle_matrix, world_scale, world_shear = self._world_transformation.decompose(
        )
        self._derived_position = world_translation
        self._derived_scale = world_scale
        self._derived_orientation.setByMatrix(world_euler_angle_matrix)

    def _updateTransformation(self) -> None:
        self._updateLocalTransformation()
        self._updateWorldTransformation()

    def _resetAABB(self) -> None:
        if not self._calculate_aabb:
            return
        self._aabb = None
        self._bounding_box_mesh = None
        if self._parent:
            self._parent._resetAABB()
#     self.boundingBoxChanged.emit()

    def _calculateAABB(self) -> None:
        if self._mesh_data:
            aabb = self._mesh_data.getExtents(self.getWorldTransformation())
        else:  # If there is no mesh_data, use a boundingbox that encompasses the local (0,0,0)
            position = self.getWorldPosition()
            aabb = AxisAlignedBox(minimum=position, maximum=position)

        for child in self._children:
            if aabb is None:
                aabb = child.getBoundingBox()
            else:
                aabb = aabb + child.getBoundingBox()
        self._aabb = aabb

    ##  String output for debugging.
    def __str__(self) -> str:
        name = self._name if self._name != "" else hex(id(self))
        return "<" + self.__class__.__qualname__ + " object: '" + name + "'>"