예제 #1
0
    def _rotateCamera(self, x, y):
        camera = self._scene.getActiveCamera()
        if not camera or not camera.isEnabled():
            return

        self._scene.acquireLock()

        dx = math.radians(x * 180.0)
        dy = math.radians(y * 180.0)

        diff = camera.getPosition() - self._origin

        diff_flat = Vector(diff.x, 0.0, diff.z).getNormalized()
        try:
            new_angle = math.acos(diff_flat.dot(diff.getNormalized())) + dy
        except ValueError:
            return

        m = Matrix()
        m.setByRotationAxis(dx, Vector.Unit_Y)
        if new_angle < (math.pi / 2 - 0.01):
            m.rotateByAxis(dy, Vector.Unit_Y.cross(diff).normalize())

        n = diff.multiply(m)
        n += self._origin

        camera.setPosition(n)
        camera.lookAt(self._origin)

        self._scene.releaseLock()
예제 #2
0
def createGroupOperationForArrange(nodes_to_arrange: List["SceneNode"],
                                   build_volume: "BuildVolume",
                                   fixed_nodes: Optional[List["SceneNode"]] = None,
                                   factor = 10000,
                                   add_new_nodes_in_scene: bool = False)  -> Tuple[GroupedOperation, int]:
    scene_root = Application.getInstance().getController().getScene().getRoot()
    found_solution_for_all, node_items = findNodePlacement(nodes_to_arrange, build_volume, fixed_nodes, factor)

    not_fit_count = 0
    grouped_operation = GroupedOperation()
    for node, node_item in zip(nodes_to_arrange, node_items):
        if add_new_nodes_in_scene:
            grouped_operation.addOperation(AddSceneNodeOperation(node, scene_root))

        if node_item.binId() == 0:
            # We found a spot for it
            rotation_matrix = Matrix()
            rotation_matrix.setByRotationAxis(node_item.rotation(), Vector(0, -1, 0))
            grouped_operation.addOperation(RotateOperation(node, Quaternion.fromMatrix(rotation_matrix)))
            grouped_operation.addOperation(TranslateOperation(node, Vector(node_item.translation().x() / factor, 0,
                                                                           node_item.translation().y() / factor)))
        else:
            # We didn't find a spot
            grouped_operation.addOperation(
                TranslateOperation(node, Vector(200, node.getWorldPosition().y, -not_fit_count * 20), set_position = True))
            not_fit_count += 1

    return grouped_operation, not_fit_count
예제 #3
0
    def test_toMatrix(self):
        q1 = Quaternion()
        q1.setByAngleAxis(math.pi / 2, Vector.Unit_Z)

        m1 = q1.toMatrix()

        m2 = Matrix()
        m2.setByRotationAxis(math.pi / 2, Vector.Unit_Z)

        self.assertTrue(Float.fuzzyCompare(m1.at(0, 0), m2.at(0, 0), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(0, 1), m2.at(0, 1), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(0, 2), m2.at(0, 2), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(0, 3), m2.at(0, 3), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(1, 0), m2.at(1, 0), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(1, 1), m2.at(1, 1), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(1, 2), m2.at(1, 2), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(1, 3), m2.at(1, 3), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(2, 0), m2.at(2, 0), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(2, 1), m2.at(2, 1), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(2, 2), m2.at(2, 2), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(2, 3), m2.at(2, 3), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(3, 0), m2.at(3, 0), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(3, 1), m2.at(3, 1), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(3, 2), m2.at(3, 2), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(3, 3), m2.at(3, 3), 1e-6))
예제 #4
0
    def _rotateCamera(self, x: float, y: float) -> None:
        camera = self._scene.getActiveCamera()
        if not camera or not camera.isEnabled():
            return

        self._scene.getSceneLock().acquire()

        dx = math.radians(x * 180.0)
        dy = math.radians(y * 180.0)

        diff = camera.getPosition() - self._origin
        my = Matrix()
        my.setByRotationAxis(dx, Vector.Unit_Y)

        mx = Matrix(my.getData())
        mx.rotateByAxis(dy, Vector.Unit_Y.cross(diff).normalized())

        n = diff.multiply(mx)

        try:
            angle = math.acos(Vector.Unit_Y.dot(n.normalized()))
        except ValueError:
            return

        if angle < 0.1 or angle > math.pi - 0.1:
            n = diff.multiply(my)

        n += self._origin

        camera.setPosition(n)
        camera.lookAt(self._origin)

        self._scene.getSceneLock().release()
예제 #5
0
    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)
예제 #6
0
    def _rotateCamera(self, x: float, y: float) -> None:
        """Rotate the camera in response to a mouse event.
        
        :param x: Amount by which the camera should be rotated horizontally, expressed in pixelunits
        :param y: Amount by which the camera should be rotated vertically, expressed in pixelunits
        """

        camera = self._scene.getActiveCamera()
        if not camera or not camera.isEnabled():
            return

        dx = math.radians(x * 180.0)
        dy = math.radians(y * 180.0)

        diff = camera.getPosition() - self._origin
        my = Matrix()
        my.setByRotationAxis(dx, Vector.Unit_Y)

        mx = Matrix(my.getData())
        mx.rotateByAxis(dy, Vector.Unit_Y.cross(diff).normalized())

        n = diff.multiply(mx)

        try:
            angle = math.acos(Vector.Unit_Y.dot(n.normalized()))
        except ValueError:
            return

        if angle < 0.1 or angle > math.pi - 0.1:
            n = diff.multiply(my)

        n += self._origin

        camera.setPosition(n)
        camera.lookAt(self._origin)
