Exemplo n.º 1
0
    def createHullMesh(self, hull_points):
        mesh = MeshData()
        if len(hull_points) > 3:
            center = (hull_points.min(0) + hull_points.max(0)) / 2.0
            mesh.addVertex(center[0], -0.1, center[1])
        else:
            return None
        for point in hull_points:
            mesh.addVertex(point[0], -0.1, point[1])
        indices = []
        for i in range(len(hull_points) - 1):
            indices.append([0, i + 1, i + 2])

        indices.append([0, mesh.getVertexCount() - 1, 1])

        mesh.addIndices(numpy.array(indices, numpy.int32))
        return mesh
Exemplo n.º 2
0
    def createHullMesh(self, hull_points):
        mesh = MeshData()
        if len(hull_points) > 3:
            center = (hull_points.min(0) + hull_points.max(0)) / 2.0
            mesh.addVertex(center[0], -0.1, center[1])
        else:
            return None
        for point in hull_points:
            mesh.addVertex(point[0], -0.1, point[1])
        indices = []
        for i in range(len(hull_points) - 1):
            indices.append([0, i + 1, i + 2])

        indices.append([0, mesh.getVertexCount() - 1, 1])

        mesh.addIndices(numpy.array(indices, numpy.int32))
        return mesh
Exemplo n.º 3
0
    def __init__(self, node, hull, parent = None):
        super().__init__(parent)

        self.setCalculateBoundingBox(False)

        self._material = None

        self._original_parent = parent

        self._inherit_orientation = False
        self._inherit_scale = False

        self._node = node
        self._node.transformationChanged.connect(self._onNodePositionChanged)
        self._node.parentChanged.connect(self._onNodeParentChanged)
        #self._onNodePositionChanged(self._node)

        self._hull = hull

        hull_points = self._hull.getPoints()
        center = (hull_points.min(0) + hull_points.max(0)) / 2.0

        mesh = MeshData()
        mesh.addVertex(center[0], 0.1, center[1])

        for point in hull_points:
            mesh.addVertex(point[0], 0.1, point[1])

        indices = []
        for i in range(len(hull_points) - 1):
            indices.append([0, i + 1, i + 2])

        indices.append([0, mesh.getVertexCount() - 1, 1])

        mesh.addIndices(numpy.array(indices, numpy.int32))

        self.setMeshData(mesh)
Exemplo n.º 4
0
    def __init__(self, node, hull, parent = None):
        super().__init__(parent)

        self.setCalculateBoundingBox(False)

        self._material = None

        self._original_parent = parent

        self._inherit_orientation = False
        self._inherit_scale = False

        self._node = node
        self._node.transformationChanged.connect(self._onNodePositionChanged)
        self._node.parentChanged.connect(self._onNodeParentChanged)
        #self._onNodePositionChanged(self._node)

        self._hull = hull

        hull_points = self._hull.getPoints()
        mesh = MeshData()
        if len(hull_points) > 3:
            center = (hull_points.min(0) + hull_points.max(0)) / 2.0
            mesh.addVertex(center[0], 0.1, center[1])
        else: #Hull has not enough points
            return
        for point in hull_points:
            mesh.addVertex(point[0], 0.1, point[1])
        indices = []
        for i in range(len(hull_points) - 1):
            indices.append([0, i + 1, i + 2])

        indices.append([0, mesh.getVertexCount() - 1, 1])

        mesh.addIndices(numpy.array(indices, numpy.int32))

        self.setMeshData(mesh)
Exemplo n.º 5
0
# Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.

from UM.Mesh.MeshData import MeshData


@profile
def calcNormals(mesh):
    mesh.calculateNormals()


mesh = MeshData()
mesh.reserveVertexCount(99999)
for i in range(33333):
    mesh.addVertex(0, 1, 0)
    mesh.addVertex(1, 1, 0)
    mesh.addVertex(1, 0, 0)

for i in range(100):
    calcNormals(mesh)
