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