예제 #7
0
    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).normalized() * radius
        else:
            start = axis.cross(Vector.Unit_Y).normalized() * radius

        angle_increment = angle / sections
        current_angle = 0

        point = start + center
        m = Matrix()
        while current_angle <= angle:  #Add each of the vertices.
            self.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.addVertex(point.x, point.y, point.z)

            if color:  #If we have a colour, add that colour to the new vertex.
                self.setVertexColor(self.getVertexCount() - 2, color)
                self.setVertexColor(self.getVertexCount() - 1, color)
    def _rotateCameraFree(angle: float, axisX: float, axisY: float,
                          axisZ: float) -> None:
        if SpaceMouseTool._rotationLocked:
            return
        camera = SpaceMouseTool._scene.getActiveCamera()
        if not camera or not camera.isEnabled():
            return

        # compute axis in view space:
        # space mouse system: x: right, y: front, z: down
        # camera system:     x: right, y: up,    z: front
        # i.e. rotate the vector about x by 90 degrees in mathematical positive sense
        axisInViewSpace = np.array([-axisX, axisZ, -axisY, 1])

        # get inverse view matrix
        invViewMatrix = camera.getWorldTransformation().getData()

        # compute rotation axis in world space
        axisInWorldSpace = homogenize(np.dot(invViewMatrix, axisInViewSpace))
        originInWorldSpace = homogenize(
            np.dot(invViewMatrix, np.array([0, 0, 0, 1])))

        axisInWorldSpace = axisInWorldSpace - originInWorldSpace
        axisInWorldSpace = Vector(data=axisInWorldSpace)

        # rotate camera around that axis by angle
        rotOrigin = SpaceMouseTool._cameraTool.getOrigin()

        # rotation matrix around the axis
        rotMat = Matrix()
        rotMat.setByRotationAxis(angle, axisInWorldSpace, rotOrigin.getData())
        camera.setTransformation(
            camera.getLocalTransformation().preMultiply(rotMat))
예제 #9
0
    def test_toMatrix(self):
        q1 = Quaternion()
        q1.setByAngleAxis(math.pi / 2, Vector.Unit_Z)

        m1 = q1.toMatrix()

        m2 = Matrix()
        m2.setByRotationAxis(math.pi / 2, Vector.Unit_Z)

        self.assertTrue(Float.fuzzyCompare(m1.at(0, 0), m2.at(0, 0), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(0, 1), m2.at(0, 1), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(0, 2), m2.at(0, 2), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(0, 3), m2.at(0, 3), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(1, 0), m2.at(1, 0), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(1, 1), m2.at(1, 1), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(1, 2), m2.at(1, 2), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(1, 3), m2.at(1, 3), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(2, 0), m2.at(2, 0), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(2, 1), m2.at(2, 1), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(2, 2), m2.at(2, 2), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(2, 3), m2.at(2, 3), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(3, 0), m2.at(3, 0), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(3, 1), m2.at(3, 1), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(3, 2), m2.at(3, 2), 1e-6))
        self.assertTrue(Float.fuzzyCompare(m1.at(3, 3), m2.at(3, 3), 1e-6))
예제 #10
0
    def _rotateCamera(self, x, y):
        camera = self._scene.getActiveCamera()
        if not camera or not camera.isEnabled():
            return

        self._scene.acquireLock()

        dx = math.radians(x * 180.0)
        dy = math.radians(y * 180.0)

        diff = camera.getPosition() - self._origin
        my = Matrix()
        my.setByRotationAxis(dx, Vector.Unit_Y)

        mx = Matrix(my.getData())
        mx.rotateByAxis(dy, Vector.Unit_Y.cross(diff).normalized())

        n = diff.multiply(mx)

        try:
            angle = math.acos(Vector.Unit_Y.dot(n.normalized()))
        except ValueError:
            return

        if angle < 0.1 or angle > math.pi - 0.1:
            n = diff.multiply(my)

        n += self._origin

        camera.setPosition(n)
        camera.lookAt(self._origin)

        self._scene.releaseLock()
예제 #11
0
    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.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.addVertices(vertices)
        self.addIndices(numpy.asarray(indices, dtype = numpy.int32))
        self.addColors(numpy.asarray(colors, dtype = numpy.float32))
예제 #12
0
    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.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.addVertices(vertices)
        self.addIndices(numpy.asarray(indices, dtype = numpy.int32))
        self.addColors(numpy.asarray(colors, dtype = numpy.float32))
예제 #13
0
    def test_fromMatrix(self):
        m = Matrix()
        m.setByRotationAxis(math.pi / 2, Vector.Unit_Z)

        q1 = Quaternion.fromMatrix(m)

        q2 = Quaternion()
        q2.setByAngleAxis(math.pi / 2, Vector.Unit_Z)

        self.assertTrue(Float.fuzzyCompare(q1.x, q2.x, 1e-6))
        self.assertTrue(Float.fuzzyCompare(q1.y, q2.y, 1e-6))
        self.assertTrue(Float.fuzzyCompare(q1.z, q2.z, 1e-6))
        self.assertTrue(Float.fuzzyCompare(q1.w, q2.w, 1e-6))
