示例#1
0
def test_attributes():
    attributes = { "test": {
                "value": [10],
                "derp": "OMG"
                }}
    mesh_data = MeshData(attributes = attributes)

    assert mesh_data.hasAttribute("test")
    assert mesh_data.getAttribute("test") == { "value": [10], "derp": "OMG" }

    assert mesh_data.attributeNames() == ["test"]
示例#2
0
def test_transformMeshData():
    transformation_matrix = Matrix()
    transformation_matrix.setByTranslation(Vector(30, 20, 10))

    vertices = numpy.zeros((1, 3), dtype=numpy.float32)
    mesh_data = MeshData(vertices)

    transformed_mesh = mesh_data.getTransformed(transformation_matrix)

    assert transformed_mesh.getVertex(0)[0] == 30.
    assert transformed_mesh.getVertex(0)[1] == 20.
    assert transformed_mesh.getVertex(0)[2] == 10.
    def run(self):
        objectIdMap = {}
        new_node = SceneNode()
        ## Put all nodes in a dict identified by ID
        for node in DepthFirstIterator(self._scene.getRoot()):
            if type(node) is SceneNode and node.getMeshData():
                if hasattr(node.getMeshData(), "layerData"):
                    self._scene.getRoot().removeChild(node)
                else:
                    objectIdMap[id(node)] = node

        settings = Application.getInstance().getActiveMachine()
        layerHeight = settings.getSettingValueByKey("layer_height")

        for object in self._message.objects:
            try:        
                node = objectIdMap[object.id]
            except KeyError:
                continue
            
            mesh = MeshData()

            layerData = LayerData.LayerData()
            for layer in object.layers:
                for polygon in layer.polygons:
                    points = numpy.fromstring(polygon.points, dtype="i8") # Convert bytearray to numpy array
                    points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
                    points = numpy.asarray(points, dtype=numpy.float32)
                    points /= 1000
                    points = numpy.insert(points, 1, layer.id * layerHeight, axis = 1)

                    points[:,2] *= -1

                    if not settings.getSettingValueByKey("machine_center_is_zero"):
                        center = [settings.getSettingValueByKey("machine_width") / 2, 0.0, -settings.getSettingValueByKey("machine_depth") / 2]
                        points -= numpy.array(center)

                    #points = numpy.pad(points, ((0,0), (0,1)), "constant", constant_values=(0.0, 1.0))
                    #inverse = node.getWorldTransformation().getInverse().getData()
                    #points = points.dot(inverse)
                    #points = points[:,0:3]

                    layerData.addPolygon(layer.id, polygon.type, points)

            # We are done processing all the layers we got from the engine, now create a mesh out of the data
            layerData.build()
            mesh.layerData = layerData
            
        new_node.setMeshData(mesh)
        new_node.setParent(self._scene.getRoot())
示例#4
0
def test_hasData():
    # Simple test to see if the has whatever functions do their job correctly

    empty_mesh = MeshData()

    assert not empty_mesh.hasNormals()
    assert not empty_mesh.hasColors()
    assert not empty_mesh.hasUVCoordinates()
    assert not empty_mesh.hasIndices()

    filled_mesh = MeshData(normals = [], colors = [], uvs = [], indices=[])
    assert filled_mesh.hasNormals()
    assert filled_mesh.hasColors()
    assert filled_mesh.hasUVCoordinates()
    assert filled_mesh.hasIndices()
示例#5
0
    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
示例#6
0
文件: LayerView.py 项目: cysheen/Cura
    def beginRendering(self):
        scene = self.getController().getScene()
        renderer = self.getRenderer()
        renderer.setRenderSelection(False)

        if not self._material:
            self._material = renderer.createMaterial(Resources.getPath(Resources.Shaders, "basic.vert"), Resources.getPath(Resources.Shaders, "vertexcolor.frag"))
            self._material.setUniformValue("u_color", [1.0, 0.0, 0.0, 1.0])

            self._selection_material = renderer.createMaterial(Resources.getPath(Resources.Shaders, "basic.vert"), Resources.getPath(Resources.Shaders, "color.frag"))
            self._selection_material.setUniformValue("u_color", Color(35, 35, 35, 128))

        for node in DepthFirstIterator(scene.getRoot()):
            # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
            # However, it is somewhat relevant when the node is selected, so do render it then.
            if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()):
                continue

            if not node.render(renderer):
                if node.getMeshData() and node.isVisible():
                    if Selection.isSelected(node):
                        renderer.queueNode(node, material = self._selection_material, transparent = True)
                    layer_data = node.callDecoration("getLayerData")
                    if not layer_data:
                        continue

                    # Render all layers below a certain number as line mesh instead of vertices.
                    if self._current_layer_num - self._solid_layers > -1:
                        start = 0
                        end = 0
                        element_counts = layer_data.getElementCounts()
                        for layer, counts in element_counts.items():
                            if layer + self._solid_layers > self._current_layer_num:
                                break
                            end += counts

                        # This uses glDrawRangeElements internally to only draw a certain range of lines.
                        renderer.queueNode(node, mesh = layer_data, material = self._material, mode = Renderer.RenderLines, start = start, end = end)

                    # We currently recreate the current "solid" layers every time a
                    if not self._current_layer_mesh:
                        self._current_layer_mesh = MeshData()
                        for i in range(self._solid_layers):
                            layer = self._current_layer_num - i
                            if layer < 0:
                                continue

                            layer_mesh = layer_data.getLayer(layer).createMesh()
                            if not layer_mesh or layer_mesh.getVertices() is None:
                                continue

                            self._current_layer_mesh.addVertices(layer_mesh.getVertices())

                            # Scale layer color by a brightness factor based on the current layer number
                            # This will result in a range of 0.5 - 1.0 to multiply colors by.
                            brightness = (2.0 - (i / self._solid_layers)) / 2.0
                            self._current_layer_mesh.addColors(layer_mesh.getColors() * brightness)

                    renderer.queueNode(node, mesh = self._current_layer_mesh, material = self._material)
示例#7
0
文件: LayerView.py 项目: 2bright/Cura
    def run(self):
        layer_data = None
        for node in DepthFirstIterator(self._scene.getRoot()):
            layer_data = node.callDecoration("getLayerData")
            if layer_data:
                break

        if self._cancel or not layer_data:
            return

        layer_mesh = MeshData()
        for i in range(self._solid_layers):
            layer_number = self._layer_number - i
            if layer_number < 0:
                continue

            try:
                layer = layer_data.getLayer(layer_number).createMesh()
            except Exception as e:
                print(e)
                return

            if not layer or layer.getVertices() is None:
                continue

            layer_mesh.addVertices(layer.getVertices())

            # Scale layer color by a brightness factor based on the current layer number
            # This will result in a range of 0.5 - 1.0 to multiply colors by.
            brightness = (2.0 - (i / self._solid_layers)) / 2.0
            layer_mesh.addColors(layer.getColors() * brightness)

            if self._cancel:
                return

            Job.yieldThread()

        if self._cancel:
            return

        Job.yieldThread()
        jump_mesh = layer_data.getLayer(self._layer_number).createJumps()
        if not jump_mesh or jump_mesh.getVertices() is None:
            jump_mesh = None

        self.setResult({ "layers": layer_mesh, "jumps": jump_mesh })
示例#8
0
    def read(self, file_name, storage_device):
        mesh = None
        extension = os.path.splitext(file_name)[1]
        if extension.lower() == self._supported_extension:
            mesh = MeshData()
            f = storage_device.openFile(file_name, "rb")
            if not self._loadBinary(mesh, f):
                storage_device.closeFile(f)
                f = storage_device.openFile(file_name, "rt")
                try:
                    self._loadAscii(mesh, f)
                except UnicodeDecodeError:
                    pass
                storage_device.closeFile(f)

            storage_device.closeFile(f)

            mesh.calculateNormals()

            Logger.log("d", "Loaded a mesh with %s vertices", mesh.getVertexCount())
        return mesh
示例#9
0
    def build(self) -> MeshData:
        """Build a MeshData object.
        
        :return: A Mesh data.
        """

        return MeshData(vertices=self.getVertices(),
                        normals=self.getNormals(),
                        indices=self.getIndices(),
                        colors=self.getColors(),
                        uvs=self.getUVCoordinates(),
                        file_name=self.getFileName(),
                        center_position=self.getCenterPosition())
示例#10
0
    def read(self, file_name):
        mesh = None
        scene_node = None

        mesh = MeshData()
        scene_node = SceneNode()
        f = open(file_name, "rb")
        if not self._loadBinary(mesh, f):
            f.close()
            f = open(file_name, "rt")
            try:
                self._loadAscii(mesh, f)
            except UnicodeDecodeError:
                pass
            f.close()

        f.close()
        time.sleep(0.1) #Yield somewhat to ensure the GUI has time to update a bit.
        mesh.calculateNormals(fast = True)

        Logger.log("d", "Loaded a mesh with %s vertices", mesh.getVertexCount())
        scene_node.setMeshData(mesh)
        return scene_node
示例#11
0
    def read(self, file_name):
        mesh = None
        scene_node = None
        extension = os.path.splitext(file_name)[1]
        if extension.lower() == self._supported_extension:
            mesh = MeshData()
            scene_node = SceneNode()
            f = open(file_name, "rb")
            if not self._loadBinary(mesh, f):
                f.close()
                f = open(file_name, "rt")
                try:
                    self._loadAscii(mesh, f)
                except UnicodeDecodeError:
                    pass
                f.close()

            f.close()

            mesh.calculateNormals(fast = True)

            Logger.log("d", "Loaded a mesh with %s vertices", mesh.getVertexCount())
            scene_node.setMeshData(mesh)
        return scene_node
示例#12
0
    def read(self, file_name):
        mesh = None
        scene_node = None
        extension = os.path.splitext(file_name)[1]
        if extension.lower() == self._supported_extension:
            mesh = MeshData()
            scene_node = SceneNode()
            f = open(file_name, "rb")
            if not self._loadBinary(mesh, f):
                f.close()
                f = open(file_name, "rt")
                try:
                    self._loadAscii(mesh, f)
                except UnicodeDecodeError:
                    pass
                f.close()

            f.close()
            time.sleep(0.1) #Yield somewhat to ensure the GUI has time to update a bit.
            mesh.calculateNormals(fast = True)

            Logger.log("d", "Loaded a mesh with %s vertices", mesh.getVertexCount())
            scene_node.setMeshData(mesh)
        return scene_node
示例#13
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
示例#14
0
    def test_deepCopy(self):
        node_1 = SceneNode()
        node_2 = SceneNode()
        node_1.translate(Vector(1, 2, 3))
        node_1.scale(Vector(1.5, 1., 1.))
        node_1.setMeshData(MeshData())
        node_1.addChild(node_2)
        node_1.addDecorator(GroupDecorator())
        copied_node = deepcopy(node_1)

        assert copied_node.getScale() == Vector(1.5, 1, 1)
        assert copied_node.getPosition() == Vector(1, 2, 3)
        assert len(copied_node.getChildren()) == 1
        # Ensure that the decorator also got copied
        assert copied_node.callDecoration("isGroup")