Exemplo n.º 6
0
class SceneNode(SignalEmitter):
    class TransformSpace:
        Local = 1
        Parent = 2
        World = 3

    def __init__(self, parent = None, name = ""):
        super().__init__() # Call super to make multiple inheritence work.

        self._children = []
        self._mesh_data = None

        self._position = Vector()
        self._scale = Vector(1.0, 1.0, 1.0)
        self._mirror = Vector(1.0, 1.0, 1.0)
        self._orientation = Quaternion()

        self._transformation = None
        self._world_transformation = None

        self._derived_position = None
        self._derived_orientation = None
        self._derived_scale = None

        self._inherit_orientation = True
        self._inherit_scale = True

        self._parent = parent
        self._enabled = True
        self._selectable = False
        self._calculate_aabb = True
        self._aabb = None
        self._aabb_job = None
        self._visible = True
        self._name = name
        self._decorators = []
        self._bounding_box_mesh = None
        self.boundingBoxChanged.connect(self.calculateBoundingBoxMesh)
        self.parentChanged.connect(self._onParentChanged)

        if parent:
            parent.addChild(self)

    def __deepcopy__(self, memo):
        copy = SceneNode()
        copy.translate(self.getPosition())
        copy.setOrientation(self.getOrientation())
        copy.setScale(self.getScale())
        copy.setMeshData(deepcopy(self._mesh_data, memo))
        copy.setVisible(deepcopy(self._visible, memo))
        copy._selectable = deepcopy(self._selectable, memo)
        for decorator in self._decorators:
            copy.addDecorator(deepcopy(decorator, memo))

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


    def setCenterPosition(self, center):
        if self._mesh_data:
            m = Matrix()
            m.setByTranslation(-center)
            self._mesh_data = self._mesh_data.getTransformed(m)
            self._mesh_data.setCenterPosition(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):
        return self._parent

    def getBoundingBoxMesh(self):
        return self._bounding_box_mesh

    def calculateBoundingBoxMesh(self):
        if self._aabb:
            self._bounding_box_mesh = MeshData()
            rtf = self._aabb.maximum
            lbb = self._aabb.minimum

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

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

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

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

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

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

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

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

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

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

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

            self._bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z) #Right - Bottom - Front
            self._bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z) #Right - Bottom - Back
        else:
            self._resetAABB()

    def _onParentChanged(self, node):
        for child in self.getChildren():
            child.parentChanged.emit(self)

    decoratorsChanged = Signal()
    def addDecorator(self, decorator):
        decorator.setNode(self)
        self._decorators.append(decorator)
        self.decoratorsChanged.emit(self)

    def getDecorators(self):
        return self._decorators

    def getDecorator(self, dec_type):
        for decorator in self._decorators:
            if type(decorator) == dec_type:
                return decorator

    def removeDecorators(self):
        self._decorators = []
        self.decoratorsChanged.emit(self)

    def removeDecorator(self, dec_type):
        for decorator in self._decorators:
            if type(decorator) == dec_type:
                self._decorators.remove(decorator)
                self.decoratorsChanged.emit(self)
                break

    def callDecoration(self, function, *args, **kwargs):
        for decorator in self._decorators:
            if hasattr(decorator, function):
                try:
                    return getattr(decorator, function)(*args, **kwargs)
                except Exception as e:
                    Logger.log("e", "Exception calling decoration %s: %s", str(function), str(e))
                    return None

    def hasDecoration(self, function):
        for decorator in self._decorators:
            if hasattr(decorator, function):
                return True
        return False

    def getName(self):
        return self._name

    def setName(self, name):
        self._name = name

    ##  How many nodes is this node removed from the root
    def getDepth(self):
        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):
        if self._parent:
            self._parent.removeChild(self)
        #self._parent = scene_node

        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 wether to render or not.
    def isVisible(self):
        if self._parent != None and self._visible:
            return self._parent.isVisible()
        else:
            return self._visible

    def setVisible(self, visible):
        self._visible = visible

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

    ##  \brief Get the transformed mesh data from the scene node/object, based on the transformation of scene nodes wrt root.
    #   \returns MeshData
    def getMeshDataTransformed(self):
        #transformed_mesh = deepcopy(self._mesh_data)
        #transformed_mesh.transform(self.getWorldTransformation())
        return self._mesh_data.getTransformed(self.getWorldTransformation())

    ##  \brief Set the mesh of this node/object
    #   \param mesh_data MeshData object
    def setMeshData(self, mesh_data):
        if self._mesh_data:
            self._mesh_data.dataChanged.disconnect(self._onMeshDataChanged)
        self._mesh_data = mesh_data
        if self._mesh_data is not None:
            self._mesh_data.dataChanged.connect(self._onMeshDataChanged)
        self._resetAABB()
        self.meshDataChanged.emit(self)

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


    def _onMeshDataChanged(self):
        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):
        if scene_node not in self._children:
            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):
        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.childrenChanged.emit(self)

    ##  \brief Removes all children and its children's children.
    def removeAllChildren(self):
        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):
        return self._children

    def hasChildren(self):
        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):
        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):
        if self._world_transformation is None:
            self._updateTransformation()

        return deepcopy(self._world_transformation)

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

        return deepcopy(self._transformation)

    ##  Get the local orientation value.
    def getOrientation(self):
        return deepcopy(self._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, transform_space = TransformSpace.Local):
        if not self._enabled:
            return

        if transform_space == SceneNode.TransformSpace.Local:
            self._orientation = self._orientation * rotation
        elif transform_space == SceneNode.TransformSpace.Parent:
            self._orientation = rotation * self._orientation
        elif transform_space == SceneNode.TransformSpace.World:
            self._orientation = self._orientation * self._getDerivedOrientation().getInverse() * rotation * self._getDerivedOrientation()
        else:
            raise ValueError("Unknown transform space {0}".format(transform_space))

        self._orientation.normalize()
        self._transformChanged()

    ##  Set the local orientation of this scene node.
    #
    #   \param orientation \type{Quaternion} The new orientation of this scene node.
    def setOrientation(self, orientation):
        if not self._enabled or orientation == self._orientation:
            return

        self._orientation = orientation
        self._orientation.normalize()
        self._transformChanged()

    ##  Get the local scaling value.
    def getScale(self):
        return deepcopy(self._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, transform_space = TransformSpace.Local):
        if not self._enabled:
            return

        if transform_space == SceneNode.TransformSpace.Local:
            self._scale = self._scale.scale(scale)
        elif transform_space == SceneNode.TransformSpace.Parent:
            raise NotImplementedError()
        elif transform_space == SceneNode.TransformSpace.World:
            if self._parent:
                scale_change = Vector(1,1,1) - scale

                if scale_change.x < 0 or scale_change.y < 0 or scale_change.z < 0:
                    direction = -1
                else:
                    direction = 1
                # Hackish way to do this, but this seems to correctly scale the object.
                change_vector = self._scale.scale(self._getDerivedOrientation().getInverse().rotate(scale_change))

                if change_vector.x < 0 and direction == 1:
                    change_vector.setX(-change_vector.x)
                if change_vector.x > 0 and direction == -1:
                    change_vector.setX(-change_vector.x)

                if change_vector.y < 0 and direction == 1:
                    change_vector.setY(-change_vector.y)
                if change_vector.y > 0 and direction == -1:
                    change_vector.setY(-change_vector.y)

                if change_vector.z < 0 and direction == 1:
                    change_vector.setZ(-change_vector.z)
                if change_vector.z > 0 and direction == -1:
                    change_vector.setZ(-change_vector.z)

                self._scale -= self._scale.scale(change_vector)
        else:
            raise ValueError("Unknown transform space {0}".format(transform_space))

        self._transformChanged()

    ##  Set the local scale value.
    #
    #   \param scale \type{Vector} The new scale value of the scene node.
    def setScale(self, scale):
        if not self._enabled or scale == self._scale:
            return

        self._scale = scale
        self._transformChanged()

    ##  Get the local mirror values.
    #
    #   \return The mirror values as vector of scaling values.
    def getMirror(self):
        return deepcopy(self._mirror)

    ##  Mirror the scene object (and thus its children) in the given directions.
    #
    #   \param mirror \type{Vector} A vector of three scale values that is used
    #   to mirror the node.
    #   \param transform_space The space relative to which to scale. Can be any
    #   one of the constants in SceneNode::TransformSpace.
    def mirror(self, mirror, transform_space = TransformSpace.Local):
        if not self._enabled:
            return

        if transform_space == SceneNode.TransformSpace.Local:
            self._mirror *= mirror
        elif transform_space == SceneNode.TransformSpace.Parent:
            self._mirror *= mirror
        elif transform_space == SceneNode.TransformSpace.World:
            self._mirror *= mirror
        else:
            raise ValueError("Unknown transform space {0}".format(transform_space))

        self._transformChanged()

    ##  Set the local mirror values.
    #
    #   \param mirror \type{Vector} The new mirror values as scale multipliers.
    def setMirror(self, mirror):
        if not self._enabled or mirror == self._mirror:
            return

        self._mirror = mirror
        self._transformChanged()

    ##  Get the local position.
    def getPosition(self):
        return deepcopy(self._position)

    ##  Get the position of this scene node relative to the world.
    def getWorldPosition(self):
        if not self._derived_position:
            self._updateTransformation()

        return deepcopy(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, transform_space = TransformSpace.Local):
        if not self._enabled:
            return

        if transform_space == SceneNode.TransformSpace.Local:
            self._position += self._orientation.rotate(translation)
        elif transform_space == SceneNode.TransformSpace.Parent:
            self._position += translation
        elif transform_space == SceneNode.TransformSpace.World:
            if self._parent:
                self._position += (1.0 / self._parent._getDerivedScale()).scale(self._parent._getDerivedOrientation().getInverse().rotate(translation))
            else:
                self._position += translation

        self._transformChanged()

    ##  Set the local position value.
    #
    #   \param position The new position value of the SceneNode.
    def setPosition(self, position):
        if not self._enabled or position == self._position:
            return

        self._position = position
        self._transformChanged()

    ##  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, up = Vector.Unit_Y):
        if not self._enabled:
            return

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

        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]
        ])

        if self._parent:
            self._orientation = self._parent._getDerivedOrientation() * Quaternion.fromMatrix(m)
        else:
            self._orientation = Quaternion.fromMatrix(m)
        self._transformChanged()

    ##  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):
        return False

    ##  Get whether this SceneNode is enabled, that is, it can be modified in any way.
    def isEnabled(self):
        if self._parent != 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):
        self._enabled = enable

    ##  Get whether this SceneNode can be selected.
    #
    #   \note This will return false if isEnabled() returns false.
    def isSelectable(self):
        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):
        self._selectable = select

    ##  Get the bounding box of this node and its children.
    #
    #   Note that the AABB is calculated in a separate thread. This method will return an invalid (size 0) AABB
    #   while the calculation happens.
    def getBoundingBox(self):
        if self._aabb:
            return self._aabb

        if not self._aabb_job:
            self._resetAABB()

        return AxisAlignedBox()

    ##  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):
        self._calculate_aabb = calculate

    boundingBoxChanged = Signal()

    ##  private:
    def _getDerivedPosition(self):
        if not self._derived_position:
            self._updateTransformation()

        return self._derived_position

    def _getDerivedOrientation(self):
        if not self._derived_orientation:
            self._updateTransformation()

        # Sometimes the derived orientation can be None.
        # I've not been able to locate the cause of this, but this prevents it being an issue.
        if not self._derived_orientation:
            self._derived_orientation = Quaternion()
        return self._derived_orientation

    def _getDerivedScale(self):
        if not self._derived_scale:
            self._updateTransformation()

        return self._derived_scale

    def _transformChanged(self):
        self._resetAABB()
        self._transformation = None
        self._world_transformation = None
        self._derived_position = None
        self._derived_orientation = None
        self._derived_scale = None

        self.transformationChanged.emit(self)

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

    def _updateTransformation(self):
        scale_and_mirror = self._scale * self._mirror
        self._transformation = Matrix.fromPositionOrientationScale(self._position, self._orientation, scale_and_mirror)

        if self._parent:
            parent_orientation = self._parent._getDerivedOrientation()
            if self._inherit_orientation:
                self._derived_orientation = parent_orientation * self._orientation
            else:
                self._derived_orientation = self._orientation

            # Sometimes the derived orientation can be None.
            # I've not been able to locate the cause of this, but this prevents it being an issue.
            if not self._derived_orientation:
                self._derived_orientation = Quaternion()

            parent_scale = self._parent._getDerivedScale()
            if self._inherit_scale:
                self._derived_scale = parent_scale.scale(scale_and_mirror)
            else:
                self._derived_scale = scale_and_mirror

            self._derived_position = parent_orientation.rotate(parent_scale.scale(self._position))
            self._derived_position += self._parent._getDerivedPosition()

            self._world_transformation = Matrix.fromPositionOrientationScale(self._derived_position, self._derived_orientation, self._derived_scale)
        else:
            self._derived_position = self._position
            self._derived_orientation = self._orientation
            self._derived_scale = scale_and_mirror
            self._world_transformation = self._transformation

    def _resetAABB(self):
        if not self._calculate_aabb:
            return

        self._aabb = None

        if self._aabb_job:
            self._aabb_job.cancel()

        self._aabb_job = _CalculateAABBJob(self)
        self._aabb_job.start()