예제 #14
0
    def test_fromMatrix(self):
        m = Matrix()
        m.setByRotationAxis(math.pi / 2, Vector.Unit_Z)

        q1 = Quaternion.fromMatrix(m)

        q2 = Quaternion()
        q2.setByAngleAxis(math.pi / 2, Vector.Unit_Z)

        self.assertTrue(Float.fuzzyCompare(q1.x, q2.x, 1e-6))
        self.assertTrue(Float.fuzzyCompare(q1.y, q2.y, 1e-6))
        self.assertTrue(Float.fuzzyCompare(q1.z, q2.z, 1e-6))
        self.assertTrue(Float.fuzzyCompare(q1.w, q2.w, 1e-6))
예제 #15
0
def arrange(nodes_to_arrange: List["SceneNode"],
            build_volume: "BuildVolume",
            fixed_nodes: Optional[List["SceneNode"]] = None,
            factor=10000,
            add_new_nodes_in_scene: bool = False) -> bool:
    """
    Find placement for a set of scene nodes, and move them by using a single grouped operation.
    :param nodes_to_arrange: The list of nodes that need to be moved.
    :param build_volume: The build volume that we want to place the nodes in. It gets size & disallowed areas from this.
    :param fixed_nodes: List of nods that should not be moved, but should be used when deciding where the others nodes
                        are placed.
    :param factor: The library that we use is int based. This factor defines how accuracte we want it to be.
    :param add_new_nodes_in_scene: Whether to create new scene nodes before applying the transformations and rotations

    :return: found_solution_for_all: Whether the algorithm found a place on the buildplate for all the objects
    """
    scene_root = Application.getInstance().getController().getScene().getRoot()
    found_solution_for_all, node_items = findNodePlacement(
        nodes_to_arrange, build_volume, fixed_nodes, factor)

    not_fit_count = 0
    grouped_operation = GroupedOperation()
    for node, node_item in zip(nodes_to_arrange, node_items):
        if add_new_nodes_in_scene:
            grouped_operation.addOperation(
                AddSceneNodeOperation(node, scene_root))

        if node_item.binId() == 0:
            # We found a spot for it
            rotation_matrix = Matrix()
            rotation_matrix.setByRotationAxis(node_item.rotation(),
                                              Vector(0, -1, 0))
            grouped_operation.addOperation(
                RotateOperation(node, Quaternion.fromMatrix(rotation_matrix)))
            grouped_operation.addOperation(
                TranslateOperation(
                    node,
                    Vector(node_item.translation().x() / factor, 0,
                           node_item.translation().y() / factor)))
        else:
            # We didn't find a spot
            grouped_operation.addOperation(
                TranslateOperation(node,
                                   Vector(200,
                                          node.getWorldPosition().y,
                                          -not_fit_count * 20),
                                   set_position=True))
            not_fit_count += 1
    grouped_operation.push()

    return found_solution_for_all
예제 #16
0
    def addArc(self,
               radius,
               axis,
               angle=math.pi * 2,
               center=Vector(0, 0, 0),
               sections=32,
               color=None):
        """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.
        """

        #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).normalized() * radius
        else:
            start = axis.cross(Vector.Unit_Y).normalized() * radius

        angle_increment = angle / sections
        current_angle = 0

        point = start + center
        m = Matrix()
        while current_angle <= angle:  #Add each of the vertices.
            self.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.addVertex(point.x, point.y, point.z)

            if color:  #If we have a colour, add that colour to the new vertex.
                self.setVertexColor(self.getVertexCount() - 2, color)
                self.setVertexColor(self.getVertexCount() - 1, color)
예제 #17
0
    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.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.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.addIndices(indices)

        if color:  #If we have a colour, add the colour to each of the vertices.
            vertex_count = self.getVertexCount()
            for i in range(1, 6):
                self.setVertexColor(vertex_count - i, color)
예제 #18
0
    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)
예제 #19
0
    def _rotateCamera(self, x, y):
        camera = self._scene.getActiveCamera()
        if not camera or not camera.isEnabled():
            return

        self._scene.acquireLock()

        dx = math.radians(x * 180.0)
        dy = math.radians(y * 180.0)

        diff = camera.getPosition() - self._origin

        m = Matrix()
        m.setByRotationAxis(dx, Vector.Unit_Y)
        m.rotateByAxis(dy, Vector.Unit_Y.cross(diff).normalize())

        n = diff.multiply(m)
        n += self._origin

        camera.setPosition(n)
        camera.lookAt(self._origin)

        self._scene.releaseLock()
예제 #20
0
    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).normalized() * radius
        else:
            start = axis.cross(Vector.Unit_Y).normalized() * radius

        angle_increment = angle / sections
        current_angle = 0

        point = start + center
        m = Matrix()
        while current_angle <= angle: #Add each of the vertices.
            self.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.addVertex(point.x, point.y, point.z)

            if color: #If we have a colour, add that colour to the new vertex.
                self.setVertexColor(self.getVertexCount() - 2, color)
                self.setVertexColor(self.getVertexCount() - 1, color)
예제 #21
0
    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.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.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.addIndices(indices)

        if color: #If we have a colour, add the colour to each of the vertices.
            vertex_count = self.getVertexCount()
            for i in range(1, 6):
                self.setVertexColor(vertex_count - i, color)