示例#15
0
    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)
示例#16
0
def test_counts():
    empty_mesh = MeshData()

    assert empty_mesh.getFaceCount() == 0
    assert empty_mesh.getVertexCount() == 0

    filled_mesh = MeshData(indices = numpy.zeros((5, 3), dtype=numpy.float32), vertices = numpy.zeros((12, 3), dtype=numpy.float32))

    assert filled_mesh.getFaceCount() == 5
    assert filled_mesh.getVertexCount() == 12
示例#17
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)
示例#18
0
    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()
示例#19
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)
    def run(self):
        layer_data = None
        for node in DepthFirstIterator(self._scene.getRoot()):
            layer_data = node.callDecoration("getLayerData")
            if layer_data:
                break

        if self._cancel or not layer_data:
            return

        layer_mesh = MeshData()
        for i in range(self._solid_layers):
            layer_number = self._layer_number - i
            if layer_number < 0:
                continue

            try:
                layer = layer_data.getLayer(layer_number).createMesh()
            except Exception as e:
                print(e)
                return

            if not layer or layer.getVertices() is None:
                continue

            layer_mesh.addVertices(layer.getVertices())

            # Scale layer color by a brightness factor based on the current layer number
            # This will result in a range of 0.5 - 1.0 to multiply colors by.
            brightness = (2.0 - (i / self._solid_layers)) / 2.0
            layer_mesh.addColors(layer.getColors() * brightness)

            if self._cancel:
                return

            Job.yieldThread()

        if self._cancel:
            return

        Job.yieldThread()
        jump_mesh = layer_data.getLayer(self._layer_number).createJumps()
        if not jump_mesh or jump_mesh.getVertices() is None:
            jump_mesh = None

        self.setResult({"layers": layer_mesh, "jumps": jump_mesh})
示例#21
0
    def _toMeshData(self,
                    tri_node: trimesh.base.Trimesh,
                    file_name: str = "") -> MeshData:
        """Converts a Trimesh to Uranium's MeshData.

        :param tri_node: A Trimesh containing the contents of a file that was just read.
        :param file_name: The full original filename used to watch for changes
        :return: Mesh data from the Trimesh in a way that Uranium can understand it.
        """

        tri_faces = tri_node.faces
        tri_vertices = tri_node.vertices

        indices_list = []
        vertices_list = []

        index_count = 0
        face_count = 0
        for tri_face in tri_faces:
            face = []
            for tri_index in tri_face:
                vertices_list.append(tri_vertices[tri_index])
                face.append(index_count)
                index_count += 1
            indices_list.append(face)
            face_count += 1

        vertices = numpy.asarray(vertices_list, dtype=numpy.float32)
        indices = numpy.asarray(indices_list, dtype=numpy.int32)
        normals = calculateNormalsFromIndexedVertices(vertices, indices,
                                                      face_count)

        mesh_data = MeshData(vertices=vertices,
                             indices=indices,
                             normals=normals,
                             file_name=file_name)
        return mesh_data
示例#22
0
    def _toMeshData(self, tri_node: trimesh.base.Trimesh) -> MeshData:
        tri_faces = tri_node.faces
        tri_vertices = tri_node.vertices

        indices = []
        vertices = []

        index_count = 0
        face_count = 0
        for tri_face in tri_faces:
            face = []
            for tri_index in tri_face:
                vertices.append(tri_vertices[tri_index])
                face.append(index_count)
                index_count += 1
            indices.append(face)
            face_count += 1

        vertices = numpy.asarray(vertices, dtype=numpy.float32)
        indices = numpy.asarray(indices, dtype=numpy.int32)
        normals = calculateNormalsFromIndexedVertices(vertices, indices, face_count)

        mesh_data = MeshData(vertices=vertices, indices=indices, normals=normals)
        return mesh_data
示例#23
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)
示例#24
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)
示例#25
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())
示例#26
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()
示例#27
0
    def read(self, file_name):
        mesh = None
        scene_node = None
        extension = os.path.splitext(file_name)[1]
        if extension.lower() == self._supported_extension:
            vertex_list = []
            normal_list = []
            uv_list = []
            face_list = []
            scene_node = SceneNode()

            mesh = MeshData()
            scene_node.setMeshData(mesh)
            f = open(file_name, "rt")
            for line in f:
                parts = line.split()
                if len(parts) < 1:
                    continue
                if parts[0] == "v":
                    vertex_list.append([float(parts[1]), float(parts[3]), -float(parts[2])])
                if parts[0] == "vn":
                    normal_list.append([float(parts[1]), float(parts[3]), -float(parts[2])])
                if parts[0] == "vt":
                    uv_list.append([float(parts[1]), float(parts[2])])
                if parts[0] == "f":
                    parts = [i for i in map(lambda p: p.split("/"), parts)]
                    for idx in range(1, len(parts)-2):
                        data = [int(parts[1][0]), int(parts[idx+1][0]), int(parts[idx+2][0])]
                        if len(parts[1]) > 2:
                            data += [int(parts[1][2]), int(parts[idx+1][2]), int(parts[idx+2][2])]

                            if parts[1][1] and parts[idx+1][1] and parts[idx+2][1]:
                                data += [int(parts[1][1]), int(parts[idx+1][1]), int(parts[idx+2][1])]
                        face_list.append(data)
            f.close()

            mesh.reserveVertexCount(3 * len(face_list))
            num_vertices = len(vertex_list)
            num_normals = len(normal_list)

            for face in face_list:
                # Substract 1 from index, as obj starts counting at 1 instead of 0
                i = face[0] - 1
                j = face[1] - 1
                k = face[2] - 1

                if len(face) > 3:
                    ni = face[3] - 1
                    nj = face[4] - 1
                    nk = face[5] - 1
                else:
                    ni = -1
                    nj = -1
                    nk = -1

                if len(face) > 6:
                    ui = face[6] - 1
                    uj = face[7] - 1
                    uk = face[8] - 1
                else:
                    ui = -1
                    uj = -1
                    uk = -1

                #TODO: improve this handling, this can cause weird errors
                if i < 0 or i >= num_vertices:
                    i = 0
                if j < 0 or j >= num_vertices:
                    j = 0
                if k < 0 or k >= num_vertices:
                    k = 0
                if(ni != -1 and nj != -1 and nk != -1):
                    mesh.addFaceWithNormals(vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], normal_list[ni][0], normal_list[ni][1], normal_list[ni][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], normal_list[nj][0], normal_list[nj][1], normal_list[nj][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2],normal_list[nk][0], normal_list[nk][1], normal_list[nk][2])
                else:
                    mesh.addFace(vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2])

                if ui != -1:
                    mesh.setVertexUVCoordinates(mesh.getVertexCount() - 3, uv_list[ui][0], uv_list[ui][1])

                if uj != -1:
                    mesh.setVertexUVCoordinates(mesh.getVertexCount() - 2, uv_list[uj][0], uv_list[uj][1])

                if uk != -1:
                    mesh.setVertexUVCoordinates(mesh.getVertexCount() - 1, uv_list[uk][0], uv_list[uk][1])
            if not mesh.hasNormals():
                mesh.calculateNormals(fast = True)
        return scene_node