Exemplo n.º 7
0
class SceneNode(SignalEmitter):
    class TransformSpace:
        Local = 1
        Parent = 2
        World = 3

    def __init__(self, parent=None):
        super().__init__()  # Call super to make multiple inheritence work.

        self._children = []
        self._mesh_data = None

        self._position = Vector()
        self._scale = Vector(1.0, 1.0, 1.0)
        self._orientation = Quaternion()

        self._transformation = None
        self._world_transformation = None

        self._derived_position = None
        self._derived_orientation = None
        self._derived_scale = None

        self._inherit_orientation = True
        self._inherit_scale = True

        self._parent = parent
        self._enabled = True
        self._selectable = False
        self._calculate_aabb = True
        self._aabb = None
        self._aabb_job = None
        self._visible = True
        self._name = ""
        self._decorators = []
        self._bounding_box_mesh = None
        self.boundingBoxChanged.connect(self.calculateBoundingBoxMesh)
        self.parentChanged.connect(self._onParentChanged)

        if parent:
            parent.addChild(self)

    def __deepcopy__(self, memo):
        copy = SceneNode()
        copy.translate(self.getPosition())
        copy.setOrientation(self.getOrientation())
        copy.setScale(self.getScale())
        copy.setMeshData(deepcopy(self._mesh_data, memo))
        copy.setVisible(deepcopy(self._visible, memo))
        copy._selectable = deepcopy(self._selectable, memo)
        for decorator in self._decorators:
            copy.addDecorator(deepcopy(decorator, memo))

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

    def setCenterPosition(self, center):
        if self._mesh_data:
            m = Matrix()
            m.setByTranslation(-center)
            self._mesh_data = self._mesh_data.getTransformed(m)
            self._mesh_data.setCenterPosition(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):
        return self._parent

    def getBoundingBoxMesh(self):
        return self._bounding_box_mesh

    def calculateBoundingBoxMesh(self):
        if self._aabb:
            self._bounding_box_mesh = MeshData()
            rtf = self._aabb.maximum
            lbb = self._aabb.minimum

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

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

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

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

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

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

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

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

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

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

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

            self._bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                              rtf.z)  #Right - Bottom - Front
            self._bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                              lbb.z)  #Right - Bottom - Back
        else:
            self._resetAABB()

    def _onParentChanged(self, node):
        for child in self.getChildren():
            child.parentChanged.emit(self)

    decoratorsChanged = Signal()

    def addDecorator(self, decorator):
        decorator.setNode(self)
        self._decorators.append(decorator)
        self.decoratorsChanged.emit(self)

    def getDecorators(self):
        return self._decorators

    def getDecorator(self, dec_type):
        for decorator in self._decorators:
            if type(decorator) == dec_type:
                return decorator

    def removeDecorators(self):
        self._decorators = []
        self.decoratorsChanged.emit(self)

    def removeDecorator(self, dec_type):
        for decorator in self._decorators:
            if type(decorator) == dec_type:
                self._decorators.remove(decorator)
                self.decoratorsChanged.emit(self)
                break

    def callDecoration(self, function, *args, **kwargs):
        for decorator in self._decorators:
            if hasattr(decorator, function):
                try:
                    return getattr(decorator, function)(*args, **kwargs)
                except Exception as e:
                    Logger.log("e", "Exception calling decoration %s: %s",
                               str(function), str(e))
                    return None

    def hasDecoration(self, function):
        for decorator in self._decorators:
            if hasattr(decorator, function):
                return True
        return False

    def getName(self):
        return self._name

    def setName(self, name):
        self._name = name

    ##  How many nodes is this node removed from the root
    def getDepth(self):
        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):
        if self._parent:
            self._parent.removeChild(self)
        #self._parent = scene_node

        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 wether to render or not.
    def isVisible(self):
        if self._parent != None and self._visible:
            return self._parent.isVisible()
        else:
            return self._visible

    def setVisible(self, visible):
        self._visible = visible

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

    ##  \brief Get the transformed mesh data from the scene node/object, based on the transformation of scene nodes wrt root.
    #   \returns MeshData
    def getMeshDataTransformed(self):
        #transformed_mesh = deepcopy(self._mesh_data)
        #transformed_mesh.transform(self.getWorldTransformation())
        return self._mesh_data.getTransformed(self.getWorldTransformation())

    ##  \brief Set the mesh of this node/object
    #   \param mesh_data MeshData object
    def setMeshData(self, mesh_data):
        if self._mesh_data:
            self._mesh_data.dataChanged.disconnect(self._onMeshDataChanged)
        self._mesh_data = mesh_data
        if self._mesh_data is not None:
            self._mesh_data.dataChanged.connect(self._onMeshDataChanged)
        self._resetAABB()
        self.meshDataChanged.emit(self)

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

    def _onMeshDataChanged(self):
        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):
        if scene_node not in self._children:
            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):
        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.childrenChanged.emit(self)

    ##  \brief Removes all children and its children's children.
    def removeAllChildren(self):
        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):
        return self._children

    def hasChildren(self):
        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):
        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):
        if self._world_transformation is None:
            self._updateTransformation()

        return deepcopy(self._world_transformation)

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

        return deepcopy(self._transformation)

    ##  Get the local orientation value.
    def getOrientation(self):
        return deepcopy(self._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, transform_space=TransformSpace.Local):
        if not self._enabled:
            return

        if transform_space == SceneNode.TransformSpace.Local:
            self._orientation = self._orientation * rotation
        elif transform_space == SceneNode.TransformSpace.Parent:
            self._orientation = rotation * self._orientation
        elif transform_space == SceneNode.TransformSpace.World:
            self._orientation = self._orientation * self._getDerivedOrientation(
            ).getInverse() * rotation * self._getDerivedOrientation()
        else:
            raise ValueError(
                "Unknown transform space {0}".format(transform_space))

        self._orientation.normalize()
        self._transformChanged()

    ##  Set the local orientation of this scene node.
    #
    #   \param orientation \type{Quaternion} The new orientation of this scene node.
    def setOrientation(self, orientation):
        if not self._enabled or orientation == self._orientation:
            return

        self._orientation = orientation
        self._orientation.normalize()
        self._transformChanged()

    ##  Get the local scaling value.
    def getScale(self):
        return deepcopy(self._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, transform_space=TransformSpace.Local):
        if not self._enabled:
            return

        if transform_space == SceneNode.TransformSpace.Local:
            self._scale = self._scale.scale(scale)
        elif transform_space == SceneNode.TransformSpace.Parent:
            raise NotImplementedError()
        elif transform_space == SceneNode.TransformSpace.World:
            if self._parent:
                scale_change = Vector(1, 1, 1) - scale

                if scale_change.x < 0 or scale_change.y < 0 or scale_change.z < 0:
                    direction = -1
                else:
                    direction = 1
                # Hackish way to do this, but this seems to correctly scale the object.
                change_vector = self._scale.scale(
                    self._getDerivedOrientation().getInverse().rotate(
                        scale_change))

                if change_vector.x < 0 and direction == 1:
                    change_vector.setX(-change_vector.x)
                if change_vector.x > 0 and direction == -1:
                    change_vector.setX(-change_vector.x)

                if change_vector.y < 0 and direction == 1:
                    change_vector.setY(-change_vector.y)
                if change_vector.y > 0 and direction == -1:
                    change_vector.setY(-change_vector.y)

                if change_vector.z < 0 and direction == 1:
                    change_vector.setZ(-change_vector.z)
                if change_vector.z > 0 and direction == -1:
                    change_vector.setZ(-change_vector.z)

                self._scale -= self._scale.scale(change_vector)
        else:
            raise ValueError(
                "Unknown transform space {0}".format(transform_space))

        self._transformChanged()

    ##  Set the local scale value.
    #
    #   \param scale \type{Vector} The new scale value of the scene node.
    def setScale(self, scale):
        if not self._enabled or scale == self._scale:
            return

        self._scale = scale
        self._transformChanged()

    ##  Get the local position.
    def getPosition(self):
        return deepcopy(self._position)

    ##  Get the position of this scene node relative to the world.
    def getWorldPosition(self):
        if not self._derived_position:
            self._updateTransformation()

        return deepcopy(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, transform_space=TransformSpace.Local):
        if not self._enabled:
            return

        if transform_space == SceneNode.TransformSpace.Local:
            self._position += self._orientation.rotate(translation)
        elif transform_space == SceneNode.TransformSpace.Parent:
            self._position += translation
        elif transform_space == SceneNode.TransformSpace.World:
            if self._parent:
                self._position += (1.0 /
                                   self._parent._getDerivedScale()).scale(
                                       self._parent._getDerivedOrientation(
                                       ).getInverse().rotate(translation))
            else:
                self._position += translation

        self._transformChanged()

    ##  Set the local position value.
    #
    #   \param position The new position value of the SceneNode.
    def setPosition(self, position):
        if not self._enabled or position == self._position:
            return

        self._position = position
        self._transformChanged()

    ##  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, up=Vector.Unit_Y):
        if not self._enabled:
            return

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

        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]])

        if self._parent:
            self._orientation = self._parent._getDerivedOrientation(
            ) * Quaternion.fromMatrix(m)
        else:
            self._orientation = Quaternion.fromMatrix(m)
        self._transformChanged()

    ##  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):
        return False

    ##  Get whether this SceneNode is enabled, that is, it can be modified in any way.
    def isEnabled(self):
        if self._parent != 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):
        self._enabled = enable

    ##  Get whether this SceneNode can be selected.
    #
    #   \note This will return false if isEnabled() returns false.
    def isSelectable(self):
        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):
        self._selectable = select

    ##  Get the bounding box of this node and its children.
    #
    #   Note that the AABB is calculated in a separate thread. This method will return an invalid (size 0) AABB
    #   while the calculation happens.
    def getBoundingBox(self):
        if self._aabb:
            return self._aabb

        if not self._aabb_job:
            self._resetAABB()

        return AxisAlignedBox()

    ##  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):
        self._calculate_aabb = calculate

    boundingBoxChanged = Signal()

    ##  private:
    def _getDerivedPosition(self):
        if not self._derived_position:
            self._updateTransformation()

        return self._derived_position

    def _getDerivedOrientation(self):
        if not self._derived_orientation:
            self._updateTransformation()

        # Sometimes the derived orientation can be None.
        # I've not been able to locate the cause of this, but this prevents it being an issue.
        if not self._derived_orientation:
            self._derived_orientation = Quaternion()
        return self._derived_orientation

    def _getDerivedScale(self):
        if not self._derived_scale:
            self._updateTransformation()

        return self._derived_scale

    def _transformChanged(self):
        self._resetAABB()
        self._transformation = None
        self._world_transformation = None
        self._derived_position = None
        self._derived_orientation = None
        self._derived_scale = None

        self.transformationChanged.emit(self)

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

    def _updateTransformation(self):
        self._transformation = Matrix.fromPositionOrientationScale(
            self._position, self._orientation, self._scale)

        if self._parent:
            parent_orientation = self._parent._getDerivedOrientation()
            if self._inherit_orientation:
                self._derived_orientation = parent_orientation * self._orientation
            else:
                self._derived_orientation = self._orientation

            # Sometimes the derived orientation can be None.
            # I've not been able to locate the cause of this, but this prevents it being an issue.
            if not self._derived_orientation:
                self._derived_orientation = Quaternion()

            parent_scale = self._parent._getDerivedScale()
            if self._inherit_scale:
                self._derived_scale = parent_scale.scale(self._scale)
            else:
                self._derived_scale = self._scale

            self._derived_position = parent_orientation.rotate(
                parent_scale.scale(self._position))
            self._derived_position += self._parent._getDerivedPosition()

            self._world_transformation = Matrix.fromPositionOrientationScale(
                self._derived_position, self._derived_orientation,
                self._derived_scale)
        else:
            self._derived_position = self._position
            self._derived_orientation = self._orientation
            self._derived_scale = self._scale
            self._world_transformation = self._transformation

    def _resetAABB(self):
        if not self._calculate_aabb:
            return

        self._aabb = None

        if self._aabb_job:
            self._aabb_job.cancel()

        self._aabb_job = _CalculateAABBJob(self)
        self._aabb_job.start()