예제 #22
0
    def _rotateCamera(self, yaw: float, pitch: float, roll: float) -> None:
        camera = self._scene.getActiveCamera()
        if not camera or not camera.isEnabled():
            return

        dyaw = math.radians(yaw * 180.0)
        dpitch = math.radians(pitch * 180.0)
        droll = math.radians(roll * 180.0)

        diff = camera.getPosition() - self._camera_tool._origin

        myaw = Matrix()
        myaw.setByRotationAxis(dyaw, Vector.Unit_Y)

        mpitch = Matrix(myaw.getData())
        mpitch.rotateByAxis(dpitch, Vector.Unit_Y.cross(diff))

        n = diff.multiply(mpitch)

        try:
            angle = math.acos(Vector.Unit_Y.dot(n.normalized()))
        except ValueError:
            return

        if angle < 0.1 or angle > math.pi - 0.1:
            n = diff.multiply(myaw)

        n += self._camera_tool._origin

        camera.setPosition(n)

        if abs(self._roll + droll) < math.pi * 0.45:
            self._roll += droll
        mroll = Matrix()
        mroll.setByRotationAxis(self._roll, (n - self._camera_tool._origin))
        camera.lookAt(self._camera_tool._origin, Vector.Unit_Y.multiply(mroll))
예제 #23
0
    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 read(self, file_name):
        result = None

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

                # Rotate the model; We use a different coordinate frame.
                rotation = Matrix()
                rotation.setByRotationAxis(-0.5 * math.pi, Vector(1, 0, 0))
                mesh = mesh.getTransformed(rotation)

                #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]
                try:
                    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.setTransformation(temp_mat)
                except AttributeError:
                    pass  # Empty list was found. Getting transformation is not possible

                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
예제 #25
0
    def addDonut(self,
                 inner_radius,
                 outer_radius,
                 width,
                 center=Vector(0, 0, 0),
                 sections=32,
                 color=None,
                 angle=0,
                 axis=Vector.Unit_Y):
        """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.
        """

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

        start = self.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.addVertices(vertices)
        self.addIndices(numpy.asarray(indices, dtype=numpy.int32))
        self.addColors(numpy.asarray(colors, dtype=numpy.float32))
예제 #26
0
    def read(self, file_name):
        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 entry in objects:
                mesh_builder = MeshBuilder()
                node = SceneNode()
                vertex_list = []
                #for vertex in entry.mesh.vertices.vertex:
                for vertex in entry.findall(".//3mf:vertex", self._namespaces):
                    vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")])
                    Job.yieldThread()

                triangles = entry.findall(".//3mf:triangle", self._namespaces)
                mesh_builder.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_builder.addFaceByPoints(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()

                # Rotate the model; We use a different coordinate frame.
                rotation = Matrix()
                rotation.setByRotationAxis(-0.5 * math.pi, Vector(1,0,0))

                #TODO: We currently do not check for normals and simply recalculate them.
                mesh_builder.calculateNormals()

                node.setMeshData(mesh_builder.build().getTransformed(rotation))
                node.setSelectable(True)

                transformations = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(entry.get("id")), self._namespaces)
                transformation = transformations[0] if transformations else None
                if transformation is not None and 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.setTransformation(temp_mat)

                result.addChild(node)

                Job.yieldThread()

            #If there is more then one object, group them.
            if len(objects) > 1:
                group_decorator = GroupDecorator()
                result.addDecorator(group_decorator)
        except Exception as e:
            Logger.log("e" ,"exception occured in 3mf reader: %s" , e)

        return result  
예제 #27
0
    def addPyramid(self,
                   width,
                   height,
                   depth,
                   angle=0,
                   axis=Vector.Unit_Y,
                   center=Vector(0, 0, 0),
                   color=None):
        """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.
        """

        angle = math.radians(angle)

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

        start = self.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.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.addIndices(indices)

        if color:  #If we have a colour, add the colour to each of the vertices.
            vertex_count = self.getVertexCount()
            for i in range(1, 6):
                self.setVertexColor(vertex_count - i, color)