示例#28
0
    def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert):
        mesh = None
        scene_node = None

        scene_node = SceneNode()

        mesh = MeshData()
        scene_node.setMeshData(mesh)

        img = QImage(file_name)

        if img.isNull():
            Logger.log("e", "Image is corrupt.")
            return None

        width = max(img.width(), 2)
        height = max(img.height(), 2)
        aspect = height / width

        if img.width() < 2 or img.height() < 2:
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        base_height = max(base_height, 0)
        peak_height = max(peak_height, -base_height)

        xz_size = max(xz_size, 1)
        scale_vector = Vector(xz_size, peak_height, xz_size)

        if width > height:
            scale_vector.setZ(scale_vector.z * aspect)
        elif height > width:
            scale_vector.setX(scale_vector.x / aspect)

        if width > max_size or height > max_size:
            scale_factor = max_size / width
            if height > width:
                scale_factor = max_size / height

            width = int(max(round(width * scale_factor), 2))
            height = int(max(round(height * scale_factor), 2))
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        width_minus_one = width - 1
        height_minus_one = height - 1

        Job.yieldThread()

        texel_width = 1.0 / (width_minus_one) * scale_vector.x
        texel_height = 1.0 / (height_minus_one) * scale_vector.z

        height_data = numpy.zeros((height, width), dtype=numpy.float32)

        for x in range(0, width):
            for y in range(0, height):
                qrgb = img.pixel(x, y)
                avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255)
                height_data[y, x] = avg

        Job.yieldThread()

        if image_color_invert:
            height_data = 1 - height_data

        for i in range(0, blur_iterations):
            copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode= "edge")

            height_data += copy[1:-1, 2:]
            height_data += copy[1:-1, :-2]
            height_data += copy[2:, 1:-1]
            height_data += copy[:-2, 1:-1]

            height_data += copy[2:, 2:]
            height_data += copy[:-2, 2:]
            height_data += copy[2:, :-2]
            height_data += copy[:-2, :-2]

            height_data /= 9

            Job.yieldThread()

        height_data *= scale_vector.y
        height_data += base_height

        heightmap_face_count = 2 * height_minus_one * width_minus_one
        total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2

        mesh.reserveFaceCount(total_face_count)

        # initialize to texel space vertex offsets.
        # 6 is for 6 vertices for each texel quad.
        heightmap_vertices = numpy.zeros((width_minus_one * height_minus_one, 6, 3), dtype = numpy.float32)
        heightmap_vertices = heightmap_vertices + numpy.array([[
            [0, base_height, 0],
            [0, base_height, texel_height],
            [texel_width, base_height, texel_height],
            [texel_width, base_height, texel_height],
            [texel_width, base_height, 0],
            [0, base_height, 0]
        ]], dtype = numpy.float32)

        offsetsz, offsetsx = numpy.mgrid[0: height_minus_one, 0: width - 1]
        offsetsx = numpy.array(offsetsx, numpy.float32).reshape(-1, 1) * texel_width
        offsetsz = numpy.array(offsetsz, numpy.float32).reshape(-1, 1) * texel_height

        # offsets for each texel quad
        heightmap_vertex_offsets = numpy.concatenate([offsetsx, numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]), dtype=numpy.float32), offsetsz], 1)
        heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(-1, 6, 3)

        # apply height data to y values
        heightmap_vertices[:, 0, 1] = heightmap_vertices[:, 5, 1] = height_data[:-1, :-1].reshape(-1)
        heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1)
        heightmap_vertices[:, 2, 1] = heightmap_vertices[:, 3, 1] = height_data[1:, 1:].reshape(-1)
        heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1)

        heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count * 3], dtype=numpy.int32).reshape(-1, 3)

        mesh._vertices[0:(heightmap_vertices.size // 3), :] = heightmap_vertices.reshape(-1, 3)
        mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices

        mesh._vertex_count = heightmap_vertices.size // 3
        mesh._face_count = heightmap_indices.size // 3

        geo_width = width_minus_one * texel_width
        geo_height = height_minus_one * texel_height

        # bottom
        mesh.addFace(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height)
        mesh.addFace(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0)

        # north and south walls
        for n in range(0, width_minus_one):
            x = n * texel_width
            nx = (n + 1) * texel_width

            hn0 = height_data[0, n]
            hn1 = height_data[0, n + 1]

            hs0 = height_data[height_minus_one, n]
            hs1 = height_data[height_minus_one, n + 1]

            mesh.addFace(x, 0, 0, nx, 0, 0, nx, hn1, 0)
            mesh.addFace(nx, hn1, 0, x, hn0, 0, x, 0, 0)

            mesh.addFace(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height)
            mesh.addFace(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height)

        # west and east walls
        for n in range(0, height_minus_one):
            y = n * texel_height
            ny = (n + 1) * texel_height

            hw0 = height_data[n, 0]
            hw1 = height_data[n + 1, 0]

            he0 = height_data[n, width_minus_one]
            he1 = height_data[n + 1, width_minus_one]

            mesh.addFace(0, 0, y, 0, 0, ny, 0, hw1, ny)
            mesh.addFace(0, hw1, ny, 0, hw0, y, 0, 0, y)

            mesh.addFace(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny)
            mesh.addFace(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y)

        mesh.calculateNormals(fast=True)

        return scene_node
示例#29
0
    def run(self):
        start_time = time()
        if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
            self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1)
            self._progress.show()
            Job.yieldThread()
            if self._abort_requested:
                if self._progress:
                    self._progress.hide()
                return

        Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)

        new_node = SceneNode()

        ## Remove old layer data (if any)
        for node in DepthFirstIterator(self._scene.getRoot()):
            if node.callDecoration("getLayerData"):
                node.getParent().removeChild(node)
                break
            if self._abort_requested:
                if self._progress:
                    self._progress.hide()
                return

        # Force garbage collection.
        # For some reason, Python has a tendency to keep the layer data
        # in memory longer than needed. Forcing the GC to run here makes
        # sure any old layer data is really cleaned up before adding new.
        gc.collect()

        mesh = MeshData()
        layer_data = LayerDataBuilder.LayerDataBuilder()
        layer_count = len(self._layers)

        # Find the minimum layer number
        # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we
        # instead simply offset all other layers so the lowest layer is always 0.
        min_layer_number = 0
        for layer in self._layers:
            if layer.id < min_layer_number:
                min_layer_number = layer.id

        current_layer = 0

        for layer in self._layers:
            abs_layer_number = layer.id + abs(min_layer_number)

            layer_data.addLayer(abs_layer_number)
            this_layer = layer_data.getLayer(abs_layer_number)
            layer_data.setLayerHeight(abs_layer_number, layer.height)

            for p in range(layer.repeatedMessageCount("path_segment")):
                polygon = layer.getRepeatedMessage("path_segment", p)

                extruder = polygon.extruder

                line_types = numpy.fromstring(polygon.line_type, dtype="u1")  # Convert bytearray to numpy array
                line_types = line_types.reshape((-1,1))

                points = numpy.fromstring(polygon.points, dtype="f4")  # Convert bytearray to numpy array
                if polygon.point_type == 0: # Point2D
                    points = points.reshape((-1,2))  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
                else:  # Point3D
                    points = points.reshape((-1,3))

                line_widths = numpy.fromstring(polygon.line_width, dtype="f4")  # Convert bytearray to numpy array
                line_widths = line_widths.reshape((-1,1))  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                # In the future, line_thicknesses should be given by CuraEngine as well.
                # Currently the infill layer thickness also translates to line width
                line_thicknesses = numpy.zeros(line_widths.shape, dtype="f4")
                line_thicknesses[:] = layer.thickness / 1000  # from micrometer to millimeter

                # Create a new 3D-array, copy the 2D points over and insert the right height.
                # This uses manual array creation + copy rather than numpy.insert since this is
                # faster.
                new_points = numpy.empty((len(points), 3), numpy.float32)
                if polygon.point_type == 0:  # Point2D
                    new_points[:, 0] = points[:, 0]
                    new_points[:, 1] = layer.height / 1000  # layer height value is in backend representation
                    new_points[:, 2] = -points[:, 1]
                else: # Point3D
                    new_points[:, 0] = points[:, 0]
                    new_points[:, 1] = points[:, 2]
                    new_points[:, 2] = -points[:, 1]

                this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses)
                this_poly.buildCache()

                this_layer.polygons.append(this_poly)

                Job.yieldThread()
            Job.yieldThread()
            current_layer += 1
            progress = (current_layer / layer_count) * 99
            # TODO: Rebuild the layer data mesh once the layer has been processed.
            # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh.

            if self._abort_requested:
                if self._progress:
                    self._progress.hide()
                return
            if self._progress:
                self._progress.setProgress(progress)

        # We are done processing all the layers we got from the engine, now create a mesh out of the data

        # Find out colors per extruder
        global_container_stack = Application.getInstance().getGlobalContainerStack()
        manager = ExtruderManager.getInstance()
        extruders = list(manager.getMachineExtruders(global_container_stack.getId()))
        if extruders:
            material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
            for extruder in extruders:
                position = int(extruder.getMetaDataEntry("position", default="0"))  # Get the position
                try:
                    default_color = ExtrudersModel.defaultColors[position]
                except KeyError:
                    default_color = "#e0e000"
                color_code = extruder.material.getMetaDataEntry("color_code", default=default_color)
                color = colorCodeToRGBA(color_code)
                material_color_map[position, :] = color
        else:
            # Single extruder via global stack.
            material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
            color_code = global_container_stack.material.getMetaDataEntry("color_code", default="#e0e000")
            color = colorCodeToRGBA(color_code)
            material_color_map[0, :] = color

        # We have to scale the colors for compatibility mode
        if OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")):
            line_type_brightness = 0.5  # for compatibility mode
        else:
            line_type_brightness = 1.0
        layer_mesh = layer_data.build(material_color_map, line_type_brightness)

        if self._abort_requested:
            if self._progress:
                self._progress.hide()
            return

        # Add LayerDataDecorator to scene node to indicate that the node has layer data
        decorator = LayerDataDecorator.LayerDataDecorator()
        decorator.setLayerData(layer_mesh)
        new_node.addDecorator(decorator)

        new_node.setMeshData(mesh)
        # Set build volume as parent, the build volume can move as a result of raft settings.
        # It makes sense to set the build volume as parent: the print is actually printed on it.
        new_node_parent = Application.getInstance().getBuildVolume()
        new_node.setParent(new_node_parent)  # Note: After this we can no longer abort!

        settings = Application.getInstance().getGlobalContainerStack()
        if not settings.getProperty("machine_center_is_zero", "value"):
            new_node.setPosition(Vector(-settings.getProperty("machine_width", "value") / 2, 0.0, settings.getProperty("machine_depth", "value") / 2))

        if self._progress:
            self._progress.setProgress(100)

        view = Application.getInstance().getController().getActiveView()
        if view.getPluginId() == "LayerView":
            view.resetLayerData()

        if self._progress:
            self._progress.hide()

        # Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed.
        self._layers = None

        Logger.log("d", "Processing layers took %s seconds", time() - start_time)