Exemplo n.º 8
0
class MeshBuilder:
    def __init__(self):
        self._mesh_data = MeshData()

    def getData(self):
        return self._mesh_data

    def addLine(self, v0, v1, **kwargs):
        self._mesh_data.addVertex(v0.x, v0.y, v0.z)
        self._mesh_data.addVertex(v1.x, v1.y, v1.z)

        color = kwargs.get("color", None)
        if color:
            self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color)
            self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color)

    def addFace(self, v0, v1, v2, **kwargs):
        normal = kwargs.get("normal", None)
        if normal:
            self._mesh_data.addFaceWithNormals(
                                v0.x, v0.y, v0.z,
                                normal.x, normal.y, normal.z,
                                v1.x, v1.y, v1.z,
                                normal.x, normal.y, normal.z,
                                v2.x, v2.y, v2.z,
                                normal.x, normal.y, normal.z
                            )
        else:
            self._mesh_data.addFace(v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z)

        color = kwargs.get("color", None)
        if color:
            self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 3, color)
            self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color)
            self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color)

    def addQuad(self, v0, v1, v2, v3, **kwargs):
        self.addFace(v0, v2, v1,
            color = kwargs.get("color"),
            normal = kwargs.get("normal")
        )
        self.addFace(v0, v3, v2,
            color = kwargs.get("color"),
            normal = kwargs.get("normal")
        )

    def addCube(self, **kwargs):
        width = kwargs["width"]
        height = kwargs["height"]
        depth = kwargs["depth"]

        center = kwargs.get("center", Vector(0, 0, 0))

        minW = -width / 2 + center.x
        maxW = width / 2 + center.x
        minH = -height / 2 + center.y
        maxH = height / 2 + center.y
        minD = -depth / 2 + center.z
        maxD = depth / 2 + center.z

        start = self._mesh_data.getVertexCount()

        verts = numpy.asarray([
            [minW, minH, maxD],
            [minW, maxH, maxD],
            [maxW, maxH, maxD],
            [maxW, minH, maxD],
            [minW, minH, minD],
            [minW, maxH, minD],
            [maxW, maxH, minD],
            [maxW, minH, minD],
        ], dtype=numpy.float32)
        self._mesh_data.addVertices(verts)

        indices = numpy.asarray([
            [start, start + 2, start + 1],
            [start, start + 3, start + 2],

            [start + 3, start + 7, start + 6],
            [start + 3, start + 6, start + 2],

            [start + 7, start + 5, start + 6],
            [start + 7, start + 4, start + 5],

            [start + 4, start + 1, start + 5],
            [start + 4, start + 0, start + 1],

            [start + 1, start + 6, start + 5],
            [start + 1, start + 2, start + 6],

            [start + 0, start + 7, start + 3],
            [start + 0, start + 4, start + 7]
        ], dtype=numpy.int32)
        self._mesh_data.addIndices(indices)

        color = kwargs.get("color", None)
        if color:
            vertex_count = self._mesh_data.getVertexCount()
            for i in range(1, 9):
                self._mesh_data.setVertexColor(vertex_count - i, color)

    def addArc(self, **kwargs):
        radius = kwargs["radius"]
        axis = kwargs["axis"]

        max_angle = kwargs.get("angle", math.pi * 2)
        center = kwargs.get("center", Vector(0, 0, 0))
        sections = kwargs.get("sections", 32)
        color = kwargs.get("color", None)

        if axis == Vector.Unit_Y:
            start = axis.cross(Vector.Unit_X).normalize() * radius
        else:
            start = axis.cross(Vector.Unit_Y).normalize() * radius

        angle_increment = max_angle / sections
        angle = 0

        point = start + center
        m = Matrix()
        while angle <= max_angle:
            self._mesh_data.addVertex(point.x, point.y, point.z)
            angle += angle_increment
            m.setByRotationAxis(angle, axis)
            point = start.multiply(m) + center
            self._mesh_data.addVertex(point.x, point.y, point.z)

            if color:
                self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color)
                self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color)

    def addDonut(self, **kwargs):
        inner_radius = kwargs["inner_radius"]
        outer_radius = kwargs["outer_radius"]
        width = kwargs["width"]

        center = kwargs.get("center", Vector(0, 0, 0))
        sections = kwargs.get("sections", 32)
        color = kwargs.get("color", None)

        angle = kwargs.get("angle", 0)
        axis = kwargs.get("axis", Vector.Unit_Y)

        vertices = []
        indices = []
        colors = []

        start = self._mesh_data.getVertexCount()

        for i in range(sections):
            v1 = start + i * 3
            v2 = v1 + 1
            v3 = v1 + 2
            v4 = v1 + 3
            v5 = v1 + 4
            v6 = v1 + 5

            if i+1 >= sections: # connect the end to the start
                v4 = start
                v5 = start + 1
                v6 = start + 2

            theta = i * math.pi / (sections / 2)
            c = math.cos(theta)
            s = math.sin(theta)

            vertices.append( [inner_radius * c, inner_radius * s, 0] )
            vertices.append( [outer_radius * c, outer_radius * s, width] )
            vertices.append( [outer_radius * c, outer_radius * s, -width] )

            indices.append( [v1, v4, v5] )
            indices.append( [v2, v1, v5] )

            indices.append( [v2, v5, v6] )
            indices.append( [v3, v2, v6] )

            indices.append( [v3, v6, v4] )
            indices.append( [v1, v3, v4] )

            if color:
                colors.append( [color.r, color.g, color.b, color.a] )
                colors.append( [color.r, color.g, color.b, color.a] )
                colors.append( [color.r, color.g, color.b, color.a] )

        matrix = Matrix()
        matrix.setByRotationAxis(angle, axis)
        vertices = numpy.asarray(vertices, dtype = numpy.float32)
        vertices = vertices.dot(matrix.getData()[0:3,0:3])
        vertices[:] += center.getData()
        self._mesh_data.addVertices(vertices)

        self._mesh_data.addIndices(numpy.asarray(indices, dtype = numpy.int32))
        self._mesh_data.addColors(numpy.asarray(colors, dtype = numpy.float32))

    def addPyramid(self, **kwargs):
        width = kwargs["width"]
        height = kwargs["height"]
        depth = kwargs["depth"]

        angle = math.radians(kwargs.get("angle", 0))
        axis = kwargs.get("axis", Vector.Unit_Y)

        center = kwargs.get("center", Vector(0, 0, 0))

        minW = -width / 2
        maxW = width / 2
        minD = -depth / 2
        maxD = depth / 2

        start = self._mesh_data.getVertexCount()

        matrix = Matrix()
        matrix.setByRotationAxis(angle, axis)
        verts = numpy.asarray([
            [minW, 0, maxD],
            [maxW, 0, maxD],
            [minW, 0, minD],
            [maxW, 0, minD],
            [0, height, 0]
        ], dtype=numpy.float32)
        verts = verts.dot(matrix.getData()[0:3,0:3])
        verts[:] += center.getData()
        self._mesh_data.addVertices(verts)

        indices = numpy.asarray([
            [start, start + 1, start + 4],
            [start + 1, start + 3, start + 4],
            [start + 3, start + 2, start + 4],
            [start + 2, start, start + 4],
            [start, start + 3, start + 1],
            [start, start + 2, start + 3]
        ], dtype=numpy.int32)
        self._mesh_data.addIndices(indices)

        color = kwargs.get("color", None)
        if color:
            vertex_count = self._mesh_data.getVertexCount()
            for i in range(1, 6):
                self._mesh_data.setVertexColor(vertex_count - i, color)