예제 #28
0
class CliParser:

    def __init__(self) -> None:
        SteSlicerApplication.getInstance().hideMessageSignal.connect(self._onHideMessage)
        self._is_layers_in_file = False
        self._cancelled = False
        self._message = None
        self._layer_number = -1
        self._extruder_number = 0
        self._pi_faction = 0
        self._position = Position
        self._gcode_position = Position
        # stack to get print settingd via getProperty method
        self._application = SteSlicerApplication.getInstance()
        self._global_stack = self._application.getGlobalContainerStack() #type: GlobalStack
        self._licensed = self._application.getLicenseManager().licenseValid

        self._rot_nwp = Matrix()
        self._rot_nws = Matrix()

        self._scene_node = None
        
        self._extruder_number = 0
        # type: Dict[int, List[float]] # Offsets for multi extruders. key is index, value is [x-offset, y-offset]
        self._extruder_offsets = {}
        self._gcode_list = []
        self._current_layer_thickness = 0
        self._current_layer_height = 0

        #speeds
        self._travel_speed = 0
        self._wall_0_speed = 0
        self._skin_speed = 0
        self._infill_speed = 0
        self._support_speed = 0
        self._retraction_speed = 0
        self._prime_speed = 0

        #retraction
        self._enable_retraction = False
        self._retraction_amount = 0
        self._retraction_min_travel = 1.5
        self._retraction_hop_enabled = False
        self._retraction_hop = 1

        self._filament_diameter = 1.75
        self._line_width = 0.4
        self._layer_thickness = 0.2
        self._clearValues()

    _layer_keyword = "$$LAYER/"
    _geometry_end_keyword = "$$GEOMETRYEND"
    _body_type_keyword = "//body//"
    _support_type_keyword = "//support//"
    _skin_type_keyword = "//skin//"
    _infill_type_keyword = "//infill//"
    _perimeter_type_keyword = "//perimeter//"

    _type_keyword = ";TYPE:"

    def processCliStream(self, stream: str) -> Optional[SteSlicerSceneNode]:
        Logger.log("d", "Preparing to load CLI")
        self._cancelled = False
        self._setPrintSettings()
        self._is_layers_in_file = False

        scene_node = SteSlicerSceneNode()

        gcode_list = []
        self._writeStartCode(gcode_list)
        gcode_list.append(";LAYER_COUNT\n")

        # Reading starts here
        file_lines = 0
        current_line = 0
        for line in stream.split("\n"):
            file_lines += 1
            if not self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword:
                self._is_layers_in_file = True

        file_step = max(math.floor(file_lines / 100), 1)

        self._clearValues()

        self._message = Message(catalog.i18nc("@info:status", "Parsing CLI"),
                                lifetime=0,
                                title=catalog.i18nc("@info:title", "CLI Details"))

        assert(self._message is not None)  # use for typing purposes
        self._message.setProgress(0)
        self._message.show()

        Logger.log("d", "Parsing CLI...")

        self._position = Position(0, 0, 0, 0, 0, 1, 0, [0])
        self._gcode_position = Position(0, 0, 0, 0, 0, 0, 0, [0])
        current_path = []  # type: List[List[float]]
        geometry_start = False
        for line in stream.split("\n"):
            if self._cancelled:
                Logger.log("d", "Parsing CLI file cancelled")
                return None
            current_line += 1
            if current_line % file_step == 0:
                self._message.setProgress(math.floor(
                    current_line / file_lines * 100))
                Job.yieldThread()
            if len(line) == 0:
                continue
            if line == "$$GEOMETRYSTART":
                geometry_start = True
                continue
            if not geometry_start:
                continue

            if self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword:
                try:
                    layer_height = float(line[len(self._layer_keyword):])
                    self._current_layer_thickness = layer_height - self._current_layer_height
                    if self._current_layer_thickness > 0.4:
                        self._current_layer_thickness = 0.2
                    self._current_layer_height = layer_height
                    self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(
                        self._extruder_number, [0, 0]))
                    current_path.clear()

                    # Start the new layer at the end position of the last layer
                    current_path.append([self._position.x, self._position.y, self._position.z, self._position.a, self._position.b,
                                         self._position.c, self._position.f, self._position.e[self._extruder_number], LayerPolygon.MoveCombingType])
                    self._layer_number += 1
                    gcode_list.append(";LAYER:%s\n" % self._layer_number)
                except:
                    pass
                
            if line.find(self._body_type_keyword) == 0:
                self._layer_type = LayerPolygon.Inset0Type
            if line.find(self._support_type_keyword) == 0:
                self._layer_type = LayerPolygon.SupportType
            if line.find(self._perimeter_type_keyword) == 0:
                self._layer_type = LayerPolygon.Inset0Type
            if line.find(self._skin_type_keyword) == 0:
                self._layer_type = LayerPolygon.SkinType
            if line.find(self._infill_type_keyword) == 0:
                self._layer_type = LayerPolygon.InfillType

            # Comment line
            if line.startswith("//"):
                continue

            # Polyline processing
            self.processPolyline(line, current_path, gcode_list)

        # "Flush" leftovers. Last layer paths are still stored
        if len(current_path) > 1:
            if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
                self._layer_number += 1
                current_path.clear()

        layer_count_idx = gcode_list.index(";LAYER_COUNT\n")
        if layer_count_idx > 0:
            gcode_list[layer_count_idx] = ";LAYER_COUNT:%s\n" % self._layer_number

        end_gcode = self._global_stack.getProperty(
            "machine_end_gcode", "value")
        gcode_list.append(end_gcode + "\n")
        
        material_color_map = numpy.zeros((8, 4), dtype=numpy.float32)
        material_color_map[0, :] = [0.0, 0.7, 0.9, 1.0]
        material_color_map[1, :] = [0.7, 0.9, 0.0, 1.0]
        material_color_map[2, :] = [0.9, 0.0, 0.7, 1.0]
        material_color_map[3, :] = [0.7, 0.0, 0.0, 1.0]
        material_color_map[4, :] = [0.0, 0.7, 0.0, 1.0]
        material_color_map[5, :] = [0.0, 0.0, 0.7, 1.0]
        material_color_map[6, :] = [0.3, 0.3, 0.3, 1.0]
        material_color_map[7, :] = [0.7, 0.7, 0.7, 1.0]
        layer_mesh = self._layer_data_builder.build(material_color_map)
        decorator = LayerDataDecorator()
        decorator.setLayerData(layer_mesh)
        scene_node.addDecorator(decorator)

        gcode_list_decorator = GCodeListDecorator()
        gcode_list_decorator.setGCodeList(gcode_list)
        scene_node.addDecorator(gcode_list_decorator)

        # gcode_dict stores gcode_lists for a number of build plates.
        active_build_plate_id = SteSlicerApplication.getInstance(
        ).getMultiBuildPlateModel().activeBuildPlate
        gcode_dict = {active_build_plate_id: gcode_list}
        # type: ignore #Because gcode_dict is generated dynamically.
        SteSlicerApplication.getInstance().getController().getScene().gcode_dict = gcode_dict

        Logger.log("d", "Finished parsing CLI file")
        self._message.hide()

        if self._layer_number == 0:
            Logger.log("w", "File doesn't contain any valid layers")

        if not self._global_stack.getProperty("machine_center_is_zero", "value"):
            machine_width = self._global_stack.getProperty(
                "machine_width", "value")
            machine_depth = self._global_stack.getProperty(
                "machine_depth", "value")
            scene_node.setPosition(
                Vector(-machine_width / 2, 0, machine_depth / 2))

        Logger.log("d", "CLI loading finished")

        if SteSlicerApplication.getInstance().getPreferences().getValue("gcodereader/show_caution"):
            caution_message = Message(catalog.i18nc(
                "@info:generic",
                "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."),
                lifetime=0,
                title=catalog.i18nc("@info:title", "G-code Details"))
            caution_message.show()

        backend = SteSlicerApplication.getInstance().getBackend()
        backend.backendStateChange.emit(Backend.BackendState.Disabled)

        return scene_node

    def _setPrintSettings(self):
        pass

    def _onHideMessage(self, message: Optional[Union[str, Message]]) -> None:
        if message == self._message:
            self._cancelled = True

    def _clearValues(self):
        self._extruder_number = 0
        self._layer_number = -1
        self._layer_data_builder = LayerDataBuilder()
        self._pi_faction = 0
        self._position = Position(0,0,0,0,0,1,0,[0])
        self._gcode_position = Position(0, 0, 0, 0, 0, 0, 0, [0])
        self._rot_nwp = Matrix()
        self._rot_nws = Matrix()
        self._layer_type = LayerPolygon.Inset0Type

        self._parsing_type = self._global_stack.getProperty(
            "printing_mode", "value")
        self._line_width = self._global_stack.getProperty("wall_line_width_0", "value")
        self._layer_thickness = self._global_stack.getProperty("layer_height", "value")

        self._travel_speed = self._global_stack.getProperty(
            "speed_travel", "value")
        self._wall_0_speed = self._global_stack.getProperty(
            "speed_wall_0", "value")
        self._skin_speed = self._global_stack.getProperty(
            "speed_topbottom", "value")
        self._infill_speed = self._global_stack.getProperty("speed_infill", "value")
        self._support_speed = self._global_stack.getProperty(
            "speed_support", "value")
        self._retraction_speed = self._global_stack.getProperty(
            "retraction_retract_speed", "value")
        self._prime_speed = self._global_stack.getProperty(
            "retraction_prime_speed", "value")

        extruder = self._global_stack.extruders.get("%s" % self._extruder_number, None) #type: Optional[ExtruderStack]
        
        self._filament_diameter = extruder.getProperty(
            "material_diameter", "value")
        self._enable_retraction = extruder.getProperty(
            "retraction_enable", "value")
        self._retraction_amount = extruder.getProperty(
            "retraction_amount", "value")
        self._retraction_min_travel = extruder.getProperty(
            "retraction_min_travel", "value")
        self._retraction_hop_enabled = extruder.getProperty(
            "retraction_hop_enabled", "value")
        self._retraction_hop = extruder.getProperty(
            "retraction_hop", "value")

    def _transformCoordinates(self, x: float, y: float, z: float, i: float, j: float, k: float, position: Position) -> (float, float, float, float, float, float):
        a = position.a
        c = position.c
        # Get coordinate angles
        if abs(self._position.c - k) > 0.00001:
            a = math.acos(k)
            self._rot_nwp = Matrix()
            self._rot_nwp.setByRotationAxis(-a, Vector.Unit_X)
            a = degrees(a)
        if abs(self._position.a - i) > 0.00001 or abs(self._position.b - j) > 0.00001:
            c = numpy.arctan2(j, i) if x != 0 and y != 0 else 0
            angle = degrees(c + self._pi_faction * 2 * math.pi)
            if abs(angle - position.c) > 180:
                self._pi_faction += 1 if (angle - position.c) < 0 else -1
            c += self._pi_faction * 2 * math.pi
            c -= math.pi / 2
            self._rot_nws = Matrix()
            self._rot_nws.setByRotationAxis(c, Vector.Unit_Z)
            c = degrees(c)
        
        #tr = self._rot_nws.multiply(self._rot_nwp, True)
        tr = self._rot_nws.multiply(self._rot_nwp, True)
        #tr = tr.multiply(self._rot_nwp)
        tr.invert()
        pt = Vector(data=numpy.array([x, y, z, 1]))
        ret = tr.multiply(pt, True).getData()
        return Position(ret[0], ret[1], ret[2], a, 0, c, 0, [0])

    @staticmethod
    def _getValue(line: str, key: str) -> Optional[str]:
        n = line.find(key)
        if n < 0:
            return None
        n += len(key)
        splitted = line[n:].split("/")
        if len(splitted) > 1:
            return splitted[1]
        else:
            return None

    def _createPolygon(self, layer_thickness: float, path: List[List[Union[float, int]]], extruder_offsets: List[float]) -> bool:
        countvalid = 0
        for point in path:
            if point[8] > 0:
                countvalid += 1
                if countvalid >= 2:
                    # we know what to do now, no need to count further
                    continue
        if countvalid < 2:
            return False
        try:
            self._layer_data_builder.addLayer(self._layer_number)
            self._layer_data_builder.setLayerHeight(
                self._layer_number, self._current_layer_height)
            self._layer_data_builder.setLayerThickness(
                self._layer_number, layer_thickness)
            this_layer = self._layer_data_builder.getLayer(self._layer_number)
        except ValueError:
            return False
        count = len(path)
        line_types = numpy.empty((count - 1, 1), numpy.int32)
        line_widths = numpy.empty((count - 1, 1), numpy.float32)
        line_thicknesses = numpy.empty((count - 1, 1), numpy.float32)
        line_feedrates = numpy.empty((count - 1, 1), numpy.float32)
        line_widths[:, 0] = 0.35  # Just a guess
        line_thicknesses[:, 0] = layer_thickness
        points = numpy.empty((count, 6), numpy.float32)
        extrusion_values = numpy.empty((count, 1), numpy.float32)
        i = 0
        for point in path:

            points[i, :] = [point[0] + extruder_offsets[0], point[2], -point[1] - extruder_offsets[1],
                            -point[4], point[5], -point[3]]
            extrusion_values[i] = point[7]
            if i > 0:
                line_feedrates[i - 1] = point[6]
                line_types[i - 1] = point[8]
                if point[8] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
                    line_widths[i - 1] = 0.1
                    # Travels are set as zero thickness lines
                    line_thicknesses[i - 1] = 0.0
                else:
                    line_widths[i - 1] = self._line_width
            i += 1

        this_poly = LayerPolygon(self._extruder_number, line_types,
                                 points, line_widths, line_thicknesses, line_feedrates)
        this_poly.buildCache()

        this_layer.polygons.append(this_poly)
        return True

    def processPolyline(self, line: str, path: List[List[Union[float, int]]], gcode_list: List[str]) -> bool:
        # Convering line to point array
        values_line = self._getValue(line, "$$POLYLINE")
        if not values_line:
            return (self._position, None)
        values = values_line.split(",")
        if len(values[3:]) % 2 != 0:
            return (self._position, None)
        idx = 2
        points = values[3:]
        if len(points) < 2:
            return (self._position, None)
        # TODO: add combing to this polyline
        new_position, new_gcode_position = self._cliPointToPosition(
            CliPoint(float(points[0]), float(points[1])), self._position, False)
        
        is_retraction = self._enable_retraction and self._positionLength(
            self._position, new_position) > self._retraction_min_travel
        if is_retraction:
            #we have retraction move
            new_extruder_position = self._position.e[self._extruder_number] - self._retraction_amount
            gcode_list.append("G1 E%.5f F%.0f\n" % (new_extruder_position, (self._retraction_speed * 60)))
            self._position.e[self._extruder_number] = new_extruder_position
            self._gcode_position.e[self._extruder_number] = new_extruder_position
            path.append([self._position.x, self._position.y, self._position.z, self._position.a, self._position.b,
                         self._position.c, self._retraction_speed, self._position.e, LayerPolygon.MoveRetractionType])
        
            if self._retraction_hop_enabled:
                    #add hop movement
                    gx, gy, gz, ga, gb, gc, gf, ge = self._gcode_position
                    x, y, z, a, b, c, f, e = self._position
                    gcode_position = Position(
                        gx, gy, gz + self._retraction_hop, ga, gb, gc, self._travel_speed, ge)
                    self._position = Position(
                        x + a * self._retraction_hop, y + b * self._retraction_hop, z + c * self._retraction_hop, a, b, c, self._travel_speed, e)
                    gcode_command = self._generateGCodeCommand(
                        0, gcode_position, self._travel_speed)
                    if gcode_command is not None:
                        gcode_list.append(gcode_command)
                    self._gcode_position = gcode_position
                    path.append([self._position.x, self._position.y, self._position.z, self._position.a, self._position.b,
                                 self._position.c, self._prime_speed, self._position.e, LayerPolygon.MoveCombingType])
                    gx, gy, gz, ga, gb, gc, gf, ge = new_gcode_position
                    x, y, z, a, b, c, f, e = new_position
                    gcode_position = Position(
                        gx, gy, gz + self._retraction_hop, ga, gb, gc, self._travel_speed, ge)
                    position = Position(
                        x + a * self._retraction_hop, y + b * self._retraction_hop, z + c * self._retraction_hop, a, b, c, self._travel_speed, e)
                    gcode_command = self._generateGCodeCommand(
                        0, gcode_position, self._travel_speed)
                    if gcode_command is not None:
                        gcode_list.append(gcode_command)
                    path.append([position.x, position.y, position.z, position.a, position.b,
                                 position.c, position.f, position.e, LayerPolygon.MoveCombingType])

        feedrate = self._travel_speed
        x, y, z, a, b, c, f, e = new_position
        self._position = Position(x, y, z, a, b, c, feedrate, self._position.e)
        gcode_command = self._generateGCodeCommand(0, new_gcode_position, feedrate)
        if gcode_command is not None:
            gcode_list.append(gcode_command)
        gx, gy, gz, ga, gb, gc, gf, ge = new_gcode_position
        self._gcode_position = Position(gx, gy, gz, ga, gb, gc, feedrate, ge)
        path.append([x, y, z, a, b, c, feedrate, e,
                     LayerPolygon.MoveCombingType])
        
        if is_retraction:
            #we have retraction move
            new_extruder_position = self._position.e[self._extruder_number] + self._retraction_amount
            gcode_list.append("G1 E%.5f F%.0f\n" % (new_extruder_position, (self._prime_speed * 60)))
            self._position.e[self._extruder_number] = new_extruder_position
            self._gcode_position.e[self._extruder_number] = new_extruder_position
            path.append([self._position.x, self._position.y, self._position.z, self._position.a, self._position.b,
                         self._position.c, self._prime_speed, self._position.e, LayerPolygon.MoveRetractionType])
            
        if self._layer_type == LayerPolygon.SupportType:
            gcode_list.append(self._type_keyword + "SUPPORT\n")
        elif self._layer_type == LayerPolygon.SkinType:
            gcode_list.append(self._type_keyword + "SKIN\n")
        elif self._layer_type == LayerPolygon.InfillType:
            gcode_list.append(self._type_keyword + "FILL\n")
        else:
            gcode_list.append(self._type_keyword + "WALL-OUTER\n")

        while idx < len(points):
            point = CliPoint(float(points[idx]), float(points[idx + 1]))
            idx += 2
            new_position, new_gcode_position = self._cliPointToPosition(point, self._position)
            feedrate = self._wall_0_speed
            if self._layer_type == LayerPolygon.SupportType:
                feedrate = self._support_speed
            elif self._layer_type == LayerPolygon.SkinType:
                feedrate = self._skin_speed
            elif self._layer_type == LayerPolygon.InfillType:
                feedrate = self._infill_speed
            x, y, z, a, b, c, f, e = new_position
            self._position = Position(x, y, z, a, b, c, feedrate, e)
            gcode_command = self._generateGCodeCommand(1, new_gcode_position, feedrate)
            if gcode_command is not None:
                gcode_list.append(gcode_command)
            gx, gy, gz, ga, gb, gc, gf, ge = new_gcode_position
            self._gcode_position = Position(gx, gy, gz, ga, gb, gc, feedrate, ge)
            path.append([x,y,z,a,b,c, feedrate, e, self._layer_type])

    def _generateGCodeCommand(self, g: int, gcode_position: Position, feedrate: float) -> Optional[str]:
            gcode_command = "G%s" % g
            if abs(gcode_position.x - self._gcode_position.x) > 0.0001:
                gcode_command += " X%.2f" % gcode_position.x
            if abs(gcode_position.y - self._gcode_position.y) > 0.0001:
                gcode_command += " Y%.2f" % gcode_position.y
            if abs(gcode_position.z - self._gcode_position.z) > 0.0001:
                gcode_command += " Z%.2f" % gcode_position.z
            if abs(gcode_position.a - self._gcode_position.a) > 0.0001:
                gcode_command += " A%.2f" % gcode_position.a
            if abs(gcode_position.b - self._gcode_position.b) > 0.0001:
                gcode_command += " B%.2f" % gcode_position.b
            if abs(gcode_position.c - self._gcode_position.c) > 0.0001:
                gcode_command += " C%.2f" % gcode_position.c
            if abs(feedrate - self._gcode_position.f) > 0.0001:
                gcode_command += " F%.0f" % (feedrate * 60)
            if abs(gcode_position.e[self._extruder_number] - self._gcode_position.e[self._extruder_number]) > 0.0001 and g > 0:
                gcode_command += " E%.5f" % gcode_position.e[self._extruder_number]
            gcode_command += "\n"
            if gcode_command != "G%s\n" % g:
                return gcode_command
            else:
                return None
        
    def _calculateExtrusion(self, current_point: List[float], previous_point: Position) -> float:
        
        Af = (self._filament_diameter / 2) ** 2 * numpy.pi
        Al = self._line_width * self._layer_thickness
        de = numpy.sqrt((current_point[0] - previous_point[0])
                        ** 2 + (current_point[1] - previous_point[1])**2 +
                         (current_point[2] - previous_point[2])**2)
        dVe = Al * de
        return dVe / Af

    def _writeStartCode(self, gcode_list: List[str]):
        gcode_list.append("T0\n")
        init_temperature = self._global_stack.getProperty(
            "material_initial_print_temperature", "value")
        init_bed_temperature = self._global_stack.getProperty(
            "material_bed_temperature_layer_0", "value")
        gcode_list.extend(["M140 S%s\n" % init_bed_temperature,
                           "M105\n",
                           "M190 S%s\n" % init_bed_temperature,
                           "M104 S%s\n" % init_temperature,
                           "M105\n",
                           "M109 S%s\n" % init_temperature,
                           "M82 ;absolute extrusion mode\n"])
        start_gcode = self._global_stack.getProperty(
            "machine_start_gcode", "value")
        gcode_list.append(start_gcode + "\n")

    def _cliPointToPosition(self, point: CliPoint, position: Position, extrusion_move: bool = True) -> (Position, Position):
        x, y, z, i, j, k = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
        if self._parsing_type == "classic":
            x = point.x
            y = point.y
            z = self._current_layer_height
            i = 0
            j = 0
            k = 1
        elif self._parsing_type == "cylindrical":
            x = self._current_layer_height * math.cos(point.y)
            y = self._current_layer_height * math.sin(point.y)
            z = point.x
            length = numpy.sqrt(x**2 + y**2)
            i = x / length if length != 0 else 0
            j = y / length if length != 0 else 0
            k = 0
        new_position = Position(x,y,z,i,j,k,0, [0])
        new_gcode_position = self._transformCoordinates(x,y,z,i,j,k, self._gcode_position)
        new_position.e[self._extruder_number] = position.e[self._extruder_number] + self._calculateExtrusion([x,y,z], position) if extrusion_move else position.e[self._extruder_number]
        new_gcode_position.e[self._extruder_number] = new_position.e[self._extruder_number]
        return new_position, new_gcode_position

    @staticmethod
    def _positionLength(start: Position, end: Position) -> float:
        return numpy.sqrt((start.x - end.x)**2 + (start.y - end.y)**2 + (start.z - end.z)**2)