示例#30
0
    def run(self):
        Logger.log(
            "d", "Processing new layer for build plate %s..." %
            self._build_plate_number)
        start_time = time()
        view = Application.getInstance().getController().getActiveView()
        if view.getPluginId() == "SimulationView":
            view.resetLayerData()
            self._progress_message.show()
            Job.yieldThread()
            if self._abort_requested:
                if self._progress_message:
                    self._progress_message.hide()
                return

        Application.getInstance().getController().activeViewChanged.connect(
            self._onActiveViewChanged)

        # The no_setting_override is here because adding the SettingOverrideDecorator will trigger a reslice
        new_node = CuraSceneNode(no_setting_override=True)
        new_node.addDecorator(BuildPlateDecorator(self._build_plate_number))

        # Force garbage collection.
        # For some reason, Python has a tendency to keep the layer data
        # in memory longer than needed. Forcing the GC to run here makes
        # sure any old layer data is really cleaned up before adding new.
        gc.collect()

        mesh = MeshData()
        layer_data = LayerDataBuilder.LayerDataBuilder()
        layer_count = len(self._layers)

        # Find the minimum layer number
        # When disabling the remove empty first layers setting, the minimum layer number will be a positive
        # value. In that case the first empty layers will be discarded and start processing layers from the
        # first layer with data.
        # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we
        # simply offset all other layers so the lowest layer is always 0. It could happens that the first
        # raft layer has value -8 but there are just 4 raft (negative) layers.
        min_layer_number = sys.maxsize
        negative_layers = 0
        for layer in self._layers:
            if layer.repeatedMessageCount("path_segment") > 0:
                if layer.id < min_layer_number:
                    min_layer_number = layer.id
                if layer.id < 0:
                    negative_layers += 1

        current_layer = 0

        for layer in self._layers:
            # If the layer is below the minimum, it means that there is no data, so that we don't create a layer
            # data. However, if there are empty layers in between, we compute them.
            if layer.id < min_layer_number:
                continue

            # Layers are offset by the minimum layer number. In case the raft (negative layers) is being used,
            # then the absolute layer number is adjusted by removing the empty layers that can be in between raft
            # and the model
            abs_layer_number = layer.id - min_layer_number
            if layer.id >= 0 and negative_layers != 0:
                abs_layer_number += (min_layer_number + negative_layers)

            layer_data.addLayer(abs_layer_number)
            this_layer = layer_data.getLayer(abs_layer_number)
            layer_data.setLayerHeight(abs_layer_number, layer.height)
            layer_data.setLayerThickness(abs_layer_number, layer.thickness)

            for p in range(layer.repeatedMessageCount("path_segment")):
                polygon = layer.getRepeatedMessage("path_segment", p)

                extruder = polygon.extruder

                line_types = numpy.fromstring(
                    polygon.line_type,
                    dtype="u1")  # Convert bytearray to numpy array

                line_types = line_types.reshape((-1, 1))

                points = numpy.fromstring(
                    polygon.points,
                    dtype="f4")  # Convert bytearray to numpy array
                if polygon.point_type == 0:  # Point2D
                    points = points.reshape(
                        (-1, 2)
                    )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
                else:  # Point3D
                    points = points.reshape((-1, 3))

                line_widths = numpy.fromstring(
                    polygon.line_width,
                    dtype="f4")  # Convert bytearray to numpy array
                line_widths = line_widths.reshape(
                    (-1, 1)
                )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                line_thicknesses = numpy.fromstring(
                    polygon.line_thickness,
                    dtype="f4")  # Convert bytearray to numpy array
                line_thicknesses = line_thicknesses.reshape(
                    (-1, 1)
                )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                line_feedrates = numpy.fromstring(
                    polygon.line_feedrate,
                    dtype="f4")  # Convert bytearray to numpy array
                line_feedrates = line_feedrates.reshape(
                    (-1, 1)
                )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                # Create a new 3D-array, copy the 2D points over and insert the right height.
                # This uses manual array creation + copy rather than numpy.insert since this is
                # faster.
                new_points = numpy.empty((len(points), 3), numpy.float32)
                if polygon.point_type == 0:  # Point2D
                    new_points[:, 0] = points[:, 0]
                    new_points[:,
                               1] = layer.height / 1000  # layer height value is in backend representation
                    new_points[:, 2] = -points[:, 1]
                else:  # Point3D
                    new_points[:, 0] = points[:, 0]
                    new_points[:, 1] = points[:, 2]
                    new_points[:, 2] = -points[:, 1]

                this_poly = LayerPolygon.LayerPolygon(extruder, line_types,
                                                      new_points, line_widths,
                                                      line_thicknesses,
                                                      line_feedrates)
                this_poly.buildCache()

                this_layer.polygons.append(this_poly)

                Job.yieldThread()
            Job.yieldThread()
            current_layer += 1
            progress = (current_layer / layer_count) * 99
            # TODO: Rebuild the layer data mesh once the layer has been processed.
            # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh.

            if self._abort_requested:
                if self._progress_message:
                    self._progress_message.hide()
                return
            if self._progress_message:
                self._progress_message.setProgress(progress)

        # We are done processing all the layers we got from the engine, now create a mesh out of the data

        # Find out colors per extruder
        global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()
        manager = ExtruderManager.getInstance()
        extruders = manager.getActiveExtruderStacks()
        if extruders:
            material_color_map = numpy.zeros((len(extruders), 4),
                                             dtype=numpy.float32)
            for extruder in extruders:
                position = int(
                    extruder.getMetaDataEntry("position", default="0"))
                try:
                    default_color = ExtrudersModel.defaultColors[position]
                except IndexError:
                    default_color = "#e0e000"
                color_code = extruder.material.getMetaDataEntry(
                    "color_code", default=default_color)
                color = colorCodeToRGBA(color_code)
                material_color_map[position, :] = color
        else:
            # Single extruder via global stack.
            material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
            color_code = global_container_stack.material.getMetaDataEntry(
                "color_code", default="#e0e000")
            color = colorCodeToRGBA(color_code)
            material_color_map[0, :] = color

        # We have to scale the colors for compatibility mode
        if OpenGLContext.isLegacyOpenGL() or bool(
                Application.getInstance().getPreferences().getValue(
                    "view/force_layer_view_compatibility_mode")):
            line_type_brightness = 0.5  # for compatibility mode
        else:
            line_type_brightness = 1.0
        layer_mesh = layer_data.build(material_color_map, line_type_brightness)

        if self._abort_requested:
            if self._progress_message:
                self._progress_message.hide()
            return

        # Add LayerDataDecorator to scene node to indicate that the node has layer data
        decorator = LayerDataDecorator.LayerDataDecorator()
        decorator.setLayerData(layer_mesh)
        new_node.addDecorator(decorator)

        new_node.setMeshData(mesh)
        # Set build volume as parent, the build volume can move as a result of raft settings.
        # It makes sense to set the build volume as parent: the print is actually printed on it.
        new_node_parent = Application.getInstance().getBuildVolume()
        new_node.setParent(
            new_node_parent)  # Note: After this we can no longer abort!

        settings = Application.getInstance().getGlobalContainerStack()
        if not settings.getProperty("machine_center_is_zero", "value"):
            new_node.setPosition(
                Vector(-settings.getProperty("machine_width", "value") / 2,
                       0.0,
                       settings.getProperty("machine_depth", "value") / 2))

        if self._progress_message:
            self._progress_message.setProgress(100)

        if self._progress_message:
            self._progress_message.hide()

        # Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed.
        self._layers = None

        Logger.log("d", "Processing layers took %s seconds",
                   time() - start_time)
示例#31
0
 def build(self):
     return MeshData(vertices=self.getVertices(), normals=self.getNormals(), indices=self.getIndices(),
                     colors=self.getColors(), uvs=self.getUVCoordinates(), file_name=self.getFileName(),
                     center_position=self.getCenterPosition())
示例#32
0
文件: LayerView.py 项目: Kiddo3D/Cura
class LayerView(View):
    def __init__(self):
        super().__init__()
        self._shader = None
        self._selection_shader = None
        self._num_layers = 0
        self._layer_percentage = 0 # what percentage of layers need to be shown (SLider gives value between 0 - 100)
        self._proxy = LayerViewProxy.LayerViewProxy()
        self._controller.getScene().sceneChanged.connect(self._onSceneChanged)
        self._max_layers = 10
        self._current_layer_num = 10
        self._current_layer_mesh = None
        self._current_layer_jumps = None
        self._activity = False

        self._solid_layers = 5

    def getActivity(self):
        return self._activity

    def getCurrentLayer(self):
        return self._current_layer_num
    
    def _onSceneChanged(self, node):
        self.calculateMaxLayers()
    
    def getMaxLayers(self):
        return self._max_layers

    def resetLayerData(self):
        self._current_layer_mesh = None
        self._current_layer_jumps = None

    def beginRendering(self):
        scene = self.getController().getScene()
        renderer = self.getRenderer()

        if not self._selection_shader:
            self._selection_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
            self._selection_shader.setUniformValue("u_color", Color(32, 32, 32, 128))

        for node in DepthFirstIterator(scene.getRoot()):
            # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
            # However, it is somewhat relevant when the node is selected, so do render it then.
            if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()):
                continue

            if not node.render(renderer):
                if node.getMeshData() and node.isVisible():
                    if Selection.isSelected(node):
                        renderer.queueNode(node, transparent = True, shader = self._selection_shader)
                    layer_data = node.callDecoration("getLayerData")
                    if not layer_data:
                        continue

                    # Render all layers below a certain number as line mesh instead of vertices.
                    if self._current_layer_num - self._solid_layers > -1:
                        start = 0
                        end = 0
                        element_counts = layer_data.getElementCounts()
                        for layer, counts in element_counts.items():
                            if layer + self._solid_layers > self._current_layer_num:
                                break
                            end += counts

                        # This uses glDrawRangeElements internally to only draw a certain range of lines.
                        renderer.queueNode(node, mesh = layer_data, mode = RenderBatch.RenderMode.Lines, range = (start, end))

                    # We currently recreate the current "solid" layers every time a
                    if not self._current_layer_mesh:
                        self._current_layer_mesh = MeshData()
                        for i in range(self._solid_layers):
                            layer = self._current_layer_num - i
                            if layer < 0:
                                continue
                            try:
                                layer_mesh = layer_data.getLayer(layer).createMesh()
                                if not layer_mesh or layer_mesh.getVertices() is None:
                                    continue
                            except:
                                continue
                            if self._current_layer_mesh: #Threading thing; Switching between views can cause the current layer mesh to be deleted.
                                self._current_layer_mesh.addVertices(layer_mesh.getVertices())

                            # Scale layer color by a brightness factor based on the current layer number
                            # This will result in a range of 0.5 - 1.0 to multiply colors by.
                            brightness = (2.0 - (i / self._solid_layers)) / 2.0
                            if self._current_layer_mesh:
                                self._current_layer_mesh.addColors(layer_mesh.getColors() * brightness)
                    if self._current_layer_mesh:
                        renderer.queueNode(node, mesh = self._current_layer_mesh)

                    if not self._current_layer_jumps:
                        self._current_layer_jumps = MeshData()
                        for i in range(1):
                            layer = self._current_layer_num - i
                            if layer < 0:
                                continue
                            try:
                                layer_mesh = layer_data.getLayer(layer).createJumps()
                                if not layer_mesh or layer_mesh.getVertices() is None:
                                    continue
                            except:
                                continue

                            self._current_layer_jumps.addVertices(layer_mesh.getVertices())

                            # Scale layer color by a brightness factor based on the current layer number
                            # This will result in a range of 0.5 - 1.0 to multiply colors by.
                            brightness = (2.0 - (i / self._solid_layers)) / 2.0
                            self._current_layer_jumps.addColors(layer_mesh.getColors() * brightness)

                    renderer.queueNode(node, mesh = self._current_layer_jumps)

    def setLayer(self, value):
        if self._current_layer_num != value:
            self._current_layer_num = value
            if self._current_layer_num < 0:
                self._current_layer_num = 0
            if self._current_layer_num > self._max_layers:
                self._current_layer_num = self._max_layers

            self._current_layer_mesh = None
            self._current_layer_jumps = None
            self.currentLayerNumChanged.emit()

    currentLayerNumChanged = Signal()

    def calculateMaxLayers(self):
        scene = self.getController().getScene()
        renderer = self.getRenderer()
        self._activity = True

        self._old_max_layers = self._max_layers
        ## Recalculate num max layers
        new_max_layers = 0
        for node in DepthFirstIterator(scene.getRoot()):
            layer_data = node.callDecoration("getLayerData")
            if not layer_data:
                continue

            if new_max_layers < len(layer_data.getLayers()):
                new_max_layers = len(layer_data.getLayers()) - 1

        if new_max_layers > 0 and new_max_layers != self._old_max_layers:
            self._max_layers = new_max_layers

            # The qt slider has a bit of weird behavior that if the maxvalue needs to be changed first
            # if it's the largest value. If we don't do this, we can have a slider block outside of the
            # slider. 
            if new_max_layers > self._current_layer_num:
                self.maxLayersChanged.emit()
                self.setLayer(int(self._max_layers))
            else:
                self.setLayer(int(self._max_layers))
                self.maxLayersChanged.emit()

    maxLayersChanged = Signal()
    currentLayerNumChanged = Signal()
    
    ##  Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created
    #   as this caused some issues. 
    def getProxy(self, engine, script_engine):
        return self._proxy
    
    def endRendering(self):
        pass
    
    def event(self, event):
        modifiers = QtWidgets.QApplication.keyboardModifiers()
        ctrl_is_active = modifiers == QtCore.Qt.ControlModifier
        if event.type == Event.KeyPressEvent and ctrl_is_active:
            if event.key == KeyEvent.UpKey:
                self.setLayer(self._current_layer_num + 1)
                return True
            if event.key == KeyEvent.DownKey:
                self.setLayer(self._current_layer_num - 1)
                return True
示例#33
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 addVertex(mesh):
    mesh.addVertex(0, 1, 0)


mesh = MeshData()
mesh.reserveVertexCount(1000000)
for i in range(1000000):
    addVertex(mesh)