Exemplo n.º 9
0
class SceneNode():
    class TransformSpace:
        Local = 1
        Parent = 2
        World = 3

    ##  Construct a scene node.
    #   \param parent The parent of this node (if any). Only a root node should have None as a parent.
    #   \param kwargs Keyword arguments.
    #                 Possible keywords:
    #                 - visible \type{bool} Is the SceneNode (and thus, all it's children) visible? Defaults to True
    #                 - name \type{string} Name of the SceneNode. Defaults to empty string.
    def __init__(self, parent=None, **kwargs):
        super().__init__()  # Call super to make multiple inheritance work.

        self._children = []
        self._mesh_data = None

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

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

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

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

        self._parent = parent
        self._enabled = True
        self._selectable = False
        self._calculate_aabb = True
        self._aabb = None
        self._original_aabb = None
        self._aabb_job = None
        self._visible = kwargs.get("visible", True)
        self._name = kwargs.get("name", "")
        self._decorators = []

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

        if parent:
            parent.addChild(self)

    def __deepcopy__(self, memo):
        copy = SceneNode()
        copy.translate(self.getPosition())
        copy.setOrientation(self.getOrientation())
        copy.setScale(self.getScale())
        copy.setMeshData(deepcopy(self._mesh_data, memo))
        copy.setVisible(deepcopy(self._visible, memo))
        copy._selectable = deepcopy(self._selectable, memo)
        for decorator in self._decorators:
            copy.addDecorator(deepcopy(decorator, memo))

        for child in self._children:
            copy.addChild(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):
        if self._mesh_data:
            m = Matrix()
            m.setByTranslation(-center)
            self._mesh_data = self._mesh_data.getTransformed(m)
            self._mesh_data.setCenterPosition(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):
        return self._parent

    ##  Get the MeshData of the bounding box
    #   \returns \type{MeshData} Bounding box mesh.
    def getBoundingBoxMesh(self):
        return self._bounding_box_mesh

    ##  (re)Calculate the bounding box mesh.
    def calculateBoundingBoxMesh(self):
        if self._aabb:
            self._bounding_box_mesh = MeshData()
            rtf = self._aabb.maximum
            lbb = self._aabb.minimum

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

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

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

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

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

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

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

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

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

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

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

            self._bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                              rtf.z)  # Right - Bottom - Front
            self._bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                              lbb.z)  # Right - Bottom - Back
        else:
            self._resetAABB()

    ##  Handler for the ParentChanged signal
    #   \param node Node from which this event was triggered.
    def _onParentChanged(self, node):
        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.
    #   TODO: GetDecorator seems to imply that a scne node can only have a single decorator of a type, but we never enforce this.
    def addDecorator(self, decorator):
        decorator.setNode(self)
        self._decorators.append(decorator)
        self.decoratorsChanged.emit(self)

    ##  Get all SceneNodeDecorators that decorate this SceneNode.
    #   \return list of all SceneNodeDecorators.
    def getDecorators(self):
        return self._decorators

    ##  Get SceneNodeDecorators by type.
    #   \param dec_type type of decorator to return.
    def getDecorator(self, dec_type):
        for decorator in self._decorators:
            if type(decorator) == dec_type:
                return decorator

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

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

    ##  Call a decoration of this SceneNode.
    #   SceneNodeDecorators add Decorations, which are callable functions.
    #   \param \type{string} function The function to be called.
    #   \param *args
    #   \param **kwargs
    def callDecoration(self, function, *args, **kwargs):
        for decorator in self._decorators:
            if hasattr(decorator, function):
                try:
                    return getattr(decorator, function)(*args, **kwargs)
                except Exception as e:
                    Logger.log("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):
        for decorator in self._decorators:
            if hasattr(decorator, function):
                return True
        return False

    def getName(self):
        return self._name

    def setName(self, name):
        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):
        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):
        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):
        if self._parent != None and self._visible:
            return self._parent.isVisible()
        else:
            return self._visible

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

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

    ##  \brief Get the transformed mesh data from the scene node/object, based on the transformation of scene nodes wrt root.
    #   \returns MeshData
    def getMeshDataTransformed(self):
        return self._mesh_data.getTransformed(self.getWorldTransformation())

    ##  \brief Set the mesh of this node/object
    #   \param mesh_data MeshData object
    def setMeshData(self, mesh_data):
        if self._mesh_data:
            self._mesh_data.dataChanged.disconnect(self._onMeshDataChanged)
        self._mesh_data = mesh_data
        if self._mesh_data is not None:
            self._mesh_data.dataChanged.connect(self._onMeshDataChanged)
        self._resetAABB()
        self.meshDataChanged.emit(self)

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

    def _onMeshDataChanged(self):
        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):
        if scene_node not in self._children:
            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):
        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.childrenChanged.emit(self)

    ##  \brief Removes all children and its children's children.
    def removeAllChildren(self):
        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):
        return self._children

    def hasChildren(self):
        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):
        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):
        if self._world_transformation is None:
            self._updateTransformation()

        return deepcopy(self._world_transformation)

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

        return deepcopy(self._transformation)

    def setTransformation(self, transformation):
        self._transformation = transformation
        self._transformChanged()

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

    def getWorldOrientation(self):
        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, transform_space=TransformSpace.Local):
        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,
                       transform_space=TransformSpace.Local):
        if not self._enabled or orientation == self._orientation:
            return

        new_transform_matrix = Matrix()
        if transform_space == SceneNode.TransformSpace.Local:
            orientation_matrix = orientation.toMatrix()
        if transform_space == SceneNode.TransformSpace.World:
            if self.getWorldOrientation() == orientation:
                return
            new_orientation = orientation * (
                self.getWorldOrientation() *
                self._orientation.getInverse()).getInverse()
            orientation_matrix = new_orientation.toMatrix()
        euler_angles = orientation_matrix.getEuler()

        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):
        return deepcopy(self._scale)

    def getWorldScale(self):
        return deepcopy(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, transform_space=TransformSpace.Local):
        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, transform_space=TransformSpace.Local):
        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):
        return deepcopy(self._position)

    ##  Get the position of this scene node relative to the world.
    def getWorldPosition(self):
        return deepcopy(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, transform_space=TransformSpace.Local):
        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:
            self._transformation.multiply(
                self._world_transformation.getInverse())
            self._transformation.multiply(translation_matrix)
            self._transformation.multiply(self._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, transform_space=TransformSpace.Local):
        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._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, up=Vector.Unit_Y):
        if not self._enabled:
            return

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

        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):
        return False

    ##  Get whether this SceneNode is enabled, that is, it can be modified in any way.
    def isEnabled(self):
        if self._parent != 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):
        self._enabled = enable

    ##  Get whether this SceneNode can be selected.
    #
    #   \note This will return false if isEnabled() returns false.
    def isSelectable(self):
        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):
        self._selectable = select

    ##  Get the bounding box of this node and its children.
    #
    #   Note that the AABB is calculated in a separate thread. This method will return an invalid (size 0) AABB
    #   while the calculation happens.
    def getBoundingBox(self):
        if self._aabb:
            return self._aabb

        if not self._aabb_job:
            self._resetAABB()

        return AxisAlignedBox()

    ##  Get the bounding box of this node and its children. Without taking any transformation into account
    def getOriginalBoundingBox(self):
        if self._original_aabb:
            return self._original_aabb

        if not self._aabb_job:
            self._resetAABB()

        return AxisAlignedBox()

    ##  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):
        self._calculate_aabb = calculate

    boundingBoxChanged = Signal()

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

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

    def _updateTransformation(self):
        scale, shear, euler_angles, translation = self._transformation.decompose(
        )
        self._position = translation
        self._scale = scale
        self._shear = shear
        orientation = Quaternion()
        euler_angle_matrix = Matrix()
        euler_angle_matrix.setByEuler(euler_angles.x, euler_angles.y,
                                      euler_angles.z)
        orientation.setByMatrix(euler_angle_matrix)
        self._orientation = orientation
        if self._parent:
            self._world_transformation = self._parent.getWorldTransformation(
            ).multiply(self._transformation, copy=True)
        else:
            self._world_transformation = self._transformation

        world_scale, world_shear, world_euler_angles, world_translation = self._world_transformation.decompose(
        )
        self._derived_position = world_translation
        self._derived_scale = world_scale

        world_euler_angle_matrix = Matrix()
        world_euler_angle_matrix.setByEuler(world_euler_angles.x,
                                            world_euler_angles.y,
                                            world_euler_angles.z)
        self._derived_orientation.setByMatrix(world_euler_angle_matrix)

        world_scale, world_shear, world_euler_angles, world_translation = self._world_transformation.decompose(
        )

    def _resetAABB(self):
        if not self._calculate_aabb:
            return

        self._aabb = None

        if self._aabb_job:
            self._aabb_job.cancel()

        self._aabb_job = _CalculateAABBJob(self)
        self._aabb_job.start()