示例#34
0
    def read(self, file_name):
        result = None
        extension = os.path.splitext(file_name)[1]
        if extension.lower() == self._supported_extension:
            result = SceneNode()
            # The base object of 3mf is a zipped archive.
            archive = zipfile.ZipFile(file_name, "r")
            try:
                root = ET.parse(archive.open("3D/3dmodel.model"))

                # There can be multiple objects, try to load all of them.
                objects = root.findall("./3mf:resources/3mf:object",
                                       self._namespaces)
                if len(objects) == 0:
                    Logger.log(
                        "w",
                        "No objects found in 3MF file %s, either the file is corrupt or you are using an outdated format",
                        file_name)
                    return None

                for object in objects:
                    mesh = MeshData()
                    node = SceneNode()
                    vertex_list = []
                    #for vertex in object.mesh.vertices.vertex:
                    for vertex in object.findall(".//3mf:vertex",
                                                 self._namespaces):
                        vertex_list.append([
                            vertex.get("x"),
                            vertex.get("y"),
                            vertex.get("z")
                        ])
                        Job.yieldThread()

                    triangles = object.findall(".//3mf:triangle",
                                               self._namespaces)

                    mesh.reserveFaceCount(len(triangles))

                    #for triangle in object.mesh.triangles.triangle:
                    for triangle in triangles:
                        v1 = int(triangle.get("v1"))
                        v2 = int(triangle.get("v2"))
                        v3 = int(triangle.get("v3"))
                        mesh.addFace(vertex_list[v1][0], vertex_list[v1][1],
                                     vertex_list[v1][2], vertex_list[v2][0],
                                     vertex_list[v2][1], vertex_list[v2][2],
                                     vertex_list[v3][0], vertex_list[v3][1],
                                     vertex_list[v3][2])
                        Job.yieldThread()

                    #TODO: We currently do not check for normals and simply recalculate them.
                    mesh.calculateNormals()
                    node.setMeshData(mesh)
                    node.setSelectable(True)

                    transformation = root.findall(
                        "./3mf:build/3mf:item[@objectid='{0}']".format(
                            object.get("id")), self._namespaces)
                    if transformation:
                        transformation = transformation[0]

                    if transformation.get("transform"):
                        splitted_transformation = transformation.get(
                            "transform").split()
                        ## Transformation is saved as:
                        ## M00 M01 M02 0.0
                        ## M10 M11 M12 0.0
                        ## M20 M21 M22 0.0
                        ## M30 M31 M32 1.0
                        ## We switch the row & cols as that is how everyone else uses matrices!
                        temp_mat = Matrix()
                        # Rotation & Scale
                        temp_mat._data[0, 0] = splitted_transformation[0]
                        temp_mat._data[1, 0] = splitted_transformation[1]
                        temp_mat._data[2, 0] = splitted_transformation[2]
                        temp_mat._data[0, 1] = splitted_transformation[3]
                        temp_mat._data[1, 1] = splitted_transformation[4]
                        temp_mat._data[2, 1] = splitted_transformation[5]
                        temp_mat._data[0, 2] = splitted_transformation[6]
                        temp_mat._data[1, 2] = splitted_transformation[7]
                        temp_mat._data[2, 2] = splitted_transformation[8]

                        # Translation
                        temp_mat._data[0, 3] = splitted_transformation[9]
                        temp_mat._data[1, 3] = splitted_transformation[10]
                        temp_mat._data[2, 3] = splitted_transformation[11]

                        node.setPosition(
                            Vector(temp_mat.at(0, 3), temp_mat.at(1, 3),
                                   temp_mat.at(2, 3)))

                        temp_quaternion = Quaternion()
                        temp_quaternion.setByMatrix(temp_mat)
                        node.setOrientation(temp_quaternion)

                        # Magical scale extraction
                        scale = temp_mat.getTransposed().multiply(temp_mat)
                        scale_x = math.sqrt(scale.at(0, 0))
                        scale_y = math.sqrt(scale.at(1, 1))
                        scale_z = math.sqrt(scale.at(2, 2))
                        node.setScale(Vector(scale_x, scale_y, scale_z))

                        # We use a different coordinate frame, so rotate.
                        #rotation = Quaternion.fromAngleAxis(-0.5 * math.pi, Vector(1,0,0))
                        #node.rotate(rotation)
                    result.addChild(node)

                    Job.yieldThread()

                #If there is more then one object, group them.
                try:
                    if len(objects) > 1:
                        group_decorator = GroupDecorator()
                        result.addDecorator(group_decorator)
                except:
                    pass
            except Exception as e:
                Logger.log("e", "exception occured in 3mf reader: %s", e)
        return result
示例#35
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 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)
示例#36
0
    def beginRendering(self):
        scene = self.getController().getScene()
        renderer = self.getRenderer()
        renderer.setRenderSelection(False)

        if not self._material:
            self._material = renderer.createMaterial(
                Resources.getPath(Resources.Shaders, "basic.vert"),
                Resources.getPath(Resources.Shaders, "vertexcolor.frag"))
            self._material.setUniformValue("u_color", [1.0, 0.0, 0.0, 1.0])

            self._selection_material = renderer.createMaterial(
                Resources.getPath(Resources.Shaders, "basic.vert"),
                Resources.getPath(Resources.Shaders, "color.frag"))
            self._selection_material.setUniformValue("u_color",
                                                     Color(35, 35, 35, 128))

        for node in DepthFirstIterator(scene.getRoot()):
            # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
            # However, it is somewhat relevant when the node is selected, so do render it then.
            if type(node) is ConvexHullNode and not Selection.isSelected(
                    node.getWatchedNode()):
                continue

            if not node.render(renderer):
                if node.getMeshData() and node.isVisible():
                    if Selection.isSelected(node):
                        renderer.queueNode(node,
                                           material=self._selection_material,
                                           transparent=True)
                    layer_data = node.callDecoration("getLayerData")
                    if not layer_data:
                        continue

                    # Render all layers below a certain number as line mesh instead of vertices.
                    if self._current_layer_num - self._solid_layers > -1:
                        start = 0
                        end = 0
                        element_counts = layer_data.getElementCounts()
                        for layer, counts in element_counts.items():
                            if layer + self._solid_layers > self._current_layer_num:
                                break
                            end += counts

                        # This uses glDrawRangeElements internally to only draw a certain range of lines.
                        renderer.queueNode(node,
                                           mesh=layer_data,
                                           material=self._material,
                                           mode=Renderer.RenderLines,
                                           start=start,
                                           end=end)

                    # We currently recreate the current "solid" layers every time a
                    if not self._current_layer_mesh:
                        self._current_layer_mesh = MeshData()
                        for i in range(self._solid_layers):
                            layer = self._current_layer_num - i
                            if layer < 0:
                                continue
                            try:
                                layer_mesh = layer_data.getLayer(
                                    layer).createMesh()
                                if not layer_mesh or layer_mesh.getVertices(
                                ) is None:
                                    continue
                            except:
                                continue
                            if self._current_layer_mesh:  #Threading thing; Switching between views can cause the current layer mesh to be deleted.
                                self._current_layer_mesh.addVertices(
                                    layer_mesh.getVertices())

                            # Scale layer color by a brightness factor based on the current layer number
                            # This will result in a range of 0.5 - 1.0 to multiply colors by.
                            brightness = (2.0 - (i / self._solid_layers)) / 2.0
                            if self._current_layer_mesh:
                                self._current_layer_mesh.addColors(
                                    layer_mesh.getColors() * brightness)
                    if self._current_layer_mesh:
                        renderer.queueNode(node,
                                           mesh=self._current_layer_mesh,
                                           material=self._material)

                    if not self._current_layer_jumps:
                        self._current_layer_jumps = MeshData()
                        for i in range(1):
                            layer = self._current_layer_num - i
                            if layer < 0:
                                continue
                            try:
                                layer_mesh = layer_data.getLayer(
                                    layer).createJumps()
                                if not layer_mesh or layer_mesh.getVertices(
                                ) is None:
                                    continue
                            except:
                                continue

                            self._current_layer_jumps.addVertices(
                                layer_mesh.getVertices())

                            # Scale layer color by a brightness factor based on the current layer number
                            # This will result in a range of 0.5 - 1.0 to multiply colors by.
                            brightness = (2.0 - (i / self._solid_layers)) / 2.0
                            self._current_layer_jumps.addColors(
                                layer_mesh.getColors() * brightness)

                    renderer.queueNode(node,
                                       mesh=self._current_layer_jumps,
                                       material=self._material)
示例#37
0
def test_hasData():
    # Simple test to see if the has whatever functions do their job correctly

    empty_mesh = MeshData()

    assert not empty_mesh.hasNormals()
    assert not empty_mesh.hasColors()
    assert not empty_mesh.hasUVCoordinates()
    assert not empty_mesh.hasIndices()

    filled_mesh = MeshData(normals=[], colors=[], uvs=[], indices=[])
    assert filled_mesh.hasNormals()
    assert filled_mesh.hasColors()
    assert filled_mesh.hasUVCoordinates()
    assert filled_mesh.hasIndices()
示例#38
0
    def run(self):
        if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
            self._progress = Message(catalog.i18nc("Layers View mode", "Layers"), 0, False, 0)
            self._progress.show()

        objectIdMap = {}
        new_node = SceneNode()
        ## Put all nodes in a dict identified by ID
        for node in DepthFirstIterator(self._scene.getRoot()):
            if type(node) is SceneNode and node.getMeshData():
                if hasattr(node.getMeshData(), "layerData"):
                    self._scene.getRoot().removeChild(node)
                else:
                    objectIdMap[id(node)] = node

        settings = Application.getInstance().getActiveMachine()
        layerHeight = settings.getSettingValueByKey("layer_height")

        center = None
        if not settings.getSettingValueByKey("machine_center_is_zero"):
            center = numpy.array([settings.getSettingValueByKey("machine_width") / 2, 0.0, -settings.getSettingValueByKey("machine_depth") / 2])
        else:
            center = numpy.array([0.0, 0.0, 0.0])

        if self._progress:
            self._progress.setProgress(2)

        mesh = MeshData()
        for object in self._message.objects:
            try:
                node = objectIdMap[object.id]
            except KeyError:
                continue

            layerData = LayerData.LayerData()
            for layer in object.layers:
                layerData.addLayer(layer.id)
                layerData.setLayerHeight(layer.id, layer.height)
                layerData.setLayerThickness(layer.id, layer.thickness)
                for polygon in layer.polygons:
                    points = numpy.fromstring(polygon.points, dtype="i8") # Convert bytearray to numpy array
                    points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
                    points = numpy.asarray(points, dtype=numpy.float32)
                    points /= 1000
                    points = numpy.insert(points, 1, (layer.height / 1000), axis = 1)

                    points[:,2] *= -1

                    points -= numpy.array(center)

                    layerData.addPolygon(layer.id, polygon.type, points, polygon.line_width)

        if self._progress:
            self._progress.setProgress(50)

        # We are done processing all the layers we got from the engine, now create a mesh out of the data
        layerData.build()
        mesh.layerData = layerData

        if self._progress:
            self._progress.setProgress(100)

        new_node.setMeshData(mesh)
        new_node.setParent(self._scene.getRoot())

        view = Application.getInstance().getController().getActiveView()
        if view.getPluginId() == "LayerView":
            view.resetLayerData()

        if self._progress:
            self._progress.hide()