Exemplo n.º 10
0
    def _rebuild(self):
        lines = MeshData()

        offset = 0
        if self.YAxis in self._enabled_axis:
            lines.addVertex(0, 0, 0)
            lines.addVertex(0, 20, 0)
            lines.setVertexColor(offset, ToolHandle.YAxisColor)
            lines.setVertexColor(offset + 1, ToolHandle.YAxisColor)
            offset += 2

        if self.XAxis in self._enabled_axis:
            lines.addVertex(0, 0, 0)
            lines.addVertex(20, 0, 0)
            lines.setVertexColor(offset, ToolHandle.XAxisColor)
            lines.setVertexColor(offset + 1, ToolHandle.XAxisColor)
            offset += 2

        if self.ZAxis in self._enabled_axis:
            lines.addVertex(0, 0, 0)
            lines.addVertex(0, 0, 20)
            lines.setVertexColor(offset, ToolHandle.ZAxisColor)
            lines.setVertexColor(offset + 1, ToolHandle.ZAxisColor)
            offset += 2

        self.setLineMesh(lines)

        mb = MeshBuilder()

        if self.YAxis in self._enabled_axis:
            mb.addPyramid(
                width = 2,
                height = 4,
                depth = 2,
                center = Vector(0, 20, 0),
                color = ToolHandle.YAxisColor
            )

        if self.XAxis in self._enabled_axis:
            mb.addPyramid(
                width = 2,
                height = 4,
                depth = 2,
                center = Vector(20, 0, 0),
                color = ToolHandle.XAxisColor,
                axis = Vector.Unit_Z,
                angle = 90
            )

        if self.ZAxis in self._enabled_axis:
            mb.addPyramid(
                width = 2,
                height = 4,
                depth = 2,
                center = Vector(0, 0, 20),
                color = ToolHandle.ZAxisColor,
                axis = Vector.Unit_X,
                angle = -90
            )

        self.setSolidMesh(mb.getData())

        mb = MeshBuilder()

        if self.YAxis in self._enabled_axis:
            mb.addCube(
                width = 4,
                height = 20,
                depth = 4,
                center = Vector(0, 10, 0),
                color = ToolHandle.YAxisColor
            )

            mb.addPyramid(
                width = 4,
                height = 8,
                depth = 4,
                center = Vector(0, 20, 0),
                color = ToolHandle.YAxisColor
            )

        if self.XAxis in self._enabled_axis:
            mb.addCube(
                width = 20,
                height = 4,
                depth = 4,
                center = Vector(10, 0, 0),
                color = ToolHandle.XAxisColor
            )

            mb.addPyramid(
                width = 4,
                height = 8,
                depth = 4,
                center = Vector(20, 0, 0),
                color = ToolHandle.XAxisColor,
                axis = Vector.Unit_Z,
                angle = 90
            )

        if self.ZAxis in self._enabled_axis:
            mb.addCube(
                width = 4,
                height = 4,
                depth = 20,
                center = Vector(0, 0, 10),
                color = ToolHandle.ZAxisColor
            )

            mb.addPyramid(
                width = 4,
                height = 8,
                depth = 4,
                center = Vector(0, 0, 20),
                color = ToolHandle.ZAxisColor,
                axis = Vector.Unit_X,
                angle = -90
            )

        self.setSelectionMesh(mb.getData())