示例#39
0
    def run(self):
        if Application.getInstance().getController().getActiveView(
        ).getPluginId() == "LayerView":
            self._progress = Message(
                catalog.i18nc("@info:status", "Processing Layers"), 0, False,
                -1)
            self._progress.show()
            Job.yieldThread()
            if self._abort_requested:
                if self._progress:
                    self._progress.hide()
                return

        Application.getInstance().getController().activeViewChanged.connect(
            self._onActiveViewChanged)

        new_node = SceneNode()

        ## Remove old layer data (if any)
        for node in DepthFirstIterator(self._scene.getRoot()):
            if type(node) is SceneNode and node.getMeshData():
                if node.callDecoration("getLayerData"):
                    self._scene.getRoot().removeChild(node)
            Job.yieldThread()
            if self._abort_requested:
                if self._progress:
                    self._progress.hide()
                return

        mesh = MeshData()
        layer_data = LayerData.LayerData()
        layer_count = len(self._layers)

        # Find the minimum layer number
        # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we
        # instead simply offset all other layers so the lowest layer is always 0.
        min_layer_number = 0
        for layer in self._layers:
            if (layer.id < min_layer_number):
                min_layer_number = layer.id

        current_layer = 0

        for layer in self._layers:
            abs_layer_number = layer.id + abs(min_layer_number)

            layer_data.addLayer(abs_layer_number)
            layer_data.setLayerHeight(abs_layer_number, layer.height)
            layer_data.setLayerThickness(abs_layer_number, layer.thickness)

            for p in range(layer.repeatedMessageCount("polygons")):
                polygon = layer.getRepeatedMessage("polygons", p)

                points = numpy.fromstring(
                    polygon.points,
                    dtype="i8")  # Convert bytearray to numpy array
                points = points.reshape(
                    (-1, 2)
                )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                # Create a new 3D-array, copy the 2D points over and insert the right height.
                # This uses manual array creation + copy rather than numpy.insert since this is
                # faster.
                new_points = numpy.empty((len(points), 3), numpy.float32)
                new_points[:, 0] = points[:, 0]
                new_points[:, 1] = layer.height
                new_points[:, 2] = -points[:, 1]

                new_points /= 1000

                layer_data.addPolygon(abs_layer_number, polygon.type,
                                      new_points, polygon.line_width)
                Job.yieldThread()
            Job.yieldThread()
            current_layer += 1
            progress = (current_layer / layer_count) * 100
            # TODO: Rebuild the layer data mesh once the layer has been processed.
            # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh.

            if self._abort_requested:
                if self._progress:
                    self._progress.hide()
                return
            if self._progress:
                self._progress.setProgress(progress)

        # We are done processing all the layers we got from the engine, now create a mesh out of the data
        layer_data.build()

        if self._abort_requested:
            if self._progress:
                self._progress.hide()
            return

        # Add LayerDataDecorator to scene node to indicate that the node has layer data
        decorator = LayerDataDecorator.LayerDataDecorator()
        decorator.setLayerData(layer_data)
        new_node.addDecorator(decorator)

        new_node.setMeshData(mesh)
        new_node.setParent(
            self._scene.getRoot())  # Note: After this we can no longer abort!

        settings = Application.getInstance().getGlobalContainerStack()
        if not settings.getProperty("machine_center_is_zero", "value"):
            new_node.setPosition(
                Vector(-settings.getProperty("machine_width", "value") / 2,
                       0.0,
                       settings.getProperty("machine_depth", "value") / 2))

        if self._progress:
            self._progress.setProgress(100)

        view = Application.getInstance().getController().getActiveView()
        if view.getPluginId() == "LayerView":
            view.resetLayerData()

        if self._progress:
            self._progress.hide()

        # Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed.
        self._layers = None
示例#40
0
    def run(self):
        if Application.getInstance().getController().getActiveView(
        ).getPluginId() == "LayerView":
            self._progress = Message(
                catalog.i18nc("@info:status", "Processing Layers"), 0, False,
                -1)
            self._progress.show()
            Job.yieldThread()
            if self._abort_requested:
                if self._progress:
                    self._progress.hide()
                return

        Application.getInstance().getController().activeViewChanged.connect(
            self._onActiveViewChanged)

        object_id_map = {}
        new_node = SceneNode()

        ## Remove old layer data (if any)
        for node in DepthFirstIterator(self._scene.getRoot()):
            if type(node) is SceneNode and node.getMeshData():
                if node.callDecoration("getLayerData"):
                    self._scene.getRoot().removeChild(node)
            Job.yieldThread()
            if self._abort_requested:
                if self._progress:
                    self._progress.hide()
                return

        settings = Application.getInstance().getMachineManager(
        ).getWorkingProfile()

        mesh = MeshData()
        layer_data = LayerData.LayerData()
        layer_count = len(self._layers)

        current_layer = 0

        for layer in self._layers:
            layer_data.addLayer(layer.id)
            layer_data.setLayerHeight(layer.id, layer.height)
            layer_data.setLayerThickness(layer.id, layer.thickness)

            for p in range(layer.repeatedMessageCount("polygons")):
                polygon = layer.getRepeatedMessage("polygons", p)

                points = numpy.fromstring(
                    polygon.points,
                    dtype="i8")  # Convert bytearray to numpy array
                points = points.reshape(
                    (-1, 2)
                )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                # Create a new 3D-array, copy the 2D points over and insert the right height.
                # This uses manual array creation + copy rather than numpy.insert since this is
                # faster.
                new_points = numpy.empty((len(points), 3), numpy.float32)
                new_points[:, 0] = points[:, 0]
                new_points[:, 1] = layer.height
                new_points[:, 2] = -points[:, 1]

                new_points /= 1000

                layer_data.addPolygon(layer.id, polygon.type, new_points,
                                      polygon.line_width)
                Job.yieldThread()
            Job.yieldThread()
            current_layer += 1
            progress = (current_layer / layer_count) * 100
            # TODO: Rebuild the layer data mesh once the layer has been processed.
            # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh.

            if self._abort_requested:
                if self._progress:
                    self._progress.hide()
                return
            if self._progress:
                self._progress.setProgress(progress)

        # We are done processing all the layers we got from the engine, now create a mesh out of the data
        layer_data.build()

        if self._abort_requested:
            if self._progress:
                self._progress.hide()
            return

        #Add layerdata decorator to scene node to indicate that the node has layerdata
        decorator = LayerDataDecorator.LayerDataDecorator()
        decorator.setLayerData(layer_data)
        new_node.addDecorator(decorator)

        new_node.setMeshData(mesh)
        new_node.setParent(
            self._scene.getRoot())  #Note: After this we can no longer abort!

        if not settings.getSettingValue("machine_center_is_zero"):
            new_node.setPosition(
                Vector(-settings.getSettingValue("machine_width") / 2, 0.0,
                       settings.getSettingValue("machine_depth") / 2))

        if self._progress:
            self._progress.setProgress(100)

        view = Application.getInstance().getController().getActiveView()
        if view.getPluginId() == "LayerView":
            view.resetLayerData()

        if self._progress:
            self._progress.hide()
示例#41
0
    def read(self, file_name):
        result = None
        extension = os.path.splitext(file_name)[1]
        if extension.lower() == self._supported_extension:
            result = SceneNode()
            # The base object of 3mf is a zipped archive.
            archive = zipfile.ZipFile(file_name, 'r')
            try:
                root = ET.parse(archive.open("3D/3dmodel.model"))

                # There can be multiple objects, try to load all of them.
                objects = root.findall("./3mf:resources/3mf:object", self._namespaces)
                for object in objects:
                    mesh = MeshData()
                    node = SceneNode()
                    vertex_list = []
                    #for vertex in object.mesh.vertices.vertex:
                    for vertex in object.findall(".//3mf:vertex", self._namespaces):
                        vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")])

                    triangles = object.findall(".//3mf:triangle", self._namespaces)

                    mesh.reserveFaceCount(len(triangles))
                    
                    #for triangle in object.mesh.triangles.triangle:
                    for triangle in triangles:
                        v1 = int(triangle.get("v1"))
                        v2 = int(triangle.get("v2"))
                        v3 = int(triangle.get("v3"))
                        mesh.addFace(vertex_list[v1][0],vertex_list[v1][1],vertex_list[v1][2],vertex_list[v2][0],vertex_list[v2][1],vertex_list[v2][2],vertex_list[v3][0],vertex_list[v3][1],vertex_list[v3][2])
                    #TODO: We currently do not check for normals and simply recalculate them. 
                    mesh.calculateNormals()
                    node.setMeshData(mesh)
                    node.setSelectable(True)
                    
                    transformation = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(object.get("id")), self._namespaces)
                    if transformation:
                        transformation = transformation[0]

                    if transformation.get("transform"):
                        splitted_transformation = transformation.get("transform").split()
                        ## Transformation is saved as:
                        ## M00 M01 M02 0.0
                        ## M10 M11 M12 0.0
                        ## M20 M21 M22 0.0
                        ## M30 M31 M32 1.0
                        ## We switch the row & cols as that is how everyone else uses matrices!
                        temp_mat = Matrix()
                        # Rotation & Scale
                        temp_mat._data[0,0] = splitted_transformation[0]
                        temp_mat._data[1,0] = splitted_transformation[1]
                        temp_mat._data[2,0] = splitted_transformation[2]
                        temp_mat._data[0,1] = splitted_transformation[3]
                        temp_mat._data[1,1] = splitted_transformation[4]
                        temp_mat._data[2,1] = splitted_transformation[5]
                        temp_mat._data[0,2] = splitted_transformation[6]
                        temp_mat._data[1,2] = splitted_transformation[7]
                        temp_mat._data[2,2] = splitted_transformation[8]
                        
                        # Translation
                        temp_mat._data[0,3] = splitted_transformation[9]
                        temp_mat._data[1,3] = splitted_transformation[10]
                        temp_mat._data[2,3] = splitted_transformation[11]
                        
                        node.setPosition(Vector(temp_mat.at(0,3), temp_mat.at(1,3), temp_mat.at(2,3)))
                        
                        temp_quaternion = Quaternion()
                        temp_quaternion.setByMatrix(temp_mat)
                        node.setOrientation(temp_quaternion)
                        
                        # Magical scale extraction
                        S2 = temp_mat.getTransposed().multiply(temp_mat)
                        scale_x = math.sqrt(S2.at(0,0))
                        scale_y = math.sqrt(S2.at(1,1))
                        scale_z = math.sqrt(S2.at(2,2))
                        node.setScale(Vector(scale_x,scale_y,scale_z))
                        
                        # We use a different coordinate frame, so rotate.
                        rotation = Quaternion.fromAngleAxis(-0.5 * math.pi, Vector(1,0,0))
                        node.rotate(rotation)
                    result.addChild(node)

                #If there is more then one object, group them.
                try:
                    if len(objects) > 1:
                        group_decorator = GroupDecorator()
                        result.addDecorator(group_decorator)
                except:
                    pass
            except Exception as e:
                Logger.log("e" ,"exception occured in 3mf reader: %s" , e)      
        return result  