Exemplo n.º 11
0
class MeshBuilder:
    ##  Creates a new MeshBuilder with an empty mesh.
    def __init__(self):
        self._mesh_data = MeshData()

    ##  Gets the mesh that was built by this MeshBuilder.
    #
    #   Note that this gets a reference to the mesh. Adding more primitives to
    #   the MeshBuilder will cause the mesh returned by this function to change
    #   as well.
    #
    #   \return A mesh with all the primitives added to this MeshBuilder.
    def getData(self):
        return self._mesh_data

    ##  Adds a 3-dimensional line to the mesh of this mesh builder.
    #
    #   \param v0 One endpoint of the line to add.
    #   \param v1 The other endpoint of the line to add.
    #   \param color (Optional) The colour of the line, if any. If no colour is
    #   provided, the colour is determined by the shader.
    def addLine(self, v0, v1, color = None):
        self._mesh_data.addVertex(v0.x, v0.y, v0.z)
        self._mesh_data.addVertex(v1.x, v1.y, v1.z)

        if color: #Add colours to the vertices, if we have them.
            self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color)
            self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color)

    ##  Adds a triangle to the mesh of this mesh builder.
    #
    #   \param v0 The first corner of the triangle.
    #   \param v1 The second corner of the triangle.
    #   \param v2 The third corner of the triangle.
    #   \param normal (Optional) The normal vector for the triangle. If no
    #   normal vector is provided, it will be calculated automatically.
    #   \param color (Optional) The colour for the triangle. If no colour is
    #   provided, the colour is determined by the shader.
    def addFace(self, v0, v1, v2, normal = None, color = None):
        if normal:
            self._mesh_data.addFaceWithNormals(
                                v0.x, v0.y, v0.z,
                                normal.x, normal.y, normal.z,
                                v1.x, v1.y, v1.z,
                                normal.x, normal.y, normal.z,
                                v2.x, v2.y, v2.z,
                                normal.x, normal.y, normal.z
                            )
        else:
            self._mesh_data.addFace(v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z) #Computes the normal by itself.

        if color: #Add colours to the vertices if we have them.
            self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 3, color)
            self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color)
            self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color)

    ##  Add a quadrilateral to the mesh of this mesh builder.
    #
    #   The quadrilateral will be constructed as two triangles. v0 and v2 are
    #   the two vertices across the diagonal of the quadrilateral.
    #
    #   \param v0 The first corner of the quadrilateral.
    #   \param v1 The second corner of the quadrilateral.
    #   \param v2 The third corner of the quadrilateral.
    #   \param v3 The fourth corner of the quadrilateral.
    #   \param normal (Optional) The normal vector for the quadrilateral. Both
    #   triangles will get the same normal vector, if provided. If no normal
    #   vector is provided, the normal vectors for both triangles are computed
    #   automatically.
    #   \param color (Optional) The colour for the quadrilateral. If no colour
    #   is provided, the colour is determined by the shader.
    def addQuad(self, v0, v1, v2, v3, normal = None, color = None):
        self.addFace(v0, v2, v1,
            color = color,
            normal = normal
        )
        self.addFace(v0, v3, v2, #v0 and v2 are shared with the other triangle!
            color = color,
            normal = normal
        )

    ##  Add a rectangular cuboid to the mesh of this mesh builder.
    #
    #   A rectangular cuboid is a square block with arbitrary width, height and
    #   depth.
    #
    #   \param width The size of the rectangular cuboid in the X dimension.
    #   \param height The size of the rectangular cuboid in the Y dimension.
    #   \param depth The size of the rectangular cuboid in the Z dimension.
    #   \param center (Optional) The position of the centre of the rectangular
    #   cuboid in space. If not provided, the cuboid is placed at the coordinate
    #   origin.
    #   \param color (Optional) The colour for the rectangular cuboid. If no
    #   colour is provided, the colour is determined by the shader.
    def addCube(self, width, height, depth, center = Vector(0, 0, 0), color = None):
        #Compute the actual positions of the planes.
        minW = -width / 2 + center.x
        maxW = width / 2 + center.x
        minH = -height / 2 + center.y
        maxH = height / 2 + center.y
        minD = -depth / 2 + center.z
        maxD = depth / 2 + center.z

        start = self._mesh_data.getVertexCount()

        verts = numpy.asarray([ #All 8 corners.
            [minW, minH, maxD],
            [minW, maxH, maxD],
            [maxW, maxH, maxD],
            [maxW, minH, maxD],
            [minW, minH, minD],
            [minW, maxH, minD],
            [maxW, maxH, minD],
            [maxW, minH, minD],
        ], dtype=numpy.float32)
        self._mesh_data.addVertices(verts)

        indices = numpy.asarray([ #All 6 quads (12 triangles).
            [start, start + 2, start + 1],
            [start, start + 3, start + 2],

            [start + 3, start + 7, start + 6],
            [start + 3, start + 6, start + 2],

            [start + 7, start + 5, start + 6],
            [start + 7, start + 4, start + 5],

            [start + 4, start + 1, start + 5],
            [start + 4, start + 0, start + 1],

            [start + 1, start + 6, start + 5],
            [start + 1, start + 2, start + 6],

            [start + 0, start + 7, start + 3],
            [start + 0, start + 4, start + 7]
        ], dtype=numpy.int32)
        self._mesh_data.addIndices(indices)

        if color: #If we have a colour, add a colour to all of the vertices.
            vertex_count = self._mesh_data.getVertexCount()
            for i in range(1, 9):
                self._mesh_data.setVertexColor(vertex_count - i, color)

    ##  Add an arc to the mesh of this mesh builder.
    #
    #   An arc is a curve that is also a segment of a circle.
    #
    #   \param radius The radius of the circle this arc is a segment of.
    #   \param axis The axis perpendicular to the plane on which the arc lies.
    #   \param angle (Optional) The length of the arc, in radians. If not
    #   provided, the entire circle is used (2 pi).
    #   \param center (Optional) The position of the centre of the arc in space.
    #   If no position is provided, the arc is centred around the coordinate
    #   origin.
    #   \param sections (Optional) The resolution of the arc. The arc is
    #   approximated by this number of line segments.
    #   \param color (Optional) The colour for the arc. If no colour is
    #   provided, the colour is determined by the shader.
    def addArc(self, radius, axis, angle = math.pi * 2, center = Vector(0, 0, 0), sections = 32, color = None):
        #We'll compute the vertices of the arc by computing an initial point and
        #rotating the initial point with a rotation matrix.
        if axis == Vector.Unit_Y:
            start = axis.cross(Vector.Unit_X).normalize() * radius
        else:
            start = axis.cross(Vector.Unit_Y).normalize() * radius

        angle_increment = angle / sections
        current_angle = 0

        point = start + center
        m = Matrix()
        while current_angle <= angle: #Add each of the vertices.
            self._mesh_data.addVertex(point.x, point.y, point.z)
            current_angle += angle_increment
            m.setByRotationAxis(current_angle, axis)
            point = start.multiply(m) + center #Get the next vertex by rotating the start position with a matrix.
            self._mesh_data.addVertex(point.x, point.y, point.z)

            if color: #If we have a colour, add that colour to the new vertex.
                self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color)
                self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color)

    ##  Adds a torus to the mesh of this mesh builder.
    #
    #   The torus is the shape of a doughnut. This doughnut is delicious and
    #   moist, but not very healthy.
    #
    #   \param inner_radius The radius of the hole inside the torus. Must be
    #   smaller than outer_radius.
    #   \param outer_radius The radius of the outside of the torus. Must be
    #   larger than inner_radius.
    #   \param width The radius of the torus in perpendicular direction to its
    #   perimeter. This is the "thickness".
    #   \param center (Optional) The position of the centre of the torus. If no
    #   position is provided, the torus will be centred around the coordinate
    #   origin.
    #   \param sections (Optional) The resolution of the torus in the
    #   circumference. The resolution of the intersection of the torus cannot be
    #   changed.
    #   \param color (Optional) The colour of the torus. If no colour is
    #   provided, a colour will be determined by the shader.
    #   \param angle (Optional) An angle of rotation to rotate the torus by, in
    #   radians.
    #   \param axis (Optional) An axis of rotation to rotate the torus around.
    #   If no axis is provided and the angle of rotation is nonzero, the torus
    #   will be rotated around the Y-axis.
    def addDonut(self, inner_radius, outer_radius, width, center = Vector(0, 0, 0), sections = 32, color = None, angle = 0, axis = Vector.Unit_Y):
        vertices = []
        indices = []
        colors = []

        start = self._mesh_data.getVertexCount() #Starting index.

        for i in range(sections):
            v1 = start + i * 3 #Indices for each of the vertices we'll add for this section.
            v2 = v1 + 1
            v3 = v1 + 2
            v4 = v1 + 3
            v5 = v1 + 4
            v6 = v1 + 5

            if i+1 >= sections: # connect the end to the start
                v4 = start
                v5 = start + 1
                v6 = start + 2

            theta = i * math.pi / (sections / 2) #Angle of this piece around torus perimeter.
            c = math.cos(theta) #X-coordinate around torus perimeter.
            s = math.sin(theta) #Y-coordinate around torus perimeter.

            #One vertex on the inside perimeter, two on the outside perimiter (up and down).
            vertices.append( [inner_radius * c, inner_radius * s, 0] )
            vertices.append( [outer_radius * c, outer_radius * s, width] )
            vertices.append( [outer_radius * c, outer_radius * s, -width] )

            #Connect the vertices to the next segment.
            indices.append( [v1, v4, v5] )
            indices.append( [v2, v1, v5] )

            indices.append( [v2, v5, v6] )
            indices.append( [v3, v2, v6] )

            indices.append( [v3, v6, v4] )
            indices.append( [v1, v3, v4] )

            if color: #If we have a colour, add it to the vertices.
                colors.append( [color.r, color.g, color.b, color.a] )
                colors.append( [color.r, color.g, color.b, color.a] )
                colors.append( [color.r, color.g, color.b, color.a] )

        #Rotate the resulting torus around the specified axis.
        matrix = Matrix()
        matrix.setByRotationAxis(angle, axis)
        vertices = numpy.asarray(vertices, dtype = numpy.float32)
        vertices = vertices.dot(matrix.getData()[0:3, 0:3])
        vertices[:] += center.getData() #And translate to the desired position.

        self._mesh_data.addVertices(vertices)
        self._mesh_data.addIndices(numpy.asarray(indices, dtype = numpy.int32))
        self._mesh_data.addColors(numpy.asarray(colors, dtype = numpy.float32))

    ##  Adds a pyramid to the mesh of this mesh builder.
    #
    #   \param width The width of the base of the pyramid.
    #   \param height The height of the pyramid (from base to notch).
    #   \param depth The depth of the base of the pyramid.
    #   \param angle (Optional) An angle of rotation to rotate the pyramid by,
    #   in degrees.
    #   \param axis (Optional) An axis of rotation to rotate the pyramid around.
    #   If no axis is provided and the angle of rotation is nonzero, the pyramid
    #   will be rotated around the Y-axis.
    #   \param center (Optional) The position of the centre of the base of the
    #   pyramid. If not provided, the pyramid will be placed on the coordinate
    #   origin.
    #   \param color (Optional) The colour of the pyramid. If no colour is
    #   provided, a colour will be determined by the shader.
    def addPyramid(self, width, height, depth, angle = 0, axis = Vector.Unit_Y, center = Vector(0, 0, 0), color = None):
        angle = math.radians(angle)

        minW = -width / 2
        maxW = width / 2
        minD = -depth / 2
        maxD = depth / 2

        start = self._mesh_data.getVertexCount() #Starting index.

        matrix = Matrix()
        matrix.setByRotationAxis(angle, axis)
        verts = numpy.asarray([ #All 5 vertices of the pyramid.
            [minW, 0, maxD],
            [maxW, 0, maxD],
            [minW, 0, minD],
            [maxW, 0, minD],
            [0, height, 0]
        ], dtype=numpy.float32)
        verts = verts.dot(matrix.getData()[0:3,0:3]) #Rotate the pyramid around the axis.
        verts[:] += center.getData()
        self._mesh_data.addVertices(verts)

        indices = numpy.asarray([ #Connect the vertices to each other (6 triangles).
            [start, start + 1, start + 4], #The four sides of the pyramid.
            [start + 1, start + 3, start + 4],
            [start + 3, start + 2, start + 4],
            [start + 2, start, start + 4],
            [start, start + 3, start + 1], #The base of the pyramid.
            [start, start + 2, start + 3]
        ], dtype=numpy.int32)
        self._mesh_data.addIndices(indices)

        if color: #If we have a colour, add the colour to each of the vertices.
            vertex_count = self._mesh_data.getVertexCount()
            for i in range(1, 6):
                self._mesh_data.setVertexColor(vertex_count - i, color)
Exemplo n.º 12
0
    def __init__(self, parent = None):
        super().__init__(parent)

        lines = MeshData()
        lines.addVertex(0, 0, 0)
        lines.addVertex(0, 20, 0)
        lines.addVertex(0, 0, 0)
        lines.addVertex(20, 0, 0)
        lines.addVertex(0, 0, 0)
        lines.addVertex(0, 0, 20)

        lines.setVertexColor(0, ToolHandle.YAxisColor)
        lines.setVertexColor(1, ToolHandle.YAxisColor)
        lines.setVertexColor(2, ToolHandle.XAxisColor)
        lines.setVertexColor(3, ToolHandle.XAxisColor)
        lines.setVertexColor(4, ToolHandle.ZAxisColor)
        lines.setVertexColor(5, ToolHandle.ZAxisColor)

        self.setLineMesh(lines)

        mb = MeshBuilder()

        mb.addCube(
            width = 2,
            height = 2,
            depth = 2,
            center = Vector(0, 0, 0),
            color = ToolHandle.AllAxisColor
        )

        mb.addCube(
            width = 2,
            height = 2,
            depth = 2,
            center = Vector(0, 20, 0),
            color = ToolHandle.YAxisColor
        )

        mb.addCube(
            width = 2,
            height = 2,
            depth = 2,
            center = Vector(20, 0, 0),
            color = ToolHandle.XAxisColor
        )

        mb.addCube(
            width = 2,
            height = 2,
            depth = 2,
            center = Vector(0, 0, 20),
            color = ToolHandle.ZAxisColor
        )

        self.setSolidMesh(mb.getData())

        mb = MeshBuilder()

        mb.addCube(
            width = 4,
            height = 20,
            depth = 4,
            center = Vector(0, 10, 0),
            color = ToolHandle.YAxisColor
        )

        mb.addCube(
            width = 4,
            height = 4,
            depth = 4,
            center = Vector(0, 20, 0),
            color = ToolHandle.YAxisColor
        )

        mb.addCube(
            width = 20,
            height = 4,
            depth = 4,
            center = Vector(10, 0, 0),
            color = ToolHandle.XAxisColor
        )

        mb.addCube(
            width = 4,
            height = 4,
            depth = 4,
            center = Vector(20, 0, 0),
            color = ToolHandle.XAxisColor
        )

        mb.addCube(
            width = 4,
            height = 4,
            depth = 20,
            center = Vector(0, 0, 10),
            color = ToolHandle.ZAxisColor
        )

        mb.addCube(
            width = 4,
            height = 4,
            depth = 4,
            center = Vector(0, 0, 20),
            color = ToolHandle.ZAxisColor
        )

        mb.addCube(
            width = 8,
            height = 8,
            depth = 8,
            center = Vector(0, 0, 0),
            color = ToolHandle.AllAxisColor
        )

        self.setSelectionMesh(mb.getData())
Exemplo n.º 13
0
# Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the AGPLv3 or higher.

from UM.Mesh.MeshData import MeshData


@profile
def getByteArray(mesh):
    return mesh.getVerticesAsByteArray()


mesh = MeshData()
mesh.reserveVertexCount(10000)
for i in range(10000):
    mesh.addVertex(0, 1, 0)

for i in range(100):
    a = getByteArray(mesh)
Exemplo n.º 14
0
class PathResultDecorator(SceneNodeDecorator):
    _move_color = Color(0, 0, 0, 128)
    _cut_color = Color(255, 0, 0, 255)
    _engrave_color = Color(0, 0, 255, 255)

    def __init__(self):
        super().__init__()
        self._paths = None
        
    def getCutPaths(self):
        return self._cut_paths

    def getEngravePaths(self):
        return self._engrave_paths
    
    def setPaths(self, engrave_paths, cut_paths):
        self._cut_paths = cut_paths
        self._engrave_paths = engrave_paths
        self._mesh = MeshData()

        last_point = None

        for path in engrave_paths.closed_paths:
            if last_point is not None:
                self._addMeshLine(last_point, path[0], self._move_color)

            last_point = path[0]
            for point in path[1:]:
                self._addMeshLine(last_point, point, self._engrave_color)
                last_point = point
            self._addMeshLine(last_point, path[0], self._engrave_color)
            last_point = path[0]

        for path in engrave_paths.open_paths:
            if last_point is not None:
                self._addMeshLine(last_point, path[0], self._move_color)

            last_point = path[0]
            for point in path[1:]:
                self._addMeshLine(last_point, point, self._engrave_color)
                last_point = point

        for path in cut_paths.closed_paths:
            if last_point is not None:
                self._addMeshLine(last_point, path[0], self._move_color)

            last_point = path[0]
            for point in path[1:]:
                self._addMeshLine(last_point, point, self._cut_color)
                last_point = point
            self._addMeshLine(last_point, path[0], self._cut_color)
            last_point = path[0]

        for path in cut_paths.open_paths:
            if last_point is not None:
                self._addMeshLine(last_point, path[0], self._move_color)

            last_point = path[0]
            for point in path[1:]:
                self._addMeshLine(last_point, point, self._cut_color)
                last_point = point

        self.getNode().setMeshData(self._mesh)

    def _addMeshLine(self, p0, p1, color):
        self._mesh.addVertex(p0[0] / Paths.SCALE, 0.0, p0[1] / Paths.SCALE)
        self._mesh.addVertex(p1[0] / Paths.SCALE, 0.0, p1[1] / Paths.SCALE)
        self._mesh.setVertexColor(self._mesh.getVertexCount() - 2, color)
        self._mesh.setVertexColor(self._mesh.getVertexCount() - 1, color)
Exemplo n.º 15
0
class DXFReader(MeshReader):
    def __init__(self):
        super(DXFReader, self).__init__()
        self._supported_extensions = [".dxf"]

        self._dxf = None
        self._mesh = None

    def read(self, file_name):
        self._dxf = DXFObjectReader.DXFObjectReader(open(file_name, "rt"))
        self._mesh = MeshData()
        for obj in self._dxf:
            if obj.getName() == "SECTION":
                if obj.get(2) == "ENTITIES":
                    self._handleEntities()
                elif obj.get(2) == "TABLES":
                    self._handleTables()
                else:
                    Logger.log("d", "DXF: Got unknown section: %s", obj.get(2))
                    for obj in self._dxf:
                        if obj.getName() == "ENDSEC":
                            break
                        else:
                            Logger.log("d", "DXF: %s", obj)
            elif obj.getName() == "EOF":
                pass
            else:
                Logger.log("e", "DXF: Unexpected object: %s", obj)

        #self._mesh.calculateNormals()

        node = SceneNode()
        node.setMeshData(self._mesh)
        return node

    def _handleTables(self):
        for obj in self._dxf:
            if obj.getName() == "ENDSEC":
                break
            elif obj.getName() == "TABLE":
                if obj.get(2) == "":
                    pass
                else:
                    Logger.log("d", "DXF: Got unknown table: %s", obj.get(2))
                    for obj in self._dxf:
                        if obj.getName() == "ENDTAB":
                            break
                        else:
                            Logger.log("d", "DXF: %s", obj)
            else:
                Logger.log("e", "DXF: Unexpected object in tables section: %s", obj)

    def _handleEntities(self):
        for obj in self._dxf:
            if obj.getName() == "ENDSEC":
                break
            elif obj.getName() == "LINE":
                self._addLine(obj.get(10), obj.get(20), obj.get(11), obj.get(21))
            elif obj.getName() == "LWPOLYLINE":
                for n in range(0, obj.count(20) - 1):
                    self._addLine(obj.get(10, n), obj.get(20, n), obj.get(10, n + 1), obj.get(20, n + 1))
            elif obj.getName() == "ARC":
                cx = float(obj.get(10))
                cy = float(obj.get(20))
                r = float(obj.get(40))
                a_start = float(obj.get(50))
                a_end = float(obj.get(51))
                if a_end < a_start:
                    a_end += 360.0
                steps = math.ceil(((2.0 * math.pi * r) * (a_end - a_start) / 360.0) / 0.5)
                for n in range(0, steps):
                    a0 = a_start + (a_end - a_start) * n / steps
                    a1 = a_start + (a_end - a_start) * (n + 1) / steps
                    self._addLine(
                        cx + math.cos(math.radians(a0)) * r, cy + math.sin(math.radians(a0)) * r,
                        cx + math.cos(math.radians(a1)) * r, cy + math.sin(math.radians(a1)) * r
                    )
            elif obj.getName() == "CIRCLE":
                cx = float(obj.get(10))
                cy = float(obj.get(20))
                r = float(obj.get(40))
                steps = math.ceil((2.0 * math.pi * r) / 0.5)
                for n in range(0, steps):
                    a0 = 360.0 * n / steps
                    a1 = 360.0 * (n + 1) / steps
                    self._addLine(
                        cx + math.cos(math.radians(a0)) * r, cy + math.sin(math.radians(a0)) * r,
                        cx + math.cos(math.radians(a1)) * r, cy + math.sin(math.radians(a1)) * r
                    )
            elif obj.getName() == "ELLIPSE":
                cx = float(obj.get(10))
                cy = float(obj.get(20))
                emx = float(obj.get(11))
                emy = float(obj.get(21))
                r_major = math.sqrt(emx * emx + emy * emy)
                r_minor = r_major * float(obj.get(40))
                a_start = math.degrees(float(obj.get(41)))
                a_end = math.degrees(float(obj.get(42)))
                if a_end < a_start:
                    a_end += 360.0
                steps = math.ceil(((2.0 * math.pi * r) * (a_end - a_start) / 360.0) / 0.5)
                for n in range(0, steps):
                    a0 = a_start + (a_end - a_start) * n / steps
                    a1 = a_start + (a_end - a_start) * (n + 1) / steps
                    self._addLine(
                        cx + math.cos(math.radians(a0)) * r_major, cy + math.sin(math.radians(a0)) * r_minor,
                        cx + math.cos(math.radians(a1)) * r_major, cy + math.sin(math.radians(a1)) * r_minor
                    )
            elif obj.getName() == "SPLINE":
                nurbs = NURBS.NURBS(int(obj.get(71)))
                for n in range(0, obj.count(40)):
                    nurbs.addKnot(float(obj.get(40, n)))
                for n in range(0, obj.count(10)):
                    nurbs.addPoint(float(obj.get(10, n)), float(obj.get(20, n)))
                points = nurbs.calculate(2)
                distance = math.sqrt((points[0][0] - points[-1][0]) * (points[0][0] - points[-1][0]) + (points[0][1] - points[-1][1]) * (points[0][1] - points[-1][1]))
                if distance < 1.0:
                    point_count = int(max(2, distance / 0.1))
                elif distance < 5.0:
                    point_count = int(max(2, distance / 0.3))
                else:
                    point_count = int(max(2, distance / 0.5))
                points = nurbs.calculate(point_count)
                for n in range(0, len(points) - 1):
                    self._addLine(points[n][0], points[n][1], points[n + 1][0], points[n + 1][1])
            else:
                Logger.log("w", "DXF: Unknown entity: %s", str(obj))

    def _addLine(self, x0, y0, x1, y1):
        x0 = float(x0)
        x1 = float(x1)
        y0 = -float(y0)
        y1 = -float(y1)
        if x0 == x1 and y0 == y1:
            return
        self._mesh.addVertex(x0, 0, y0)
        self._mesh.addVertex(x1, 0, y1)
        self._mesh.addVertex(x1, 1, y1)
        self._mesh.addVertex(x0, 1, y0)
        self._mesh.addVertex(x1, 1, y1)
        self._mesh.addVertex(x0, 0, y0)