示例#42
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)
示例#43
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)
示例#44
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)
示例#45
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()
示例#46
0
文件: LayerView.py 项目: cysheen/Cura
class LayerView(View):
    def __init__(self):
        super().__init__()
        self._material = None
        self._num_layers = 0
        self._layer_percentage = 0 # what percentage of layers need to be shown (SLider gives value between 0 - 100)
        self._proxy = LayerViewProxy.LayerViewProxy()
        self._controller.getScene().sceneChanged.connect(self._onSceneChanged)
        self._max_layers = 10
        self._current_layer_num = 10
        self._current_layer_mesh = None
        self._activity = False

        self._solid_layers = 5

    def getActivity(self):
        return self._activity

    def getCurrentLayer(self):
        return self._current_layer_num
    
    def _onSceneChanged(self, node):
        self.calculateMaxLayers()
    
    def getMaxLayers(self):
        return self._max_layers

    def resetLayerData(self):
        self._current_layer_mesh = None

    def beginRendering(self):
        scene = self.getController().getScene()
        renderer = self.getRenderer()
        renderer.setRenderSelection(False)

        if not self._material:
            self._material = renderer.createMaterial(Resources.getPath(Resources.Shaders, "basic.vert"), Resources.getPath(Resources.Shaders, "vertexcolor.frag"))
            self._material.setUniformValue("u_color", [1.0, 0.0, 0.0, 1.0])

            self._selection_material = renderer.createMaterial(Resources.getPath(Resources.Shaders, "basic.vert"), Resources.getPath(Resources.Shaders, "color.frag"))
            self._selection_material.setUniformValue("u_color", Color(35, 35, 35, 128))

        for node in DepthFirstIterator(scene.getRoot()):
            # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
            # However, it is somewhat relevant when the node is selected, so do render it then.
            if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()):
                continue

            if not node.render(renderer):
                if node.getMeshData() and node.isVisible():
                    if Selection.isSelected(node):
                        renderer.queueNode(node, material = self._selection_material, transparent = True)
                    layer_data = node.callDecoration("getLayerData")
                    if not layer_data:
                        continue

                    # Render all layers below a certain number as line mesh instead of vertices.
                    if self._current_layer_num - self._solid_layers > -1:
                        start = 0
                        end = 0
                        element_counts = layer_data.getElementCounts()
                        for layer, counts in element_counts.items():
                            if layer + self._solid_layers > self._current_layer_num:
                                break
                            end += counts

                        # This uses glDrawRangeElements internally to only draw a certain range of lines.
                        renderer.queueNode(node, mesh = layer_data, material = self._material, mode = Renderer.RenderLines, start = start, end = end)

                    # We currently recreate the current "solid" layers every time a
                    if not self._current_layer_mesh:
                        self._current_layer_mesh = MeshData()
                        for i in range(self._solid_layers):
                            layer = self._current_layer_num - i
                            if layer < 0:
                                continue

                            layer_mesh = layer_data.getLayer(layer).createMesh()
                            if not layer_mesh or layer_mesh.getVertices() is None:
                                continue

                            self._current_layer_mesh.addVertices(layer_mesh.getVertices())

                            # Scale layer color by a brightness factor based on the current layer number
                            # This will result in a range of 0.5 - 1.0 to multiply colors by.
                            brightness = (2.0 - (i / self._solid_layers)) / 2.0
                            self._current_layer_mesh.addColors(layer_mesh.getColors() * brightness)

                    renderer.queueNode(node, mesh = self._current_layer_mesh, material = self._material)

    def setLayer(self, value):
        if self._current_layer_num != value:
            self._current_layer_num = value
            if self._current_layer_num < 0:
                self._current_layer_num = 0
            if self._current_layer_num > self._max_layers:
                self._current_layer_num = self._max_layers

            self._current_layer_mesh = None
            self.currentLayerNumChanged.emit()

    currentLayerNumChanged = Signal()

    def calculateMaxLayers(self):
        scene = self.getController().getScene()
        renderer = self.getRenderer()
        if renderer and self._material:
            self._activity = True
            renderer.setRenderSelection(False)
            self._old_max_layers = self._max_layers
            ## Recalculate num max layers
            new_max_layers = 0
            for node in DepthFirstIterator(scene.getRoot()):
                if not node.render(renderer):
                    if node.getMeshData() and node.isVisible():
                        
                        layer_data = node.callDecoration("getLayerData")
                        if not layer_data:
                            continue

                        if new_max_layers < len(layer_data.getLayers()):
                            new_max_layers = len(layer_data.getLayers()) - 1

            if new_max_layers > 0 and new_max_layers != self._old_max_layers:
                self._max_layers = new_max_layers
                self.maxLayersChanged.emit()
                self._current_layer_num = self._max_layers

                # This makes sure we update the current layer
                self.setLayer(int(self._max_layers))
                self.currentLayerNumChanged.emit()

    maxLayersChanged = Signal()
    currentLayerNumChanged = Signal()
    
    ##  Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created
    #   as this caused some issues. 
    def getProxy(self, engine, script_engine):
        return self._proxy
    
    def endRendering(self):
        pass
    
    def event(self, event):
        if event.type == Event.KeyPressEvent:
            if event.key == KeyEvent.UpKey:
                self.setLayer(self._current_layer_num + 1)
            if event.key == KeyEvent.DownKey:
                self.setLayer(self._current_layer_num - 1)
示例#47
0
 def __init__(self):
     self._mesh_data = MeshData()
示例#48
0
def test_setMeshData(camera):
    with pytest.raises(AssertionError):
        camera.setMeshData(MeshData())

    # This is allowed
    camera.setMeshData(None)
示例#49
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()
示例#50
0
 def getMeshDataTransformed(self) -> Optional[MeshData]:
     return MeshData(vertices=self.getMeshDataTransformedVertices())
示例#51
0
    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()
示例#52
0
    def _generateSceneNode(self, file_name, xz_size, peak_height, base_height,
                           blur_iterations, max_size, image_color_invert):
        mesh = None
        scene_node = None

        scene_node = SceneNode()

        mesh = MeshData()
        scene_node.setMeshData(mesh)

        img = QImage(file_name)

        if img.isNull():
            Logger.log("e", "Image is corrupt.")
            return None

        width = max(img.width(), 2)
        height = max(img.height(), 2)
        aspect = height / width

        if img.width() < 2 or img.height() < 2:
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        base_height = max(base_height, 0)
        peak_height = max(peak_height, -base_height)

        xz_size = max(xz_size, 1)
        scale_vector = Vector(xz_size, peak_height, xz_size)

        if width > height:
            scale_vector.setZ(scale_vector.z * aspect)
        elif height > width:
            scale_vector.setX(scale_vector.x / aspect)

        if width > max_size or height > max_size:
            scale_factor = max_size / width
            if height > width:
                scale_factor = max_size / height

            width = int(max(round(width * scale_factor), 2))
            height = int(max(round(height * scale_factor), 2))
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        width_minus_one = width - 1
        height_minus_one = height - 1

        Job.yieldThread()

        texel_width = 1.0 / (width_minus_one) * scale_vector.x
        texel_height = 1.0 / (height_minus_one) * scale_vector.z

        height_data = numpy.zeros((height, width), dtype=numpy.float32)

        for x in range(0, width):
            for y in range(0, height):
                qrgb = img.pixel(x, y)
                avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 *
                                                                        255)
                height_data[y, x] = avg

        Job.yieldThread()

        if image_color_invert:
            height_data = 1 - height_data

        for i in range(0, blur_iterations):
            copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode="edge")

            height_data += copy[1:-1, 2:]
            height_data += copy[1:-1, :-2]
            height_data += copy[2:, 1:-1]
            height_data += copy[:-2, 1:-1]

            height_data += copy[2:, 2:]
            height_data += copy[:-2, 2:]
            height_data += copy[2:, :-2]
            height_data += copy[:-2, :-2]

            height_data /= 9

            Job.yieldThread()

        height_data *= scale_vector.y
        height_data += base_height

        heightmap_face_count = 2 * height_minus_one * width_minus_one
        total_face_count = heightmap_face_count + (width_minus_one * 2) * (
            height_minus_one * 2) + 2

        mesh.reserveFaceCount(total_face_count)

        # initialize to texel space vertex offsets.
        # 6 is for 6 vertices for each texel quad.
        heightmap_vertices = numpy.zeros(
            (width_minus_one * height_minus_one, 6, 3), dtype=numpy.float32)
        heightmap_vertices = heightmap_vertices + numpy.array(
            [[[0, base_height, 0], [0, base_height, texel_height],
              [texel_width, base_height, texel_height],
              [texel_width, base_height, texel_height],
              [texel_width, base_height, 0], [0, base_height, 0]]],
            dtype=numpy.float32)

        offsetsz, offsetsx = numpy.mgrid[0:height_minus_one, 0:width - 1]
        offsetsx = numpy.array(offsetsx, numpy.float32).reshape(
            -1, 1) * texel_width
        offsetsz = numpy.array(offsetsz, numpy.float32).reshape(
            -1, 1) * texel_height

        # offsets for each texel quad
        heightmap_vertex_offsets = numpy.concatenate([
            offsetsx,
            numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]),
                        dtype=numpy.float32), offsetsz
        ], 1)
        heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(
            -1, 6, 3)

        # apply height data to y values
        heightmap_vertices[:, 0,
                           1] = heightmap_vertices[:, 5,
                                                   1] = height_data[:-1, :
                                                                    -1].reshape(
                                                                        -1)
        heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1)
        heightmap_vertices[:, 2,
                           1] = heightmap_vertices[:, 3, 1] = height_data[
                               1:, 1:].reshape(-1)
        heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1)

        heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count *
                                                    3],
                                        dtype=numpy.int32).reshape(-1, 3)

        mesh._vertices[0:(heightmap_vertices.size //
                          3), :] = heightmap_vertices.reshape(-1, 3)
        mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices

        mesh._vertex_count = heightmap_vertices.size // 3
        mesh._face_count = heightmap_indices.size // 3

        geo_width = width_minus_one * texel_width
        geo_height = height_minus_one * texel_height

        # bottom
        mesh.addFace(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height)
        mesh.addFace(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0)

        # north and south walls
        for n in range(0, width_minus_one):
            x = n * texel_width
            nx = (n + 1) * texel_width

            hn0 = height_data[0, n]
            hn1 = height_data[0, n + 1]

            hs0 = height_data[height_minus_one, n]
            hs1 = height_data[height_minus_one, n + 1]

            mesh.addFace(x, 0, 0, nx, 0, 0, nx, hn1, 0)
            mesh.addFace(nx, hn1, 0, x, hn0, 0, x, 0, 0)

            mesh.addFace(x, 0, geo_height, nx, 0, geo_height, nx, hs1,
                         geo_height)
            mesh.addFace(nx, hs1, geo_height, x, hs0, geo_height, x, 0,
                         geo_height)

        # west and east walls
        for n in range(0, height_minus_one):
            y = n * texel_height
            ny = (n + 1) * texel_height

            hw0 = height_data[n, 0]
            hw1 = height_data[n + 1, 0]

            he0 = height_data[n, width_minus_one]
            he1 = height_data[n + 1, width_minus_one]

            mesh.addFace(0, 0, y, 0, 0, ny, 0, hw1, ny)
            mesh.addFace(0, hw1, ny, 0, hw0, y, 0, 0, y)

            mesh.addFace(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny)
            mesh.addFace(geo_width, he1, ny, geo_width, he0, y, geo_width, 0,
                         y)

        mesh.calculateNormals(fast=True)

        return scene_node
示例#53
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)
示例#54
0
    def read(self, file_name):
        mesh = None
        scene_node = None

        extension = os.path.splitext(file_name)[1]
        if extension.lower() in self._supported_extensions:
            vertex_list = []
            normal_list = []
            uv_list = []
            face_list = []
            scene_node = SceneNode()

            mesh = MeshData()
            scene_node.setMeshData(mesh)
            f = open(file_name, "rt")
            for line in f:
                parts = line.split()
                if len(parts) < 1:
                    continue
                if parts[0] == "v":
                    vertex_list.append([float(parts[1]), float(parts[3]), -float(parts[2])])
                if parts[0] == "vn":
                    normal_list.append([float(parts[1]), float(parts[3]), -float(parts[2])])
                if parts[0] == "vt":
                    uv_list.append([float(parts[1]), float(parts[2])])
                if parts[0] == "f":
                    parts = [i for i in map(lambda p: p.split("/"), parts)]
                    for idx in range(1, len(parts)-2):
                        data = [int(parts[1][0]), int(parts[idx+1][0]), int(parts[idx+2][0])]
                        if len(parts[1]) > 2:
                            data += [int(parts[1][2]), int(parts[idx+1][2]), int(parts[idx+2][2])]

                            if parts[1][1] and parts[idx+1][1] and parts[idx+2][1]:
                                data += [int(parts[1][1]), int(parts[idx+1][1]), int(parts[idx+2][1])]
                        face_list.append(data)
                Job.yieldThread()
            f.close()

            mesh.reserveVertexCount(3 * len(face_list))
            num_vertices = len(vertex_list)
            num_normals = len(normal_list)

            for face in face_list:
                # Substract 1 from index, as obj starts counting at 1 instead of 0
                i = face[0] - 1
                j = face[1] - 1
                k = face[2] - 1

                if len(face) > 3:
                    ni = face[3] - 1
                    nj = face[4] - 1
                    nk = face[5] - 1
                else:
                    ni = -1
                    nj = -1
                    nk = -1

                if len(face) > 6:
                    ui = face[6] - 1
                    uj = face[7] - 1
                    uk = face[8] - 1
                else:
                    ui = -1
                    uj = -1
                    uk = -1

                #TODO: improve this handling, this can cause weird errors (negative indexes are relative indexes, and are not properly handled)
                if i < 0 or i >= num_vertices:
                    i = 0
                if j < 0 or j >= num_vertices:
                    j = 0
                if k < 0 or k >= num_vertices:
                    k = 0
                if ni != -1 and nj != -1 and nk != -1:
                    mesh.addFaceWithNormals(vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], normal_list[ni][0], normal_list[ni][1], normal_list[ni][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], normal_list[nj][0], normal_list[nj][1], normal_list[nj][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2],normal_list[nk][0], normal_list[nk][1], normal_list[nk][2])
                else:
                    mesh.addFace(vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2])

                if ui != -1:
                    mesh.setVertexUVCoordinates(mesh.getVertexCount() - 3, uv_list[ui][0], uv_list[ui][1])

                if uj != -1:
                    mesh.setVertexUVCoordinates(mesh.getVertexCount() - 2, uv_list[uj][0], uv_list[uj][1])

                if uk != -1:
                    mesh.setVertexUVCoordinates(mesh.getVertexCount() - 1, uv_list[uk][0], uv_list[uk][1])

                Job.yieldThread()
            if not mesh.hasNormals():
                mesh.calculateNormals(fast = True)

        return scene_node
示例#55
0
    def beginRendering(self):
        scene = self.getController().getScene()
        renderer = self.getRenderer()

        if not self._selection_shader:
            self._selection_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "default.shader"))
            self._selection_shader.setUniformValue("u_color", Color(32, 32, 32, 128))

        for node in DepthFirstIterator(scene.getRoot()):
            # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
            # However, it is somewhat relevant when the node is selected, so do render it then.
            if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()):
                continue

            if not node.render(renderer):
                if node.getMeshData() and node.isVisible():
                    if Selection.isSelected(node):
                        renderer.queueNode(node, transparent = True, shader = self._selection_shader)
                    layer_data = node.callDecoration("getLayerData")
                    if not layer_data:
                        continue

                    # Render all layers below a certain number as line mesh instead of vertices.
                    if self._current_layer_num - self._solid_layers > -1:
                        start = 0
                        end = 0
                        element_counts = layer_data.getElementCounts()
                        for layer, counts in element_counts.items():
                            if layer + self._solid_layers > self._current_layer_num:
                                break
                            end += counts

                        # This uses glDrawRangeElements internally to only draw a certain range of lines.
                        renderer.queueNode(node, mesh = layer_data, mode = RenderBatch.RenderMode.Lines, range = (start, end))

                    # We currently recreate the current "solid" layers every time a
                    if not self._current_layer_mesh:
                        self._current_layer_mesh = MeshData()
                        for i in range(self._solid_layers):
                            layer = self._current_layer_num - i
                            if layer < 0:
                                continue
                            try:
                                layer_mesh = layer_data.getLayer(layer).createMesh()
                                if not layer_mesh or layer_mesh.getVertices() is None:
                                    continue
                            except:
                                continue
                            if self._current_layer_mesh: #Threading thing; Switching between views can cause the current layer mesh to be deleted.
                                self._current_layer_mesh.addVertices(layer_mesh.getVertices())

                            # Scale layer color by a brightness factor based on the current layer number
                            # This will result in a range of 0.5 - 1.0 to multiply colors by.
                            brightness = (2.0 - (i / self._solid_layers)) / 2.0
                            if self._current_layer_mesh:
                                self._current_layer_mesh.addColors(layer_mesh.getColors() * brightness)
                    if self._current_layer_mesh:
                        renderer.queueNode(node, mesh = self._current_layer_mesh)

                    if not self._current_layer_jumps:
                        self._current_layer_jumps = MeshData()
                        for i in range(1):
                            layer = self._current_layer_num - i
                            if layer < 0:
                                continue
                            try:
                                layer_mesh = layer_data.getLayer(layer).createJumps()
                                if not layer_mesh or layer_mesh.getVertices() is None:
                                    continue
                            except:
                                continue

                            self._current_layer_jumps.addVertices(layer_mesh.getVertices())

                            # Scale layer color by a brightness factor based on the current layer number
                            # This will result in a range of 0.5 - 1.0 to multiply colors by.
                            brightness = (2.0 - (i / self._solid_layers)) / 2.0
                            self._current_layer_jumps.addColors(layer_mesh.getColors() * brightness)

                    renderer.queueNode(node, mesh = self._current_layer_jumps)
示例#56
0
    def run(self):
        if Application.getInstance().getController().getActiveView(
        ).getPluginId() == "LayerView":
            self._progress = Message(
                catalog.i18nc("@info:status", "Processing Layers"), 0, False,
                -1)
            self._progress.show()

        Application.getInstance().getController().activeViewChanged.connect(
            self._onActiveViewChanged)

        objectIdMap = {}
        new_node = SceneNode()
        ## Put all nodes in a dict identified by ID
        for node in DepthFirstIterator(self._scene.getRoot()):
            if type(node) is SceneNode and node.getMeshData():
                if node.callDecoration("getLayerData"):
                    self._scene.getRoot().removeChild(node)
                else:
                    objectIdMap[id(node)] = node
            Job.yieldThread()

        settings = Application.getInstance().getMachineManager(
        ).getActiveProfile()
        layerHeight = settings.getSettingValue("layer_height")

        center = None
        if not settings.getSettingValue("machine_center_is_zero"):
            center = numpy.array([
                settings.getSettingValue("machine_width") / 2, 0.0,
                -settings.getSettingValue("machine_depth") / 2
            ])
        else:
            center = numpy.array([0.0, 0.0, 0.0])

        mesh = MeshData()
        layer_data = LayerData.LayerData()

        layer_count = 0
        for object in self._message.objects:
            layer_count += len(object.layers)

        current_layer = 0
        for object in self._message.objects:
            try:
                node = objectIdMap[object.id]
            except KeyError:
                continue

            for layer in object.layers:
                layer_data.addLayer(layer.id)
                layer_data.setLayerHeight(layer.id, layer.height)
                layer_data.setLayerThickness(layer.id, layer.thickness)
                for polygon in layer.polygons:
                    points = numpy.fromstring(
                        polygon.points,
                        dtype="i8")  # Convert bytearray to numpy array
                    points = points.reshape(
                        (-1, 2)
                    )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
                    points = numpy.asarray(points, dtype=numpy.float32)
                    points /= 1000
                    points = numpy.insert(points,
                                          1, (layer.height / 1000),
                                          axis=1)

                    points[:, 2] *= -1

                    points -= center

                    layer_data.addPolygon(layer.id, polygon.type, points,
                                          polygon.line_width)

                Job.yieldThread()

                current_layer += 1
                progress = (current_layer / layer_count) * 100
                # TODO: Rebuild the layer data mesh once the layer has been processed.
                # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh.

                if self._progress:
                    self._progress.setProgress(progress)

        # We are done processing all the layers we got from the engine, now create a mesh out of the data
        layer_data.build()

        #Add layerdata decorator to scene node to indicate that the node has layerdata
        decorator = LayerDataDecorator.LayerDataDecorator()
        decorator.setLayerData(layer_data)
        new_node.addDecorator(decorator)

        new_node.setMeshData(mesh)
        new_node.setParent(self._scene.getRoot())

        if self._progress:
            self._progress.setProgress(100)

        view = Application.getInstance().getController().getActiveView()
        if view.getPluginId() == "LayerView":
            view.resetLayerData()

        if self._progress:
            self._progress.hide()
示例#57
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())