def _createEraserMesh(self, parent: CuraSceneNode, position: Vector): node = CuraSceneNode() node.setName("Eraser") node.setSelectable(True) mesh = MeshBuilder() mesh.addCube(10,10,10) node.setMeshData(mesh.build()) active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate node.addDecorator(BuildPlateDecorator(active_build_plate)) node.addDecorator(SliceableObjectDecorator()) stack = node.callDecoration("getStack") # created by SettingOverrideDecorator that is automatically added to CuraSceneNode settings = stack.getTop() definition = stack.getSettingDefinition("anti_overhang_mesh") new_instance = SettingInstance(definition, settings) new_instance.setProperty("value", True) new_instance.resetState() # Ensure that the state is not seen as a user state. settings.addInstance(new_instance) op = GroupedOperation() # First add node to the scene at the correct position/scale, before parenting, so the eraser mesh does not get scaled with the parent op.addOperation(AddSceneNodeOperation(node, self._controller.getScene().getRoot())) op.addOperation(SetParentOperation(node, parent)) op.push() node.setPosition(position, CuraSceneNode.TransformSpace.World) Application.getInstance().getController().getScene().sceneChanged.emit(node)
def moveToNextLevelPosition(self): output_devices = self._getPrinterOutputDevices() if output_devices: # We found at least one output device output_device = output_devices[0] if self._bed_level_position == 0: output_device.moveHead(0, 0, 3) output_device.homeHead() output_device.moveHead(0, 0, 3) output_device.moveHead(Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") - 10, 0, 0) output_device.moveHead(0, 0, -3) self._bed_level_position += 1 elif self._bed_level_position == 1: output_device.moveHead(0, 0, 3) output_device.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value" ) / 2, Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") - 10, 0) output_device.moveHead(0, 0, -3) self._bed_level_position += 1 elif self._bed_level_position == 2: output_device.moveHead(0, 0, 3) output_device.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") / 2 + 10, -(Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") + 10), 0) output_device.moveHead(0, 0, -3) self._bed_level_position += 1 elif self._bed_level_position >= 3: output_device.sendCommand("M18") # Turn off all motors so the user can move the axes self.setFinished()
def setSettingValue(self, key, value): if key not in self._settings: return self._setting_values[key] = value self.settingValueChanged.emit(self._settings[key]) Application.getInstance().getController().getScene().sceneChanged.emit(self.getNode())
def __init__(self, parent = None): super().__init__(parent = parent) self._additional_component = None self._additional_components_view = None Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView)
def _onGlobalContainerChanged(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: self._multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 # Ensure that all extruder data is reset if not self._multi_extrusion: default_stack_id = global_container_stack.getId() else: default_stack = ExtruderManager.getInstance().getExtruderStack(0) if default_stack: default_stack_id = default_stack.getId() else: default_stack_id = global_container_stack.getId() root_node = Application.getInstance().getController().getScene().getRoot() for node in DepthFirstIterator(root_node): new_stack_id = default_stack_id # Get position of old extruder stack for this node old_extruder_pos = node.callDecoration("getActiveExtruderPosition") if old_extruder_pos is not None: # Fetch current (new) extruder stack at position new_stack = ExtruderManager.getInstance().getExtruderStack(old_extruder_pos) if new_stack: new_stack_id = new_stack.getId() node.callDecoration("setActiveExtruder", new_stack_id) self._updateEnabled()
def __init__(self) -> None: super().__init__() self._zero_conf = None # type: Optional[Zeroconf] self._browser = None # type: Optional[ServiceBrowser] self._instances = {} # type: Dict[str, OctoPrintOutputDevice.OctoPrintOutputDevice] # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. self.addInstanceSignal.connect(self.addInstance) self.removeInstanceSignal.connect(self.removeInstance) Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections) # Load custom instances from preferences self._preferences = Application.getInstance().getPreferences() self._preferences.addPreference("octoprint/manual_instances", "{}") self._preferences.addPreference("octoprint/use_zeroconf", True) try: self._manual_instances = json.loads(self._preferences.getValue("octoprint/manual_instances")) except ValueError: self._manual_instances = {} # type: Dict[str, Any] if not isinstance(self._manual_instances, dict): self._manual_instances = {} # type: Dict[str, Any] self._name_regex = re.compile("OctoPrint instance (\".*\"\.|on )(.*)\.") self._keep_alive_timer = QTimer() self._keep_alive_timer.setInterval(2000) self._keep_alive_timer.setSingleShot(True) self._keep_alive_timer.timeout.connect(self._keepDiscoveryAlive)
def __init__(self, node, hull, thickness, parent = None): super().__init__(parent) self.setCalculateBoundingBox(False) self._original_parent = parent # Color of the drawn convex hull if Application.getInstance().hasGui(): self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb()) else: self._color = Color(0, 0, 0) # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting. self._mesh_height = 0.1 self._thickness = thickness # The node this mesh is "watching" self._node = node self._convex_hull_head_mesh = None self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged) self._onNodeDecoratorsChanged(self._node) self._hull = hull if self._hull: hull_mesh_builder = MeshBuilder() if hull_mesh_builder.addConvexPolygonExtrusion( self._hull.getPoints()[::-1], # bottom layer is reversed self._mesh_height-thickness, self._mesh_height, color=self._color): hull_mesh = hull_mesh_builder.build() self.setMeshData(hull_mesh)
def activeExtruderStackId(self) -> Optional[str]: if not Application.getInstance().getGlobalContainerStack(): return None # No active machine, so no active extruder. try: return self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][str(self._active_extruder_index)].getId() except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong. return None
def extruderCount(self): if not Application.getInstance().getGlobalContainerStack(): return 0 # No active machine, so no extruders. try: return len(self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]) except KeyError: return 0
def __init__(self, parent = None, *args, **kwargs): super().__init__(parent = parent, *args, **kwargs) Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged) self._onPreferencesChanged("general/visible_settings") self.visibilityChanged.connect(self._onVisibilityChanged)
def addMachine(self, name, definition_id): container_registry = UM.Settings.ContainerRegistry.getInstance() definitions = container_registry.findDefinitionContainers(id = definition_id) if definitions: definition = definitions[0] name = self._createUniqueName("machine", "", name, definition.getName()) new_global_stack = UM.Settings.ContainerStack(name) new_global_stack.addMetaDataEntry("type", "machine") container_registry.addContainer(new_global_stack) variant_instance_container = self._updateVariantContainer(definition) material_instance_container = self._updateMaterialContainer(definition, variant_instance_container) quality_instance_container = self._updateQualityContainer(definition, variant_instance_container, material_instance_container) current_settings_instance_container = UM.Settings.InstanceContainer(name + "_current_settings") current_settings_instance_container.addMetaDataEntry("machine", name) current_settings_instance_container.addMetaDataEntry("type", "user") current_settings_instance_container.setDefinition(definitions[0]) container_registry.addContainer(current_settings_instance_container) new_global_stack.addContainer(definition) if variant_instance_container: new_global_stack.addContainer(variant_instance_container) if material_instance_container: new_global_stack.addContainer(material_instance_container) if quality_instance_container: new_global_stack.addContainer(quality_instance_container) new_global_stack.addContainer(self._empty_quality_changes_container) new_global_stack.addContainer(current_settings_instance_container) ExtruderManager.getInstance().addMachineExtruders(definition, new_global_stack.getId()) Application.getInstance().setGlobalContainerStack(new_global_stack)
def __init__(self, width, height): super().__init__("selection", width, height, -999) self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "selection.shader")) self._tool_handle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "default.shader")) self._gl = OpenGL.getInstance().getBindingsObject() self._scene = Application.getInstance().getController().getScene() self._renderer = Application.getInstance().getRenderer() self._selection_map = {} self._toolhandle_selection_map = { self._dropAlpha(ToolHandle.DisabledSelectionColor): ToolHandle.NoAxis, self._dropAlpha(ToolHandle.XAxisSelectionColor): ToolHandle.XAxis, self._dropAlpha(ToolHandle.YAxisSelectionColor): ToolHandle.YAxis, self._dropAlpha(ToolHandle.ZAxisSelectionColor): ToolHandle.ZAxis, self._dropAlpha(ToolHandle.AllAxisSelectionColor): ToolHandle.AllAxis, ToolHandle.DisabledSelectionColor: ToolHandle.NoAxis, ToolHandle.XAxisSelectionColor: ToolHandle.XAxis, ToolHandle.YAxisSelectionColor: ToolHandle.YAxis, ToolHandle.ZAxisSelectionColor: ToolHandle.ZAxis, ToolHandle.AllAxisSelectionColor: ToolHandle.AllAxis } self._output = None
def beginRendering(self): scene = self.getController().getScene() renderer = self.getRenderer() if not self._enabled_shader: self._enabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader")) if not self._disabled_shader: self._disabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader")) self._disabled_shader.setUniformValue("u_diffuseColor1", [0.48, 0.48, 0.48, 1.0]) self._disabled_shader.setUniformValue("u_diffuseColor2", [0.68, 0.68, 0.68, 1.0]) self._disabled_shader.setUniformValue("u_width", 50.0) if Application.getInstance().getGlobalContainerStack(): if Preferences.getInstance().getValue("view/show_overhang"): angle = Application.getInstance().getGlobalContainerStack().getProperty("support_angle", "value") if angle is not None: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - angle))) else: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang. else: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) for node in DepthFirstIterator(scene.getRoot()): if not node.render(renderer): if node.getMeshData() and node.isVisible(): # TODO: Find a better way to handle this #if node.getBoundingBoxMesh(): # renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(),mode = Renderer.RenderLines) uniforms = {} if self._extruders_model.rowCount() > 0: # Get color to render this mesh in from ExtrudersModel extruder_index = 0 extruder_id = node.callDecoration("getActiveExtruder") if extruder_id: extruder_index = max(0, self._extruders_model.find("id", extruder_id)) extruder_color = self._extruders_model.getItem(extruder_index)["colour"] try: # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0]) uniforms["diffuse_color"] = [ int(extruder_color[1:3], 16) / 255, int(extruder_color[3:5], 16) / 255, int(extruder_color[5:7], 16) / 255, 1.0 ] except ValueError: pass if hasattr(node, "_outside_buildarea"): if node._outside_buildarea: renderer.queueNode(node, shader = self._disabled_shader) else: renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms) else: renderer.queueNode(node, material = self._enabled_shader, uniforms = uniforms) if node.callDecoration("isGroup"): renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = Renderer.RenderLines)
def __init__(self, message): super().__init__() self._message = message self._scene = Application.getInstance().getController().getScene() self._progress = None Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
def setSelected(self, key): for index in range(0,len(self.items)): if self.items[index]["key"] == key: for node in Application.getInstance().getController().getScene().getRoot().getAllChildren(): if id(node) == key: if node not in Selection.getAllSelectedObjects(): #node already selected Selection.add(node) if self.items[index]["depth"] == 1: #Its a group node for child_node in node.getChildren(): if child_node not in Selection.getAllSelectedObjects(): #Set all children to parent state (if they arent already) Selection.add(child_node) else: Selection.remove(node) if self.items[index]["depth"] == 1: #Its a group for child_node in node.getChildren(): if child_node in Selection.getAllSelectedObjects(): Selection.remove(child_node) all_children_selected = True #Check all group nodes to see if all their children are selected (if so, they also need to be selected!) for index in range(0,len(self.items)): if self.items[index]["depth"] == 1: for node in Application.getInstance().getController().getScene().getRoot().getAllChildren(): if node.hasChildren(): if id(node) == self.items[index]["key"] and id(node) != key: for index, child_node in enumerate(node.getChildren()): if not Selection.isSelected(child_node): all_children_selected = False #At least one of its children is not selected, dont change state break if all_children_selected: Selection.add(node) else: Selection.remove(node) #Force update self.updateList(Application.getInstance().getController().getScene().getRoot())
def updateHasMaterialsMetadata(self): # Updates the has_materials metadata flag after switching gcode flavor if not self._global_container_stack: return definition = self._global_container_stack.getBottom() if definition.getProperty("machine_gcode_flavor", "value") != "UltiGCode" or definition.getMetaDataEntry("has_materials", False): # In other words: only continue for the UM2 (extended), but not for the UM2+ return has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode" material_container = self._global_container_stack.material if has_materials: if "has_materials" in self._global_container_stack.getMetaData(): self._global_container_stack.setMetaDataEntry("has_materials", True) else: self._global_container_stack.addMetaDataEntry("has_materials", True) # Set the material container to a sane default if material_container == self._empty_container: search_criteria = { "type": "material", "definition": "fdmprinter", "id": self._global_container_stack.getMetaDataEntry("preferred_material")} materials = self._container_registry.findInstanceContainers(**search_criteria) if materials: self._global_container_stack.material = materials[0] else: # The metadata entry is stored in an ini, and ini files are parsed as strings only. # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. if "has_materials" in self._global_container_stack.getMetaData(): self._global_container_stack.removeMetaDataEntry("has_materials") self._global_container_stack.material = ContainerRegistry.getInstance().getEmptyInstanceContainer() Application.getInstance().globalContainerStackChanged.emit()
def _onStartSliceCompleted(self, job): # Note that cancelled slice jobs can still call this method. if self._start_slice_job is job: self._start_slice_job = None if job.isCancelled() or job.getError() or job.getResult() == StartSliceJob.StartJobResult.Error: return if job.getResult() == StartSliceJob.StartJobResult.SettingError: if Application.getInstance().getPlatformActivity: self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors."), lifetime = 10) self._error_message.show() self.backendStateChange.emit(BackendState.Error) else: self.backendStateChange.emit(BackendState.NotStarted) return if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice: if Application.getInstance().getPlatformActivity: self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. No suitable objects found."), lifetime = 10) self._error_message.show() self.backendStateChange.emit(BackendState.Error) else: self.backendStateChange.emit(BackendState.NotStarted) return # Preparation completed, send it to the backend. self._socket.sendMessage(job.getSliceMessage())
def renameQualityContainer(self, container_id, new_name): containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id, type = "quality") if containers: new_name = self._createUniqueName("quality", containers[0].getName(), new_name, catalog.i18nc("@label", "Custom profile")) if containers[0].getName() == new_name: # Nothing to do. return # As we also want the id of the container to be changed (so that profile name is the name of the file # on disk. We need to create a new instance and remove it (so the old file of the container is removed) # If we don't do that, we might get duplicates & other weird issues. new_container = InstanceContainer("") new_container.deserialize(containers[0].serialize()) # Actually set the name new_container.setName(new_name) new_container._id = new_name # Todo: Fix proper id change function for this. # Add the "new" container. UM.Settings.ContainerRegistry.getInstance().addContainer(new_container) # Ensure that the renamed profile is saved -before- we remove the old profile. Application.getInstance().saveSettings() # Actually set & remove new / old quality. self.setActiveQuality(new_name) self.removeQualityContainer(containers[0].getId())
def __init__(self, parent = None): super().__init__(parent) self._width = 0 self._height = 0 self._depth = 0 self._shader = None self._grid_mesh = None self._grid_shader = None self._disallowed_areas = [] self._disallowed_area_mesh = None self._prime_tower_area = None self._prime_tower_area_mesh = None self.setCalculateBoundingBox(False) self._volume_aabb = None self._raft_thickness = 0.0 self._adhesion_type = None self._platform = Platform(self) self._global_container_stack = None Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) self._onGlobalContainerStackChanged() self._active_extruder_stack = None ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged) self._onActiveExtruderStackChanged() self._has_errors = False
def __init__(self, **kwargs): plugin_path = "" if sys.platform == "win32": if hasattr(sys, "frozen"): plugin_path = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "PyQt5", "plugins") Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) else: import site for dir in site.getsitepackages(): QCoreApplication.addLibraryPath(os.path.join(dir, "PyQt5", "plugins")) elif sys.platform == "darwin": plugin_path = os.path.join(Application.getInstallPrefix(), "Resources", "plugins") if plugin_path: Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) os.environ["QSG_RENDER_LOOP"] = "basic" super().__init__(sys.argv, **kwargs) self._plugins_loaded = False #Used to determine when it's safe to use the plug-ins. self._main_qml = "main.qml" self._engine = None self._renderer = None self._main_window = None self._shutting_down = False self._qml_import_paths = [] self._qml_import_paths.append(os.path.join(os.path.dirname(sys.executable), "qml")) self._qml_import_paths.append(os.path.join(Application.getInstallPrefix(), "Resources", "qml")) self.setAttribute(Qt.AA_UseDesktopOpenGL) try: self._splash = self._createSplashScreen() except FileNotFoundError: self._splash = None else: self._splash.show() self.processEvents() signal.signal(signal.SIGINT, signal.SIG_DFL) # This is done here as a lot of plugins require a correct gl context. If you want to change the framework, # these checks need to be done in your <framework>Application.py class __init__(). i18n_catalog = i18nCatalog("uranium") self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading plugins...")) self._loadPlugins() self.parseCommandLine() Logger.log("i", "Command line arguments: %s", self._parsed_command_line) self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins()) self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading preferences...")) try: file = Resources.getPath(Resources.Preferences, self.getApplicationName() + ".cfg") Preferences.getInstance().readFromFile(file) except FileNotFoundError: pass
def addMachine(self, name, definition_id): definitions = UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id = definition_id) if definitions: definition = definitions[0] name = self._createUniqueName("machine", "", name, definition.getName()) new_global_stack = UM.Settings.ContainerStack(name) new_global_stack.addMetaDataEntry("type", "machine") UM.Settings.ContainerRegistry.getInstance().addContainer(new_global_stack) variant_instance_container = self._updateVariantContainer(definition) material_instance_container = self._updateMaterialContainer(definition, variant_instance_container) quality_instance_container = self._updateQualityContainer(definition, material_instance_container) current_settings_instance_container = UM.Settings.InstanceContainer(name + "_current_settings") current_settings_instance_container.addMetaDataEntry("machine", name) current_settings_instance_container.addMetaDataEntry("type", "user") current_settings_instance_container.setDefinition(definitions[0]) UM.Settings.ContainerRegistry.getInstance().addContainer(current_settings_instance_container) # If a definition is found, its a list. Should only have one item. new_global_stack.addContainer(definition) if variant_instance_container: new_global_stack.addContainer(variant_instance_container) if material_instance_container: new_global_stack.addContainer(material_instance_container) if quality_instance_container: new_global_stack.addContainer(quality_instance_container) new_global_stack.addContainer(current_settings_instance_container) ExtruderManager.ExtruderManager.getInstance().addMachineExtruders(definition) Application.getInstance().setGlobalContainerStack(new_global_stack)
def setHasVariants(self, has_variants = True): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: variant_container = global_container_stack.extruders["0"].variant if has_variants: global_container_stack.setMetaDataEntry("has_variants", True) # Set the variant container to a sane default empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() if type(variant_container) == type(empty_container): search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" } containers = self._container_registry.findInstanceContainers(**search_criteria) if containers: global_container_stack.extruders["0"].variant = containers[0] else: # The metadata entry is stored in an ini, and ini files are parsed as strings only. # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. if "has_variants" in global_container_stack.getMetaData(): global_container_stack.removeMetaDataEntry("has_variants") # Set the variant container to an empty variant global_container_stack.extruders["0"].variant = ContainerRegistry.getInstance().getEmptyInstanceContainer() Application.getInstance().globalContainerStackChanged.emit() self._reset()
def createChangelogWindow(self): path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.qml")) component = QQmlComponent(Application.getInstance()._engine, path) self._changelog_context = QQmlContext(Application.getInstance()._engine.rootContext()) self._changelog_context.setContextProperty("manager", self) self._changelog_window = component.create(self._changelog_context)
def setActiveQuality(self, quality_id): containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = quality_id) if not containers or not self._active_container_stack: return old_quality = self._active_container_stack.findContainer({"type": "quality"}) if old_quality and old_quality != containers[0]: old_quality.nameChanged.disconnect(self._onQualityNameChanged) quality_index = self._active_container_stack.getContainerIndex(old_quality) self._active_container_stack.replaceContainer(quality_index, containers[0]) containers[0].nameChanged.connect(self._onQualityNameChanged) if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: # Ask the user if the user profile should be cleared or not (discarding the current settings) # In Simple Mode we assume the user always wants to keep the (limited) current settings details = catalog.i18nc("@label", "You made changes to the following setting(s):") user_settings = self._active_container_stack.getTop().findInstances(**{}) for setting in user_settings: details = details + "\n " + setting.definition.label Application.getInstance().messageBox(catalog.i18nc("@window:title", "Switched profiles"), catalog.i18nc("@label", "Do you want to transfer your changed settings to this profile?"), catalog.i18nc("@label", "If you transfer your settings they will override settings in the profile."), details, buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._keepUserSettingsDialogCallback) else: Logger.log("w", "While trying to set the active quality, no quality was found to replace.")
def _onHotendIdChanged(self, index, hotend_id): if not self._global_container_stack: return containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = self._global_container_stack.getBottom().getId(), name = hotend_id) if containers: extruder_manager = ExtruderManager.getInstance() old_index = extruder_manager.activeExtruderIndex if old_index != index: extruder_manager.setActiveExtruderIndex(index) else: old_index = None if self.activeVariantId != containers[0].getId(): if time.time() - self._auto_change_material_hotend_flood_time > self._auto_change_material_hotend_flood_window: Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"), catalog.i18nc("@label", "Do you want to change the hotend to match the hotend in your printer?"), catalog.i18nc("@label", "The hotend on your printer was changed. For best results always slice for the hotend that is inserted in your printer."), buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._hotendChangedDialogCallback, callback_arguments = [index, containers[0].getId()]) else: self._hotendChangedDialogCallback(self._auto_change_material_hotend_flood_last_choice, index, containers[0].getId()) if old_index is not None: extruder_manager.setActiveExtruderIndex(old_index) else: Logger.log("w", "No variant found for printer definition %s with id %s" % (self._global_container_stack.getBottom().getId(), hotend_id))
def slice(self): self._slice_start_time = time() if not self._need_slicing: self.processingProgress.emit(1.0) self.backendStateChange.emit(BackendState.Done) Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.") return if Application.getInstance().getPrintInformation(): Application.getInstance().getPrintInformation().setToZeroPrintInformation() self._stored_layer_data = [] self._stored_optimized_layer_data = [] if self._process is None: self._createSocket() self.stopSlicing() self._engine_is_fresh = False # Yes we're going to use the engine self.processingProgress.emit(0.0) self.backendStateChange.emit(BackendState.NotStarted) self._scene.gcode_list = [] self._slicing = True self.slicingStarted.emit() slice_message = self._socket.createMessage("cura.proto.Slice") self._start_slice_job = StartSliceJob.StartSliceJob(slice_message) self._start_slice_job.start() self._start_slice_job.finished.connect(self._onStartSliceCompleted)
def _onMaterialIdChanged(self, index, material_id): if not self._global_container_stack: return definition_id = "fdmprinter" if self._global_container_stack.getMetaDataEntry("has_machine_materials", False): definition_id = self._global_container_stack.getBottom().getId() containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "material", definition = definition_id, GUID = material_id) if containers: extruder_manager = ExtruderManager.getInstance() old_index = extruder_manager.activeExtruderIndex if old_index != index: extruder_manager.setActiveExtruderIndex(index) else: old_index = None if self.activeMaterialId != containers[0].getId(): if time.time() - self._auto_change_material_hotend_flood_time > self._auto_change_material_hotend_flood_window: Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"), catalog.i18nc("@label", "Do you want to change the material to match the material in your printer?"), catalog.i18nc("@label", "The material on your printer was changed. For best results always slice for the material that is inserted in your printer."), buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._materialIdChangedDialogCallback, callback_arguments = [index, containers[0].getId()]) else: self._materialIdChangedDialogCallback(self._auto_change_material_hotend_flood_last_choice, index, containers[0].getId()) if old_index is not None: extruder_manager.setActiveExtruderIndex(old_index) else: Logger.log("w", "No material definition found for printer definition %s and GUID %s" % (definition_id, material_id))
def __init__(self): super().__init__() self._zero_conf = None self._browser = None self._printers = {} self._api_version = "1" self._api_prefix = "/api/v" + self._api_version + "/" self._network_manager = QNetworkAccessManager() self._network_manager.finished.connect(self._onNetworkRequestFinished) # List of old printer names. This is used to ensure that a refresh of zeroconf does not needlessly forces # authentication requests. self._old_printers = [] # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. self.addPrinterSignal.connect(self.addPrinter) self.removePrinterSignal.connect(self.removePrinter) Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections) # Get list of manual printers from preferences self._preferences = Preferences.getInstance() self._preferences.addPreference("um3networkprinting/manual_instances", "") # A comma-separated list of ip adresses or hostnames self._manual_instances = self._preferences.getValue("um3networkprinting/manual_instances").split(",")
def _createViewFromQML(self): path = QUrl.fromLocalFile( os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url)) self._component = QQmlComponent(Application.getInstance()._engine, path) self._context = QQmlContext(Application.getInstance()._engine.rootContext()) self._context.setContextProperty("manager", self) self._view = self._component.create(self._context)
def _onPrintDurationMessage(self, total_time, material_amounts): self._current_print_time.setDuration(total_time) self.currentPrintTimeChanged.emit() # Material amount is sent as an amount of mm^3, so calculate length from that r = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") / 2 self._material_lengths = [] self._material_weights = [] extruder_stacks = list( cura.Settings.ExtruderManager.getInstance().getMachineExtruders( Application.getInstance().getGlobalContainerStack().getId() ) ) for index, amount in enumerate(material_amounts): ## Find the right extruder stack. As the list isn't sorted because it's a annoying generator, we do some # list comprehension filtering to solve this for us. if extruder_stacks: # Multi extrusion machine extruder_stack = [ extruder for extruder in extruder_stacks if extruder.getMetaDataEntry("position") == str(index) ][0] density = extruder_stack.getMetaDataEntry("properties", {}).get("density", 0) else: # Machine with no extruder stacks density = ( Application.getInstance() .getGlobalContainerStack() .getMetaDataEntry("properties", {}) .get("density", 0) ) self._material_weights.append(float(amount) * float(density) / 1000) self._material_lengths.append(round((amount / (math.pi * r ** 2)) / 1000, 2)) self.materialLengthsChanged.emit() self.materialWeightsChanged.emit()
def processGCodeFile(self, file_name): Logger.log("d", "Preparing to load %s" % file_name) self._cancelled = False # We obtain the filament diameter from the selected printer to calculate line widths self._filament_diameter = Application.getInstance( ).getGlobalContainerStack().getProperty("material_diameter", "value") scene_node = CuraSceneNode() # Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no # real data to calculate it from. scene_node.getBoundingBox = self._getNullBoundingBox gcode_list = [] self._is_layers_in_file = False Logger.log("d", "Opening file %s" % file_name) self._extruder_offsets = self._extruderOffsets( ) # dict with index the extruder number. can be empty with open(file_name, "r") as file: file_lines = 0 current_line = 0 for line in file: file_lines += 1 gcode_list.append(line) if not self._is_layers_in_file and line[:len( self._layer_keyword)] == self._layer_keyword: self._is_layers_in_file = True file.seek(0) file_step = max(math.floor(file_lines / 100), 1) self._clearValues() self._message = Message(catalog.i18nc("@info:status", "Parsing G-code"), lifetime=0, title=catalog.i18nc( "@info:title", "G-code Details")) self._message.setProgress(0) self._message.show() Logger.log("d", "Parsing %s..." % file_name) current_position = self._position(0, 0, 0, 0, [0]) current_path = [] min_layer_number = 0 negative_layers = 0 previous_layer = 0 for line in file: if self._cancelled: Logger.log("d", "Parsing %s cancelled" % file_name) 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.find(self._type_keyword) == 0: type = line[len(self._type_keyword):].strip() if type == "WALL-INNER": self._layer_type = LayerPolygon.InsetXType elif type == "WALL-OUTER": self._layer_type = LayerPolygon.Inset0Type elif type == "SKIN": self._layer_type = LayerPolygon.SkinType elif type == "SKIRT": self._layer_type = LayerPolygon.SkirtType elif type == "SUPPORT": self._layer_type = LayerPolygon.SupportType elif type == "FILL": self._layer_type = LayerPolygon.InfillType else: Logger.log( "w", "Encountered a unknown type (%s) while parsing g-code.", type) # When the layer change is reached, the polygon is computed so we have just one layer per layer per extruder if self._is_layers_in_file and line[:len( self._layer_keyword)] == self._layer_keyword: try: layer_number = int(line[len(self._layer_keyword):]) self._createPolygon( self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) current_path.clear() # When using a raft, the raft layers are stored as layers < 0, it mimics the same behavior # as in ProcessSlicedLayersJob if layer_number < min_layer_number: min_layer_number = layer_number if layer_number < 0: layer_number += abs(min_layer_number) negative_layers += 1 else: layer_number += negative_layers # In case there is a gap in the layer count, empty layers are created for empty_layer in range(previous_layer + 1, layer_number): self._createEmptyLayer(empty_layer) self._layer_number = layer_number previous_layer = layer_number except: pass # This line is a comment. Ignore it (except for the layer_keyword) if line.startswith(";"): continue G = self._getInt(line, "G") if G is not None: # When find a movement, the new posistion is calculated and added to the current_path, but # don't need to create a polygon until the end of the layer current_position = self.processGCode( G, line, current_position, current_path) continue # When changing the extruder, the polygon with the stored paths is computed if line.startswith("T"): T = self._getInt(line, "T") if T is not None: self._createPolygon( self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) current_path.clear() current_position = self.processTCode( T, line, current_position, current_path) if line.startswith("M"): M = self._getInt(line, "M") self.processMCode(M, line, current_position, current_path) # "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() 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 = Application.getInstance().getBuildPlateModel( ).activeBuildPlate gcode_dict = {active_build_plate_id: gcode_list} Application.getInstance().getController().getScene( ).gcode_dict = gcode_dict Logger.log("d", "Finished parsing %s" % file_name) self._message.hide() if self._layer_number == 0: Logger.log("w", "File %s doesn't contain any valid layers" % file_name) settings = Application.getInstance().getGlobalContainerStack() machine_width = settings.getProperty("machine_width", "value") machine_depth = settings.getProperty("machine_depth", "value") if not self._center_is_zero: scene_node.setPosition( Vector(-machine_width / 2, 0, machine_depth / 2)) Logger.log("d", "Loaded %s" % file_name) if Preferences.getInstance().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() # The "save/print" button's state is bound to the backend state. backend = Application.getInstance().getBackend() backend.backendStateChange.emit(Backend.BackendState.Disabled) return scene_node
def write(self, stream, nodes, mode=WorkspaceWriter.OutputMode.BinaryMode): application = Application.getInstance() machine_manager = application.getMachineManager() mesh_writer = application.getMeshFileHandler().getWriter("3MFWriter") if not mesh_writer: # We need to have the 3mf mesh writer, otherwise we can't save the entire workspace self.setInformation( catalog.i18nc("@error:zip", "3MF Writer plug-in is corrupt.")) Logger.error( "3MF Writer class is unavailable. Can't write workspace.") return False global_stack = machine_manager.activeMachine if global_stack is None: self.setInformation( catalog.i18nc( "@error", "There is no workspace yet to write. Please add a printer first." )) Logger.error( "Tried to write a 3MF workspace before there was a global stack." ) return False # Indicate that the 3mf mesh writer should not close the archive just yet (we still need to add stuff to it). mesh_writer.setStoreArchive(True) mesh_writer.write(stream, nodes, mode) archive = mesh_writer.getArchive() if archive is None: # This happens if there was no mesh data to write. archive = zipfile.ZipFile(stream, "w", compression=zipfile.ZIP_DEFLATED) try: # Add global container stack data to the archive. self._writeContainerToArchive(global_stack, archive) # Also write all containers in the stack to the file for container in global_stack.getContainers(): self._writeContainerToArchive(container, archive) # Check if the machine has extruders and save all that data as well. for extruder_stack in global_stack.extruderList: self._writeContainerToArchive(extruder_stack, archive) for container in extruder_stack.getContainers(): self._writeContainerToArchive(container, archive) except PermissionError: self.setInformation( catalog.i18nc("@error:zip", "No permission to write the workspace here.")) Logger.error("No permission to write workspace to this stream.") return False # Write preferences to archive original_preferences = Application.getInstance().getPreferences( ) #Copy only the preferences that we use to the workspace. temp_preferences = Preferences() for preference in { "general/visible_settings", "cura/active_mode", "cura/categories_expanded", "metadata/setting_version" }: temp_preferences.addPreference(preference, None) temp_preferences.setValue( preference, original_preferences.getValue(preference)) preferences_string = StringIO() temp_preferences.writeToFile(preferences_string) preferences_file = zipfile.ZipInfo("Cura/preferences.cfg") try: archive.writestr(preferences_file, preferences_string.getvalue()) # Save Cura version version_file = zipfile.ZipInfo("Cura/version.ini") version_config_parser = configparser.ConfigParser( interpolation=None) version_config_parser.add_section("versions") version_config_parser.set("versions", "cura_version", application.getVersion()) version_config_parser.set("versions", "build_type", application.getBuildType()) version_config_parser.set("versions", "is_debug_mode", str(application.getIsDebugMode())) version_file_string = StringIO() version_config_parser.write(version_file_string) archive.writestr(version_file, version_file_string.getvalue()) self._writePluginMetadataToArchive(archive) # Close the archive & reset states. archive.close() except PermissionError: self.setInformation( catalog.i18nc("@error:zip", "No permission to write the workspace here.")) Logger.error("No permission to write workspace to this stream.") return False except EnvironmentError as e: self.setInformation( catalog.i18nc( "@error:zip", "The operating system does not allow saving a project file to this location or with this file name." )) Logger.error( "EnvironmentError when writing workspace to this stream: {err}" .format(err=str(e))) return False mesh_writer.setStoreArchive(False) return True
def run(self): if Application.getInstance().getController().getActiveView( ).getPluginId() == "LayerView": self._progress = Message( catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1) self._progress.show() Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return Application.getInstance().getController().activeViewChanged.connect( self._onActiveViewChanged) object_id_map = {} new_node = SceneNode() ## Remove old layer data (if any) for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is SceneNode and node.getMeshData(): if node.callDecoration("getLayerData"): self._scene.getRoot().removeChild(node) Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return settings = Application.getInstance().getMachineManager( ).getWorkingProfile() mesh = MeshData() layer_data = LayerData.LayerData() layer_count = len(self._layers) current_layer = 0 for layer in self._layers: layer_data.addLayer(layer.id) layer_data.setLayerHeight(layer.id, layer.height) layer_data.setLayerThickness(layer.id, layer.thickness) for p in range(layer.repeatedMessageCount("polygons")): polygon = layer.getRepeatedMessage("polygons", p) points = numpy.fromstring( polygon.points, dtype="i8") # Convert bytearray to numpy array points = points.reshape( (-1, 2) ) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. # Create a new 3D-array, copy the 2D points over and insert the right height. # This uses manual array creation + copy rather than numpy.insert since this is # faster. new_points = numpy.empty((len(points), 3), numpy.float32) new_points[:, 0] = points[:, 0] new_points[:, 1] = layer.height new_points[:, 2] = -points[:, 1] new_points /= 1000 layer_data.addPolygon(layer.id, polygon.type, new_points, polygon.line_width) Job.yieldThread() Job.yieldThread() current_layer += 1 progress = (current_layer / layer_count) * 99 # TODO: Rebuild the layer data mesh once the layer has been processed. # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. if self._abort_requested: if self._progress: self._progress.hide() return if self._progress: self._progress.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data layer_data.build() if self._abort_requested: if self._progress: self._progress.hide() return #Add layerdata decorator to scene node to indicate that the node has layerdata decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_data) new_node.addDecorator(decorator) new_node.setMeshData(mesh) new_node.setParent( self._scene.getRoot()) #Note: After this we can no longer abort! if not settings.getSettingValue("machine_center_is_zero"): new_node.setPosition( Vector(-settings.getSettingValue("machine_width") / 2, 0.0, settings.getSettingValue("machine_depth") / 2)) if self._progress: self._progress.setProgress(100) view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": view.resetLayerData() if self._progress: self._progress.hide()
def __init__(self, layers): super().__init__() self._layers = layers self._scene = Application.getInstance().getController().getScene() self._progress = None self._abort_requested = False
def __init__(self): super().__init__() self._i18n_catalog = None self._application = Application.getInstance() self._category_key = "klipper_settings" self._category_dict = { "label": "Klipper Settings", "description": "Klipper firmware specific settings.", "type": "category", "icon": "category_machine" } self._setting_k_g_offset_key = "material_klipper_gcode_offset" self._setting_k_g_offset_dict = { "label": "K - GCODE Offset", "description": "material_klipper_gcode_offset : Sets the GCODE Offset for Klipper. Note that unless this setting is used in a start gcode snippet, it has no effect!", "type": "float", "unit": "mm", "default_value": 0, "minimum_value": "-layer_height_0", "maximum_value_warning": "layer_height_0", "settable_per_mesh": False, "settable_per_extruder": True, "settable_per_meshgroup": False } self._setting_k_pa_key = "material_klipper_pa" self._setting_k_pa_dict = { "label": "K - Pressure Advance", "description": "material_klipper_pa : 0.0 disable. Sets the Pressure Advance for Klipper. Note that unless this setting is used in a start gcode snippet, it has no effect!", "type": "float", "default_value": 0.0, "minimum_value": 0.0, "settable_per_mesh": False, "settable_per_extruder": True, "settable_per_meshgroup": False } self._setting_k_pa_l_key = "material_klipper_pa_smooth_time" self._setting_k_pa_l_dict = { "label": "K - PA Smooth Time", "description": "material_klipper_pa_smooth_time : 0.04. Sets the Pressure Advance smooth time for Klipper. Note that unless this setting is used in a start gcode snippet, it has no effect!", "type": "float", "default_value": 0.040, "settable_per_mesh": False, "settable_per_extruder": True, "settable_per_meshgroup": False } self._application.engineCreatedSignal.connect( self._fixSettingVisibility) self._application.getPreferences().preferenceChanged.connect( self._onPreferencesChanged) ContainerRegistry.getInstance().containerLoadComplete.connect( self._onContainerLoadComplete) # running through the generate GCODE if needed self._application.getOutputDeviceManager().writeStarted.connect( self._filterGcode)
def openBugReportPage(self): event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {}) Application.getInstance().functionEvent(event)
def openDocumentation(self): # Starting a web browser from a signal handler connected to a menu will crash on windows. # So instead, defer the call to the next run of the event loop, since that does work. # Note that weirdly enough, only signal handlers that open a web browser fail like that. event = CallFunctionEvent(self._openUrl, [QUrl("http://ultimaker.com/en/support/software")], {}) Application.getInstance().functionEvent(event)
def startPrint(self): self.writeStarted.emit(self) gcode_list = getattr( Application.getInstance().getController().getScene(), "gcode_list") self._updateJobState("printing") self.printGCode(gcode_list)
def __init__(self, filename): super().__init__() self._filename = filename self._handler = Application.getInstance().getMeshFileHandler()
def render(self): if not self._layer_shader: if self._compatibility_mode: shader_filename = "layers.shader" shadow_shader_filename = "layers_shadow.shader" else: shader_filename = "layers3d.shader" shadow_shader_filename = "layers3d_shadow.shader" self._layer_shader = OpenGL.getInstance().createShaderProgram( os.path.join( PluginRegistry.getInstance().getPluginPath( "SimulationView"), shader_filename)) self._layer_shadow_shader = OpenGL.getInstance( ).createShaderProgram( os.path.join( PluginRegistry.getInstance().getPluginPath( "SimulationView"), shadow_shader_filename)) self._current_shader = self._layer_shader # Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers) self._layer_shader.setUniformValue( "u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex))) if self._layer_view: self._layer_shader.setUniformValue( "u_max_feedrate", self._layer_view.getMaxFeedrate()) self._layer_shader.setUniformValue( "u_min_feedrate", self._layer_view.getMinFeedrate()) self._layer_shader.setUniformValue( "u_max_thickness", self._layer_view.getMaxThickness()) self._layer_shader.setUniformValue( "u_min_thickness", self._layer_view.getMinThickness()) self._layer_shader.setUniformValue( "u_layer_view_type", self._layer_view.getSimulationViewType()) self._layer_shader.setUniformValue( "u_extruder_opacity", self._layer_view.getExtruderOpacities()) self._layer_shader.setUniformValue( "u_show_travel_moves", self._layer_view.getShowTravelMoves()) self._layer_shader.setUniformValue( "u_show_helpers", self._layer_view.getShowHelpers()) self._layer_shader.setUniformValue("u_show_skin", self._layer_view.getShowSkin()) self._layer_shader.setUniformValue( "u_show_infill", self._layer_view.getShowInfill()) else: #defaults self._layer_shader.setUniformValue("u_max_feedrate", 1) self._layer_shader.setUniformValue("u_min_feedrate", 0) self._layer_shader.setUniformValue("u_max_thickness", 1) self._layer_shader.setUniformValue("u_min_thickness", 0) self._layer_shader.setUniformValue("u_layer_view_type", 1) self._layer_shader.setUniformValue( "u_extruder_opacity", [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]) self._layer_shader.setUniformValue("u_show_travel_moves", 0) self._layer_shader.setUniformValue("u_show_helpers", 1) self._layer_shader.setUniformValue("u_show_skin", 1) self._layer_shader.setUniformValue("u_show_infill", 1) if not self._tool_handle_shader: self._tool_handle_shader = OpenGL.getInstance( ).createShaderProgram( Resources.getPath(Resources.Shaders, "toolhandle.shader")) if not self._nozzle_shader: self._nozzle_shader = OpenGL.getInstance().createShaderProgram( Resources.getPath(Resources.Shaders, "color.shader")) self._nozzle_shader.setUniformValue( "u_color", Color(*Application.getInstance().getTheme().getColor( "layerview_nozzle").getRgb())) if not self._disabled_shader: self._disabled_shader = OpenGL.getInstance().createShaderProgram( Resources.getPath(Resources.Shaders, "striped.shader")) self._disabled_shader.setUniformValue( "u_diffuseColor1", Color(*Application.getInstance().getTheme().getColor( "model_unslicable").getRgb())) self._disabled_shader.setUniformValue( "u_diffuseColor2", Color(*Application.getInstance().getTheme().getColor( "model_unslicable_alt").getRgb())) self._disabled_shader.setUniformValue("u_width", 50.0) self._disabled_shader.setUniformValue("u_opacity", 0.6) self.bind() tool_handle_batch = RenderBatch(self._tool_handle_shader, type=RenderBatch.RenderType.Overlay, backface_cull=True) disabled_batch = RenderBatch(self._disabled_shader) head_position = None # Indicates the current position of the print head nozzle_node = None for node in DepthFirstIterator(self._scene.getRoot()): if isinstance(node, ToolHandle): tool_handle_batch.addItem(node.getWorldTransformation(), mesh=node.getSolidMesh()) elif isinstance(node, NozzleNode): nozzle_node = node nozzle_node.setVisible( False) # Don't set to true, we render it separately! elif getattr(node, "_outside_buildarea", False) and isinstance( node, SceneNode) and node.getMeshData() and node.isVisible(): disabled_batch.addItem(node.getWorldTransformation(copy=False), node.getMeshData()) elif isinstance(node, SceneNode) and (node.getMeshData( ) or node.callDecoration("isBlockSlicing")) and node.isVisible(): layer_data = node.callDecoration("getLayerData") if not layer_data: continue # Render all layers below a certain number as line mesh instead of vertices. if self._layer_view._current_layer_num > -1 and ( (not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())): start = 0 end = 0 element_counts = layer_data.getElementCounts() for layer in sorted(element_counts.keys()): # In the current layer, we show just the indicated paths if layer == self._layer_view._current_layer_num: # We look for the position of the head, searching the point of the current path index = self._layer_view._current_path_num offset = 0 for polygon in layer_data.getLayer(layer).polygons: # The size indicates all values in the two-dimension array, and the second dimension is # always size 3 because we have 3D points. if index >= polygon.data.size // 3 - offset: index -= polygon.data.size // 3 - offset offset = 1 # This is to avoid the first point when there is more than one polygon, since has the same value as the last point in the previous polygon continue # The head position is calculated and translated head_position = Vector( polygon.data[index + offset][0], polygon.data[index + offset][1], polygon.data[index + offset] [2]) + node.getWorldPosition() break break if self._layer_view._minimum_layer_num > layer: start += element_counts[layer] end += element_counts[layer] # Calculate the range of paths in the last layer current_layer_start = end current_layer_end = end + self._layer_view._current_path_num * 2 # Because each point is used twice # This uses glDrawRangeElements internally to only draw a certain range of lines. # All the layers but the current selected layer are rendered first if self._old_current_path != self._layer_view._current_path_num: self._current_shader = self._layer_shadow_shader self._switching_layers = False if not self._layer_view.isSimulationRunning( ) and self._old_current_layer != self._layer_view._current_layer_num: self._current_shader = self._layer_shader self._switching_layers = True layers_batch = RenderBatch( self._current_shader, type=RenderBatch.RenderType.Solid, mode=RenderBatch.RenderMode.Lines, range=(start, end), backface_cull=True) layers_batch.addItem(node.getWorldTransformation(), layer_data) layers_batch.render(self._scene.getActiveCamera()) # Current selected layer is rendered current_layer_batch = RenderBatch( self._layer_shader, type=RenderBatch.RenderType.Solid, mode=RenderBatch.RenderMode.Lines, range=(current_layer_start, current_layer_end)) current_layer_batch.addItem(node.getWorldTransformation(), layer_data) current_layer_batch.render(self._scene.getActiveCamera()) self._old_current_layer = self._layer_view._current_layer_num self._old_current_path = self._layer_view._current_path_num # Create a new batch that is not range-limited batch = RenderBatch(self._layer_shader, type=RenderBatch.RenderType.Solid) if self._layer_view.getCurrentLayerMesh(): batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerMesh()) if self._layer_view.getCurrentLayerJumps(): batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerJumps()) if len(batch.items) > 0: batch.render(self._scene.getActiveCamera()) # The nozzle is drawn when once we know the correct position of the head, # but the user is not using the layer slider, and the compatibility mode is not enabled if not self._switching_layers and not self._compatibility_mode and self._layer_view.getActivity( ) and nozzle_node is not None: if head_position is not None: nozzle_node.setPosition(head_position) nozzle_batch = RenderBatch( self._nozzle_shader, type=RenderBatch.RenderType.Transparent) nozzle_batch.addItem(nozzle_node.getWorldTransformation(), mesh=nozzle_node.getMeshData()) nozzle_batch.render(self._scene.getActiveCamera()) if len(disabled_batch.items) > 0: disabled_batch.render(self._scene.getActiveCamera()) # Render toolhandles on top of the layerview if len(tool_handle_batch.items) > 0: tool_handle_batch.render(self._scene.getActiveCamera()) self.release()
def _onStartSliceCompleted(self, job): if self._error_message: self._error_message.hide() # Note that cancelled slice jobs can still call this method. if self._start_slice_job is job: self._start_slice_job = None if job.isCancelled() or job.getError() or job.getResult() == StartSliceJob.StartJobResult.Error: return if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible: if Application.getInstance().getPlatformActivity: self._error_message = Message(catalog.i18nc("@info:status", "The selected material is incompatible with the selected machine or configuration.")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) else: self.backendStateChange.emit(BackendState.NotStarted) return if job.getResult() == StartSliceJob.StartJobResult.SettingError: if Application.getInstance().getPlatformActivity: extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())) error_keys = [] for extruder in extruders: error_keys.extend(extruder.getErrorKeys()) if not extruders: error_keys = self._global_container_stack.getErrorKeys() error_labels = set() definition_container = self._global_container_stack.getBottom() for key in error_keys: error_labels.add(definition_container.findDefinitions(key = key)[0].label) error_labels = ", ".join(error_labels) self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}".format(error_labels))) self._error_message.show() self.backendStateChange.emit(BackendState.Error) else: self.backendStateChange.emit(BackendState.NotStarted) return if job.getResult() == StartSliceJob.StartJobResult.BuildPlateError: if Application.getInstance().getPlatformActivity: self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid.")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) else: self.backendStateChange.emit(BackendState.NotStarted) if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice: if Application.getInstance().getPlatformActivity: self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit.")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) else: self.backendStateChange.emit(BackendState.NotStarted) return # Preparation completed, send it to the backend. self._socket.sendMessage(job.getSliceMessage()) # Notify the user that it's now up to the backend to do it's job self.backendStateChange.emit(BackendState.Processing) Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time )
def run(self): reader = self._handler.getReaderForFile(self._filename) if not reader: result_message = Message(i18n_catalog.i18nc( "@info:status", "Cannot open file type <filename>{0}</filename>", self._filename), lifetime=0) result_message.show() return # Give the plugin a chance to display a dialog before showing the loading UI pre_read_result = reader.preRead(self._filename) if pre_read_result != MeshReader.PreReadResult.accepted: if pre_read_result == MeshReader.PreReadResult.failed: result_message = Message(i18n_catalog.i18nc( "@info:status", "Failed to load <filename>{0}</filename>", self._filename), lifetime=0) result_message.show() return loading_message = Message(i18n_catalog.i18nc( "@info:status", "Loading <filename>{0}</filename>", self._filename), lifetime=0, dismissable=False) loading_message.setProgress(-1) loading_message.show() Job.yieldThread( ) # Yield to any other thread that might want to do something else. node = None try: begin_time = time.time() node = self._handler.readerRead(reader, self._filename) end_time = time.time() Logger.log("d", "Loading mesh took %s seconds", end_time - begin_time) except: Logger.logException("e", "Exception in mesh loader") if not node: loading_message.hide() result_message = Message(i18n_catalog.i18nc( "@info:status", "Failed to load <filename>{0}</filename>", self._filename), lifetime=0) result_message.show() return # Scale down to maximum bounds size if that is available if hasattr(Application.getInstance().getController().getScene(), "_maximum_bounds"): max_bounds = Application.getInstance().getController().getScene( )._maximum_bounds node._resetAABB() build_bounds = node.getBoundingBox() if Preferences.getInstance().getValue( "mesh/scale_to_fit") == True or Preferences.getInstance( ).getValue("mesh/scale_tiny_meshes") == True: scale_factor_width = max_bounds.width / build_bounds.width scale_factor_height = max_bounds.height / build_bounds.height scale_factor_depth = max_bounds.depth / build_bounds.depth scale_factor = min(scale_factor_width, scale_factor_depth, scale_factor_height) if Preferences.getInstance().getValue( "mesh/scale_to_fit") == True and ( scale_factor_width < 1 or scale_factor_height < 1 or scale_factor_depth < 1 ): # Use scale factor to scale large object down # Ignore scaling on models which are less than 1.25 times bigger than the build volume ignore_factor = 1.25 if 1 / scale_factor < ignore_factor: Logger.log( "i", "Ignoring auto-scaling, because %.3d < %.3d" % (1 / scale_factor, ignore_factor)) scale_factor = 1 pass elif Preferences.getInstance( ).getValue("mesh/scale_tiny_meshes") == True and ( scale_factor_width > 100 and scale_factor_height > 100 and scale_factor_depth > 100): # Round scale factor to lower factor of 10 to scale tiny object up (eg convert m to mm units) scale_factor = math.pow( 10, math.floor(math.log(scale_factor) / math.log(10))) else: scale_factor = 1 if scale_factor != 1: scale_vector = Vector(scale_factor, scale_factor, scale_factor) display_scale_factor = scale_factor * 100 scale_message = Message( i18n_catalog.i18nc( "@info:status", "Auto scaled object to {0}% of original size", ("%i" % display_scale_factor))) try: node.scale(scale_vector) scale_message.show() except Exception: Logger.logException( "e", "While auto-scaling an exception has been raised") self.setResult(node) loading_message.hide()
def read(self, file_name): archive = zipfile.ZipFile(file_name, "r") cura_file_names = [ name for name in archive.namelist() if name.startswith("Cura/") ] # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its # parsing code. temp_preferences = Preferences() temp_preferences.readFromFile( io.TextIOWrapper(archive.open("Cura/preferences.cfg")) ) # We need to wrap it, else the archive parser breaks. # Copy a number of settings from the temp preferences to the global global_preferences = Preferences.getInstance() visible_settings = temp_preferences.getValue( "general/visible_settings") if visible_settings is None: Logger.log( "w", "Workspace did not contain visible settings. Leaving visibility unchanged" ) else: global_preferences.setValue("general/visible_settings", visible_settings) categories_expanded = temp_preferences.getValue( "cura/categories_expanded") if categories_expanded is None: Logger.log( "w", "Workspace did not contain expanded categories. Leaving them unchanged" ) else: global_preferences.setValue("cura/categories_expanded", categories_expanded) Application.getInstance().expandedCategoriesChanged.emit( ) # Notify the GUI of the change self._id_mapping = {} # We don't add containers right away, but wait right until right before the stack serialization. # We do this so that if something goes wrong, it's easier to clean up. containers_to_add = [] # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few # TODO: cases that the container loaded is the same (most notable in materials & definitions). # TODO: It might be possible that we need to add smarter checking in the future. Logger.log("d", "Workspace loading is checking definitions...") # Get all the definition files & check if they exist. If not, add them. definition_container_files = [ name for name in cura_file_names if name.endswith(self._definition_container_suffix) ] for definition_container_file in definition_container_files: container_id = self._stripFileToId(definition_container_file) definitions = self._container_registry.findDefinitionContainers( id=container_id) if not definitions: definition_container = DefinitionContainer(container_id) definition_container.deserialize( archive.open(definition_container_file).read().decode( "utf-8")) self._container_registry.addContainer(definition_container) Job.yieldThread() Logger.log("d", "Workspace loading is checking materials...") material_containers = [] # Get all the material files and check if they exist. If not, add them. xml_material_profile = self._getXmlProfileClass() if self._material_container_suffix is None: self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer( xml_material_profile).suffixes[0] if xml_material_profile: material_container_files = [ name for name in cura_file_names if name.endswith(self._material_container_suffix) ] for material_container_file in material_container_files: container_id = self._stripFileToId(material_container_file) materials = self._container_registry.findInstanceContainers( id=container_id) if not materials: material_container = xml_material_profile(container_id) material_container.deserialize( archive.open(material_container_file).read().decode( "utf-8")) containers_to_add.append(material_container) else: if not materials[0].isReadOnly( ): # Only create new materials if they are not read only. if self._resolve_strategies["material"] == "override": materials[0].deserialize( archive.open(material_container_file).read(). decode("utf-8")) elif self._resolve_strategies["material"] == "new": # Note that we *must* deserialize it with a new ID, as multiple containers will be # auto created & added. material_container = xml_material_profile( self.getNewId(container_id)) material_container.deserialize( archive.open(material_container_file).read(). decode("utf-8")) containers_to_add.append(material_container) material_containers.append(material_container) Job.yieldThread() Logger.log("d", "Workspace loading is checking instance containers...") # Get quality_changes and user profiles saved in the workspace instance_container_files = [ name for name in cura_file_names if name.endswith(self._instance_container_suffix) ] user_instance_containers = [] quality_changes_instance_containers = [] for instance_container_file in instance_container_files: container_id = self._stripFileToId(instance_container_file) instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string instance_container.deserialize( archive.open(instance_container_file).read().decode("utf-8")) container_type = instance_container.getMetaDataEntry("type") Job.yieldThread() if container_type == "user": # Check if quality changes already exists. user_containers = self._container_registry.findInstanceContainers( id=container_id) if not user_containers: containers_to_add.append(instance_container) else: if self._resolve_strategies[ "machine"] == "override" or self._resolve_strategies[ "machine"] is None: user_containers[0].deserialize( archive.open(instance_container_file).read(). decode("utf-8")) elif self._resolve_strategies["machine"] == "new": # The machine is going to get a spiffy new name, so ensure that the id's of user settings match. extruder_id = instance_container.getMetaDataEntry( "extruder", None) if extruder_id: new_id = self.getNewId( extruder_id) + "_current_settings" instance_container._id = new_id instance_container.setName(new_id) instance_container.setMetaDataEntry( "extruder", self.getNewId(extruder_id)) containers_to_add.append(instance_container) machine_id = instance_container.getMetaDataEntry( "machine", None) if machine_id: new_id = self.getNewId( machine_id) + "_current_settings" instance_container._id = new_id instance_container.setName(new_id) instance_container.setMetaDataEntry( "machine", self.getNewId(machine_id)) containers_to_add.append(instance_container) user_instance_containers.append(instance_container) elif container_type == "quality_changes": # Check if quality changes already exists. quality_changes = self._container_registry.findInstanceContainers( id=container_id) if not quality_changes: containers_to_add.append(instance_container) else: if self._resolve_strategies[ "quality_changes"] == "override": quality_changes[0].deserialize( archive.open(instance_container_file).read(). decode("utf-8")) elif self._resolve_strategies["quality_changes"] is None: # The ID already exists, but nothing in the values changed, so do nothing. pass quality_changes_instance_containers.append(instance_container) else: continue # Add all the containers right before we try to add / serialize the stack for container in containers_to_add: self._container_registry.addContainer(container) # Get the stack(s) saved in the workspace. Logger.log("d", "Workspace loading is checking stacks containers...") container_stack_files = [ name for name in cura_file_names if name.endswith(self._container_stack_suffix) ] global_stack = None extruder_stacks = [] container_stacks_added = [] try: for container_stack_file in container_stack_files: container_id = self._stripFileToId(container_stack_file) # Check if a stack by this ID already exists; container_stacks = self._container_registry.findContainerStacks( id=container_id) if container_stacks: stack = container_stacks[0] if self._resolve_strategies["machine"] == "override": # TODO: HACK # There is a machine, check if it has authenticationd data. If so, keep that data. network_authentication_id = container_stacks[ 0].getMetaDataEntry("network_authentication_id") network_authentication_key = container_stacks[ 0].getMetaDataEntry("network_authentication_key") container_stacks[0].deserialize( archive.open(container_stack_file).read().decode( "utf-8")) if network_authentication_id: container_stacks[0].addMetaDataEntry( "network_authentication_id", network_authentication_id) if network_authentication_key: container_stacks[0].addMetaDataEntry( "network_authentication_key", network_authentication_key) elif self._resolve_strategies["machine"] == "new": new_id = self.getNewId(container_id) stack = ContainerStack(new_id) stack.deserialize( archive.open(container_stack_file).read().decode( "utf-8")) # Ensure a unique ID and name stack._id = new_id # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the # bound machine also needs to change. if stack.getMetaDataEntry("machine", None): stack.setMetaDataEntry( "machine", self.getNewId( stack.getMetaDataEntry("machine"))) if stack.getMetaDataEntry("type") != "extruder_train": # Only machines need a new name, stacks may be non-unique stack.setName( self._container_registry.uniqueName( stack.getName())) container_stacks_added.append(stack) self._container_registry.addContainer(stack) else: Logger.log( "w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"]) else: stack = ContainerStack(container_id) # Deserialize stack by converting read data from bytes to string stack.deserialize( archive.open(container_stack_file).read().decode( "utf-8")) container_stacks_added.append(stack) self._container_registry.addContainer(stack) if stack.getMetaDataEntry("type") == "extruder_train": extruder_stacks.append(stack) else: global_stack = stack Job.yieldThread() except: Logger.logException( "w", "We failed to serialize the stack. Trying to clean up.") # Something went really wrong. Try to remove any data that we added. for container in containers_to_add: self._container_registry.getInstance().removeContainer( container.getId()) for container in container_stacks_added: self._container_registry.getInstance().removeContainer( container.getId()) return None if self._resolve_strategies["machine"] == "new": # A new machine was made, but it was serialized with the wrong user container. Fix that now. for container in user_instance_containers: extruder_id = container.getMetaDataEntry("extruder", None) if extruder_id: for extruder in extruder_stacks: if extruder.getId() == extruder_id: extruder.replaceContainer(0, container) continue machine_id = container.getMetaDataEntry("machine", None) if machine_id: if global_stack.getId() == machine_id: global_stack.replaceContainer(0, container) continue if self._resolve_strategies["quality_changes"] == "new": # Quality changes needs to get a new ID, added to registry and to the right stacks for container in quality_changes_instance_containers: old_id = container.getId() container.setName( self._container_registry.uniqueName(container.getName())) # We're not really supposed to change the ID in normal cases, but this is an exception. container._id = self.getNewId(container.getId()) # The container was not added yet, as it didn't have an unique ID. It does now, so add it. self._container_registry.addContainer(container) # Replace the quality changes container old_container = global_stack.findContainer( {"type": "quality_changes"}) if old_container.getId() == old_id: quality_changes_index = global_stack.getContainerIndex( old_container) global_stack.replaceContainer(quality_changes_index, container) continue for stack in extruder_stacks: old_container = stack.findContainer( {"type": "quality_changes"}) if old_container.getId() == old_id: quality_changes_index = stack.getContainerIndex( old_container) stack.replaceContainer(quality_changes_index, container) if self._resolve_strategies["material"] == "new": for material in material_containers: old_material = global_stack.findContainer({"type": "material"}) if old_material.getId() in self._id_mapping: material_index = global_stack.getContainerIndex( old_material) global_stack.replaceContainer(material_index, material) continue for stack in extruder_stacks: old_material = stack.findContainer({"type": "material"}) if old_material.getId() in self._id_mapping: material_index = stack.getContainerIndex(old_material) stack.replaceContainer(material_index, material) continue for stack in extruder_stacks: ExtruderManager.getInstance().registerExtruder( stack, global_stack.getId()) else: # Machine has no extruders, but it needs to be registered with the extruder manager. ExtruderManager.getInstance().registerExtruder( None, global_stack.getId()) Logger.log( "d", "Workspace loading is notifying rest of the code of changes...") # Notify everything/one that is to notify about changes. global_stack.containersChanged.emit(global_stack.getTop()) for stack in extruder_stacks: stack.setNextStack(global_stack) stack.containersChanged.emit(stack.getTop()) # Actually change the active machine. Application.getInstance().setGlobalContainerStack(global_stack) # Load all the nodes / meshdata of the workspace nodes = self._3mf_mesh_reader.read(file_name) if nodes is None: nodes = [] return nodes
def __init__(self): super().__init__() # Find out where the engine is located, and how it is called. # This depends on how Cura is packaged and which OS we are running on. executable_name = "CuraEngine" if Platform.isWindows(): executable_name += ".exe" default_engine_location = executable_name if os.path.exists(os.path.join(Application.getInstallPrefix(), "bin", executable_name)): default_engine_location = os.path.join(Application.getInstallPrefix(), "bin", executable_name) if hasattr(sys, "frozen"): default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), executable_name) if Platform.isLinux() and not default_engine_location: if not os.getenv("PATH"): raise OSError("There is something wrong with your Linux installation.") for pathdir in os.getenv("PATH").split(os.pathsep): execpath = os.path.join(pathdir, executable_name) if os.path.exists(execpath): default_engine_location = execpath break if not default_engine_location: raise EnvironmentError("Could not find CuraEngine") Logger.log("i", "Found CuraEngine at: %s" %(default_engine_location)) default_engine_location = os.path.abspath(default_engine_location) Preferences.getInstance().addPreference("backend/location", default_engine_location) self._scene = Application.getInstance().getController().getScene() self._scene.sceneChanged.connect(self._onSceneChanged) self._pause_slicing = False self._block_slicing = False # continueSlicing does not have effect if True # Workaround to disable layer view processing if layer view is not active. self._layer_view_active = False Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) self._onActiveViewChanged() self._stored_layer_data = [] self._stored_optimized_layer_data = [] # Triggers for when to (re)start slicing: self._global_container_stack = None Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) self._onGlobalStackChanged() self._active_extruder_stack = None ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged) self._onActiveExtruderChanged() # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. # This timer will group them up, and only slice for the last setting changed signal. # TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction. self._change_timer = QTimer() self._change_timer.setInterval(500) self._change_timer.setSingleShot(True) self._change_timer.timeout.connect(self.slice) # Listeners for receiving messages from the back-end. self._message_handlers["cura.proto.Layer"] = self._onLayerMessage self._message_handlers["cura.proto.LayerOptimized"] = self._onOptimizedLayerMessage self._message_handlers["cura.proto.Progress"] = self._onProgressMessage self._message_handlers["cura.proto.GCodeLayer"] = self._onGCodeLayerMessage self._message_handlers["cura.proto.GCodePrefix"] = self._onGCodePrefixMessage self._message_handlers["cura.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage self._start_slice_job = None self._slicing = False # Are we currently slicing? self._restart = False # Back-end is currently restarting? self._enabled = True # Should we be slicing? Slicing might be paused when, for instance, the user is dragging the mesh around. self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers. self._backend_log_max_lines = 20000 # Maximum number of lines to buffer self._error_message = None # Pop-up message that shows errors. self.backendQuit.connect(self._onBackendQuit) self.backendConnected.connect(self._onBackendConnected) # When a tool operation is in progress, don't slice. So we need to listen for tool operations. Application.getInstance().getController().toolOperationStarted.connect(self._onToolOperationStarted) Application.getInstance().getController().toolOperationStopped.connect(self._onToolOperationStopped) self._slice_start_time = None
def __init__(self): super().__init__() self._shortcut_key = Qt.Key_G self._controller = Application.getInstance().getController()
def preRead(self, file_name): self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler( ).getReaderForFile(file_name) if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead( file_name) == WorkspaceReader.PreReadResult.accepted: pass else: Logger.log( "w", "Could not find reader that was able to read the scene data for 3MF workspace" ) return WorkspaceReader.PreReadResult.failed machine_name = "" # Check if there are any conflicts, so we can ask the user. archive = zipfile.ZipFile(file_name, "r") cura_file_names = [ name for name in archive.namelist() if name.startswith("Cura/") ] container_stack_files = [ name for name in cura_file_names if name.endswith(self._container_stack_suffix) ] self._resolve_strategies = { "machine": None, "quality_changes": None, "material": None } machine_conflict = False quality_changes_conflict = False for container_stack_file in container_stack_files: container_id = self._stripFileToId(container_stack_file) serialized = archive.open(container_stack_file).read().decode( "utf-8") if machine_name == "": machine_name = self._getMachineNameFromSerializedStack( serialized) stacks = self._container_registry.findContainerStacks( id=container_id) if stacks: # Check if there are any changes at all in any of the container stacks. id_list = self._getContainerIdListFromSerialized(serialized) for index, container_id in enumerate(id_list): if stacks[0].getContainer(index).getId() != container_id: machine_conflict = True Job.yieldThread() material_labels = [] material_conflict = False xml_material_profile = self._getXmlProfileClass() if self._material_container_suffix is None: self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer( xml_material_profile).preferredSuffix if xml_material_profile: material_container_files = [ name for name in cura_file_names if name.endswith(self._material_container_suffix) ] for material_container_file in material_container_files: container_id = self._stripFileToId(material_container_file) materials = self._container_registry.findInstanceContainers( id=container_id) material_labels.append( self._getMaterialLabelFromSerialized( archive.open(material_container_file).read().decode( "utf-8"))) if materials and not materials[0].isReadOnly( ): # Only non readonly materials can be in conflict material_conflict = True Job.yieldThread() # Check if any quality_changes instance container is in conflict. instance_container_files = [ name for name in cura_file_names if name.endswith(self._instance_container_suffix) ] quality_name = "" quality_type = "" num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes for instance_container_file in instance_container_files: container_id = self._stripFileToId(instance_container_file) instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string instance_container.deserialize( archive.open(instance_container_file).read().decode("utf-8")) container_type = instance_container.getMetaDataEntry("type") if container_type == "quality_changes": quality_name = instance_container.getName() num_settings_overriden_by_quality_changes += len( instance_container._instances) # Check if quality changes already exists. quality_changes = self._container_registry.findInstanceContainers( id=container_id) if quality_changes: # Check if there really is a conflict by comparing the values if quality_changes[0] != instance_container: quality_changes_conflict = True elif container_type == "quality": # If the quality name is not set (either by quality or changes, set it now) # Quality changes should always override this (as they are "on top") if quality_name == "": quality_name = instance_container.getName() quality_type = instance_container.getName() Job.yieldThread() num_visible_settings = 0 try: temp_preferences = Preferences() temp_preferences.readFromFile( io.TextIOWrapper(archive.open("Cura/preferences.cfg")) ) # We need to wrap it, else the archive parser breaks. visible_settings_string = temp_preferences.getValue( "general/visible_settings") if visible_settings_string is not None: num_visible_settings = len(visible_settings_string.split(";")) active_mode = temp_preferences.getValue("cura/active_mode") if not active_mode: active_mode = Preferences.getInstance().getValue( "cura/active_mode") except KeyError: # If there is no preferences file, it's not a workspace, so notify user of failure. Logger.log("w", "File %s is not a valid workspace.", file_name) return WorkspaceReader.PreReadResult.failed # Show the dialog, informing the user what is about to happen. self._dialog.setMachineConflict(machine_conflict) self._dialog.setQualityChangesConflict(quality_changes_conflict) self._dialog.setMaterialConflict(material_conflict) self._dialog.setNumVisibleSettings(num_visible_settings) self._dialog.setQualityName(quality_name) self._dialog.setQualityType(quality_type) self._dialog.setNumSettingsOverridenByQualityChanges( num_settings_overriden_by_quality_changes) self._dialog.setActiveMode(active_mode) self._dialog.setMachineName(machine_name) self._dialog.setMaterialLabels(material_labels) self._dialog.setHasObjectsOnPlate( Application.getInstance().getPlatformActivity) self._dialog.show() # Block until the dialog is closed. self._dialog.waitForClose() if self._dialog.getResult() == {}: return WorkspaceReader.PreReadResult.cancelled self._resolve_strategies = self._dialog.getResult() return WorkspaceReader.PreReadResult.accepted
def __updateExtruders(self): extruders_changed = False if self.count != 0: extruders_changed = True items = [] global_container_stack = Application.getInstance( ).getGlobalContainerStack() if global_container_stack: # get machine extruder count for verification machine_extruder_count = global_container_stack.getProperty( "machine_extruder_count", "value") for extruder in Application.getInstance().getExtruderManager( ).getActiveExtruderStacks(): position = extruder.getMetaDataEntry("position", default="0") try: position = int(position) except ValueError: # Not a proper int. position = -1 if position >= machine_extruder_count: continue default_color = self.defaultColors[ position] if 0 <= position < len( self.defaultColors) else self.defaultColors[0] color = extruder.material.getMetaDataEntry( "color_code", default=default_color ) if extruder.material else default_color material_brand = extruder.material.getMetaDataEntry( "brand", default="generic") color_name = extruder.material.getMetaDataEntry("color_name") # construct an item with only the relevant information item = { "id": extruder.getId(), "name": extruder.getName(), "enabled": extruder.isEnabled, "color": color, "index": position, "definition": extruder.getBottom().getId(), "material": extruder.material.getName() if extruder.material else "", "variant": extruder.variant.getName() if extruder.variant else "", # e.g. print core "stack": extruder, "material_brand": material_brand, "color_name": color_name } items.append(item) extruders_changed = True if extruders_changed: # sort by extruder index items.sort(key=lambda i: i["index"]) # We need optional extruder to be last, so add it after we do sorting. # This way we can simply interpret the -1 of the index as the last item (which it now always is) if self._add_optional_extruder: item = { "id": "", "name": catalog.i18nc("@menuitem", "Not overridden"), "enabled": True, "color": "#ffffff", "index": -1, "definition": "" } items.append(item) if self._items != items: self.setItems(items) self.modelChanged.emit()
def read(self, file_name): if file_name.split(".")[-1] != "ini": return None global_container_stack = Application.getInstance( ).getGlobalContainerStack() if not global_container_stack: return None multi_extrusion = global_container_stack.getProperty( "machine_extruder_count", "value") > 1 if multi_extrusion: Logger.log( "e", "Unable to import legacy profile %s. Multi extrusion is not supported", file_name) raise Exception( "Unable to import legacy profile. Multi extrusion is not supported" ) Logger.log("i", "Importing legacy profile from file " + file_name + ".") profile = InstanceContainer( "Imported Legacy Profile") # Create an empty profile. parser = configparser.ConfigParser(interpolation=None) try: parser.read([file_name]) # Parse the INI file. except Exception as e: Logger.log("e", "Unable to open legacy profile %s: %s", file_name, str(e)) return None # Legacy Cura saved the profile under the section "profile_N" where N is the ID of a machine, except when you export in which case it saves it in the section "profile". # Since importing multiple machine profiles is out of scope, just import the first section we find. section = "" for found_section in parser.sections(): if found_section.startswith("profile"): section = found_section break if not section: # No section starting with "profile" was found. Probably not a proper INI file. return None try: with open( os.path.join( PluginRegistry.getInstance().getPluginPath( "LegacyProfileReader"), "DictionaryOfDoom.json"), "r", -1, "utf-8") as f: dict_of_doom = json.load(f) # Parse the Dictionary of Doom. except IOError as e: Logger.log("e", "Could not open DictionaryOfDoom.json for reading: %s", str(e)) return None except Exception as e: Logger.log("e", "Could not parse DictionaryOfDoom.json: %s", str(e)) return None defaults = self.prepareDefaults(dict_of_doom) legacy_settings = self.prepareLocals( parser, section, defaults) #Gets the settings from the legacy profile. #Check the target version in the Dictionary of Doom with this application version. if "target_version" not in dict_of_doom: Logger.log( "e", "Dictionary of Doom has no target version. Is it the correct JSON file?" ) return None if InstanceContainer.Version != dict_of_doom["target_version"]: Logger.log( "e", "Dictionary of Doom of legacy profile reader (version %s) is not in sync with the current instance container version (version %s)!", dict_of_doom["target_version"], str(InstanceContainer.Version)) return None if "translation" not in dict_of_doom: Logger.log( "e", "Dictionary of Doom has no translation. Is it the correct JSON file?" ) return None current_printer_definition = global_container_stack.getBottom() profile.setDefinition(current_printer_definition) for new_setting in dict_of_doom[ "translation"]: # Evaluate all new settings that would get a value from the translations. old_setting_expression = dict_of_doom["translation"][new_setting] compiled = compile(old_setting_expression, new_setting, "eval") try: new_value = eval( compiled, {"math": math}, legacy_settings ) # Pass the legacy settings as local variables to allow access to in the evaluation. value_using_defaults = eval( compiled, {"math": math}, defaults ) #Evaluate again using only the default values to try to see if they are default. except Exception: # Probably some setting name that was missing or something else that went wrong in the ini file. Logger.log( "w", "Setting " + new_setting + " could not be set because the evaluation failed. Something is probably missing from the imported legacy profile." ) continue definitions = current_printer_definition.findDefinitions( key=new_setting) if definitions: if new_value != value_using_defaults and definitions[ 0].default_value != new_value: # Not equal to the default in the new Cura OR the default in the legacy Cura. profile.setProperty( new_setting, "value", new_value) # Store the setting in the profile! if len(profile.getAllKeys()) == 0: Logger.log( "i", "A legacy profile was imported but everything evaluates to the defaults, creating an empty profile." ) # We need to downgrade the container to version 1 (in Cura 2.1) so the upgrade system can correctly upgrade # it to the latest version. profile.addMetaDataEntry("type", "profile") # don't know what quality_type it is based on, so use "normal" by default profile.addMetaDataEntry("quality_type", "normal") profile.setDirty(True) parser = configparser.ConfigParser(interpolation=None) data = profile.serialize() parser.read_string(data) parser["general"]["version"] = "1" if parser.has_section("values"): parser["settings"] = parser["values"] del parser["values"] stream = io.StringIO() parser.write(stream) data = stream.getvalue() profile.deserialize(data) return profile
def requestWrite(self, nodes, file_name=None, limit_mimetypes=None): if self._writing: raise OutputDeviceError.DeviceBusyError() dialog = QFileDialog() dialog.setWindowTitle(catalog.i18nc("@title:window", "Save to File")) dialog.setFileMode(QFileDialog.AnyFile) dialog.setAcceptMode(QFileDialog.AcceptSave) # Ensure platform never ask for overwrite confirmation since we do this ourselves dialog.setOption(QFileDialog.DontConfirmOverwrite) if sys.platform == "linux" and "KDE_FULL_SESSION" in os.environ: dialog.setOption(QFileDialog.DontUseNativeDialog) filters = [] mime_types = [] selected_filter = None last_used_type = Preferences.getInstance().getValue( "local_file/last_used_type") file_types = Application.getInstance().getMeshFileHandler( ).getSupportedFileTypesWrite() file_types.sort(key=lambda k: k["description"]) if limit_mimetypes: file_types = list( filter(lambda i: i["mime_type"] in limit_mimetypes, file_types)) if len(file_types) == 0: Logger.log("e", "There are no file types available to write with!") raise OutputDeviceError.WriteRequestFailedError() for item in file_types: type_filter = "{0} (*.{1})".format(item["description"], item["extension"]) filters.append(type_filter) mime_types.append(item["mime_type"]) if last_used_type == item["mime_type"]: selected_filter = type_filter if file_name: file_name += "." + item["extension"] dialog.setNameFilters(filters) if selected_filter != None: dialog.selectNameFilter(selected_filter) if file_name != None: dialog.selectFile(file_name) stored_directory = Preferences.getInstance().getValue( "local_file/dialog_save_path") dialog.setDirectory(stored_directory) if not dialog.exec_(): raise OutputDeviceError.UserCanceledError() save_path = dialog.directory().absolutePath() Preferences.getInstance().setValue("local_file/dialog_save_path", save_path) selected_type = file_types[filters.index(dialog.selectedNameFilter())] Preferences.getInstance().setValue("local_file/last_used_type", selected_type["mime_type"]) file_name = dialog.selectedFiles()[0] if os.path.exists(file_name): result = QMessageBox.question( None, catalog.i18nc("@title:window", "File Already Exists"), catalog.i18nc( "@label", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?" ).format(file_name)) if result == QMessageBox.No: raise OutputDeviceError.UserCanceledError() self.writeStarted.emit(self) mesh_writer = Application.getInstance().getMeshFileHandler().getWriter( selected_type["id"]) try: mode = selected_type["mode"] if mode == MeshWriter.OutputMode.TextMode: Logger.log("d", "Writing to Local File %s in text mode", file_name) stream = open(file_name, "wt", encoding="utf-8") elif mode == MeshWriter.OutputMode.BinaryMode: Logger.log("d", "Writing to Local File %s in binary mode", file_name) stream = open(file_name, "wb") job = WriteMeshJob(mesh_writer, stream, nodes, mode) job.setFileName(file_name) job.progress.connect(self._onJobProgress) job.finished.connect(self._onWriteJobFinished) message = Message( catalog.i18nc( "@info:progress", "Saving to <filename>{0}</filename>").format(file_name), 0, False, -1) message.show() job._message = message self._writing = True job.start() except PermissionError as e: Logger.log("e", "Permission denied when trying to write to %s: %s", file_name, str(e)) raise OutputDeviceError.PermissionDeniedError( catalog.i18nc( "@info:status", "Permission denied when trying to save <filename>{0}</filename>" ).format(file_name)) from e except OSError as e: Logger.log("e", "Operating system would not let us write to %s: %s", file_name, str(e)) raise OutputDeviceError.WriteRequestFailedError( catalog.i18nc( "@info:status", "Could not save to <filename>{0}</filename>: <message>{1}</message>" ).format()) from e
def run(self): Logger.log( "d", "Processing new layer for build plate %s..." % self._build_plate_number) start_time = time() view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "SimulationView": view.resetLayerData() self._progress_message.show() Job.yieldThread() if self._abort_requested: if self._progress_message: self._progress_message.hide() return Application.getInstance().getController().activeViewChanged.connect( self._onActiveViewChanged) # The no_setting_override is here because adding the SettingOverrideDecorator will trigger a reslice new_node = CuraSceneNode(no_setting_override=True) new_node.addDecorator(BuildPlateDecorator(self._build_plate_number)) # Force garbage collection. # For some reason, Python has a tendency to keep the layer data # in memory longer than needed. Forcing the GC to run here makes # sure any old layer data is really cleaned up before adding new. gc.collect() mesh = MeshData() layer_data = LayerDataBuilder.LayerDataBuilder() layer_count = len(self._layers) # Find the minimum layer number # When disabling the remove empty first layers setting, the minimum layer number will be a positive # value. In that case the first empty layers will be discarded and start processing layers from the # first layer with data. # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we # simply offset all other layers so the lowest layer is always 0. It could happens that the first # raft layer has value -8 but there are just 4 raft (negative) layers. min_layer_number = sys.maxsize negative_layers = 0 for layer in self._layers: if layer.repeatedMessageCount("path_segment") > 0: if layer.id < min_layer_number: min_layer_number = layer.id if layer.id < 0: negative_layers += 1 current_layer = 0 for layer in self._layers: # If the layer is below the minimum, it means that there is no data, so that we don't create a layer # data. However, if there are empty layers in between, we compute them. if layer.id < min_layer_number: continue # Layers are offset by the minimum layer number. In case the raft (negative layers) is being used, # then the absolute layer number is adjusted by removing the empty layers that can be in between raft # and the model abs_layer_number = layer.id - min_layer_number if layer.id >= 0 and negative_layers != 0: abs_layer_number += (min_layer_number + negative_layers) layer_data.addLayer(abs_layer_number) this_layer = layer_data.getLayer(abs_layer_number) layer_data.setLayerHeight(abs_layer_number, layer.height) layer_data.setLayerThickness(abs_layer_number, layer.thickness) for p in range(layer.repeatedMessageCount("path_segment")): polygon = layer.getRepeatedMessage("path_segment", p) extruder = polygon.extruder line_types = numpy.fromstring( polygon.line_type, dtype="u1") # Convert bytearray to numpy array line_types = line_types.reshape((-1, 1)) points = numpy.fromstring( polygon.points, dtype="f4") # Convert bytearray to numpy array if polygon.point_type == 0: # Point2D points = points.reshape( (-1, 2) ) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. else: # Point3D points = points.reshape((-1, 3)) line_widths = numpy.fromstring( polygon.line_width, dtype="f4") # Convert bytearray to numpy array line_widths = line_widths.reshape( (-1, 1) ) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. line_thicknesses = numpy.fromstring( polygon.line_thickness, dtype="f4") # Convert bytearray to numpy array line_thicknesses = line_thicknesses.reshape( (-1, 1) ) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. line_feedrates = numpy.fromstring( polygon.line_feedrate, dtype="f4") # Convert bytearray to numpy array line_feedrates = line_feedrates.reshape( (-1, 1) ) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. # Create a new 3D-array, copy the 2D points over and insert the right height. # This uses manual array creation + copy rather than numpy.insert since this is # faster. new_points = numpy.empty((len(points), 3), numpy.float32) if polygon.point_type == 0: # Point2D new_points[:, 0] = points[:, 0] new_points[:, 1] = layer.height / 1000 # layer height value is in backend representation new_points[:, 2] = -points[:, 1] else: # Point3D new_points[:, 0] = points[:, 0] new_points[:, 1] = points[:, 2] new_points[:, 2] = -points[:, 1] this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses, line_feedrates) this_poly.buildCache() this_layer.polygons.append(this_poly) Job.yieldThread() Job.yieldThread() current_layer += 1 progress = (current_layer / layer_count) * 99 # TODO: Rebuild the layer data mesh once the layer has been processed. # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. if self._abort_requested: if self._progress_message: self._progress_message.hide() return if self._progress_message: self._progress_message.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data # Find out colors per extruder global_container_stack = Application.getInstance( ).getGlobalContainerStack() manager = ExtruderManager.getInstance() extruders = manager.getActiveExtruderStacks() if extruders: material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32) for extruder in extruders: position = int( extruder.getMetaDataEntry("position", default="0")) # Get the position try: default_color = ExtrudersModel.defaultColors[position] except IndexError: default_color = "#e0e000" color_code = extruder.material.getMetaDataEntry( "color_code", default=default_color) color = colorCodeToRGBA(color_code) material_color_map[position, :] = color else: # Single extruder via global stack. material_color_map = numpy.zeros((1, 4), dtype=numpy.float32) color_code = global_container_stack.material.getMetaDataEntry( "color_code", default="#e0e000") color = colorCodeToRGBA(color_code) material_color_map[0, :] = color # We have to scale the colors for compatibility mode if OpenGLContext.isLegacyOpenGL() or bool( Application.getInstance().getPreferences().getValue( "view/force_layer_view_compatibility_mode")): line_type_brightness = 0.5 # for compatibility mode else: line_type_brightness = 1.0 layer_mesh = layer_data.build(material_color_map, line_type_brightness) if self._abort_requested: if self._progress_message: self._progress_message.hide() return # Add LayerDataDecorator to scene node to indicate that the node has layer data decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_mesh) new_node.addDecorator(decorator) new_node.setMeshData(mesh) # Set build volume as parent, the build volume can move as a result of raft settings. # It makes sense to set the build volume as parent: the print is actually printed on it. new_node_parent = Application.getInstance().getBuildVolume() new_node.setParent( new_node_parent) # Note: After this we can no longer abort! settings = Application.getInstance().getGlobalContainerStack() if not settings.getProperty("machine_center_is_zero", "value"): new_node.setPosition( Vector(-settings.getProperty("machine_width", "value") / 2, 0.0, settings.getProperty("machine_depth", "value") / 2)) if self._progress_message: self._progress_message.setProgress(100) if self._progress_message: self._progress_message.hide() # Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed. self._layers = None Logger.log("d", "Processing layers took %s seconds", time() - start_time)
def _machineHasOwnQualities(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", False)) return False
def _createSettingsDialogue(self) -> QQuickWindow: qml_file_path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "CuraSnapmakerSenderSettings.qml") component = Application.getInstance().createQmlComponent(qml_file_path,{"manager": self}) return component
def _machineHasOwnMaterials(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: return global_container_stack.getMetaDataEntry("has_materials", False) return False
def __init__(self, parent=None): super().__init__(parent) self._container_registry = ContainerRegistry.getInstance() self._machine_manager = Application.getInstance().getMachineManager() self._container_name_filters = {}
def run(self): start_time = time() if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView": self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1) self._progress.show() Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) new_node = SceneNode() ## Remove old layer data (if any) for node in DepthFirstIterator(self._scene.getRoot()): if node.callDecoration("getLayerData"): node.getParent().removeChild(node) break if self._abort_requested: if self._progress: self._progress.hide() return mesh = MeshData() layer_data = LayerDataBuilder.LayerDataBuilder() layer_count = len(self._layers) # Find the minimum layer number # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we # instead simply offset all other layers so the lowest layer is always 0. min_layer_number = 0 for layer in self._layers: if layer.id < min_layer_number: min_layer_number = layer.id current_layer = 0 for layer in self._layers: abs_layer_number = layer.id + abs(min_layer_number) layer_data.addLayer(abs_layer_number) this_layer = layer_data.getLayer(abs_layer_number) layer_data.setLayerHeight(abs_layer_number, layer.height) layer_data.setLayerThickness(abs_layer_number, layer.thickness) for p in range(layer.repeatedMessageCount("path_segment")): polygon = layer.getRepeatedMessage("path_segment", p) extruder = polygon.extruder line_types = numpy.fromstring(polygon.line_type, dtype="u1") # Convert bytearray to numpy array line_types = line_types.reshape((-1,1)) points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array if polygon.point_type == 0: # Point2D points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. else: # Point3D points = points.reshape((-1,3)) line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. # Create a new 3D-array, copy the 2D points over and insert the right height. # This uses manual array creation + copy rather than numpy.insert since this is # faster. new_points = numpy.empty((len(points), 3), numpy.float32) if polygon.point_type == 0: # Point2D new_points[:, 0] = points[:, 0] new_points[:, 1] = layer.height / 1000 # layer height value is in backend representation new_points[:, 2] = -points[:, 1] else: # Point3D new_points[:, 0] = points[:, 0] new_points[:, 1] = points[:, 2] new_points[:, 2] = -points[:, 1] this_poly = LayerPolygon.LayerPolygon(layer_data, extruder, line_types, new_points, line_widths) this_poly.buildCache() this_layer.polygons.append(this_poly) Job.yieldThread() Job.yieldThread() current_layer += 1 progress = (current_layer / layer_count) * 99 # TODO: Rebuild the layer data mesh once the layer has been processed. # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. if self._abort_requested: if self._progress: self._progress.hide() return if self._progress: self._progress.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data layer_mesh = layer_data.build() if self._abort_requested: if self._progress: self._progress.hide() return # Add LayerDataDecorator to scene node to indicate that the node has layer data decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_mesh) new_node.addDecorator(decorator) new_node.setMeshData(mesh) # Set build volume as parent, the build volume can move as a result of raft settings. # It makes sense to set the build volume as parent: the print is actually printed on it. new_node_parent = Application.getInstance().getBuildVolume() new_node.setParent(new_node_parent) # Note: After this we can no longer abort! settings = Application.getInstance().getGlobalContainerStack() if not settings.getProperty("machine_center_is_zero", "value"): new_node.setPosition(Vector(-settings.getProperty("machine_width", "value") / 2, 0.0, settings.getProperty("machine_depth", "value") / 2)) if self._progress: self._progress.setProgress(100) view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": view.resetLayerData() if self._progress: self._progress.hide() # Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed. self._layers = None Logger.log("d", "Processing layers took %s seconds", time() - start_time)
def _activeMaterialId(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack and global_container_stack.material: return global_container_stack.material.getId() return ""
def _startPrint(self): if self._auto_print and not self._forced_queue: Application.getInstance().getController().setActiveStage("MonitorStage") # cancel any ongoing preheat timer before starting a print try: self._printers[0].stopPreheatTimers() except AttributeError: # stopPreheatTimers was added after Cura 3.3 beta pass self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to OctoPrint"), 0, False, -1) self._progress_message.addAction("Cancel", i18n_catalog.i18nc("@action:button", "Cancel"), None, "") self._progress_message.actionTriggered.connect(self._cancelSendGcode) self._progress_message.show() ## Mash the data into single string single_string_file_data = "" last_process_events = time() for line in self._gcode: single_string_file_data += line if time() > last_process_events + 0.05: # Ensure that the GUI keeps updated at least 20 times per second. QCoreApplication.processEvents() last_process_events = time() job_name = Application.getInstance().getPrintInformation().jobName.strip() if job_name is "": job_name = "untitled_print" file_name = "%s.gcode" % job_name ## Create multi_part request self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) ## Create parts (to be placed inside multipart) post_part = QHttpPart() post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"select\"") post_part.setBody(b"true") self._post_multi_part.append(post_part) if self._auto_print and not self._forced_queue: post_part = QHttpPart() post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"print\"") post_part.setBody(b"true") self._post_multi_part.append(post_part) post_part = QHttpPart() post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\"; filename=\"%s\"" % file_name) post_part.setBody(single_string_file_data.encode()) self._post_multi_part.append(post_part) destination = "local" if self._sd_supported and parseBool(Application.getInstance().getGlobalContainerStack().getMetaDataEntry("octoprint_store_sd", False)): destination = "sdcard" try: ## Post request + data post_request = self._createApiRequest("files/" + destination) self._post_reply = self._manager.post(post_request, self._post_multi_part) self._post_reply.uploadProgress.connect(self._onUploadProgress) except IOError: self._progress_message.hide() self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to send data to OctoPrint.")) self._error_message.show() except Exception as e: self._progress_message.hide() Logger.log("e", "An exception occurred in network connection: %s" % str(e)) self._gcode = None
def importProfile(self, file_name): Logger.log("d", "Attempting to import profile %s", file_name) if not file_name: return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path")} plugin_registry = PluginRegistry.getInstance() extension = file_name.split(".")[-1] global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack: return machine_extruders = [] for position in sorted(global_stack.extruders): machine_extruders.append(global_stack.extruders[position]) for plugin_id, meta_data in self._getIOPlugins("profile_reader"): if meta_data["profile_reader"][0]["extension"] != extension: continue profile_reader = plugin_registry.getPluginObject(plugin_id) try: profile_or_list = profile_reader.read(file_name) # Try to open the file with the profile reader. except NoProfileException: return { "status": "ok", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "No custom profile to import in file <filename>{0}</filename>", file_name)} except Exception as e: # Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None. Logger.log("e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name, profile_reader.getPluginId(), str(e)) return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Failed to import profile from <filename>{0}</filename>:", file_name) + "\n<message>" + str(e) + "</message>"} if profile_or_list: # Ensure it is always a list of profiles if not isinstance(profile_or_list, list): profile_or_list = [profile_or_list] # First check if this profile is suitable for this machine global_profile = None extruder_profiles = [] if len(profile_or_list) == 1: global_profile = profile_or_list[0] else: for profile in profile_or_list: if not profile.getMetaDataEntry("position"): global_profile = profile else: extruder_profiles.append(profile) extruder_profiles = sorted(extruder_profiles, key = lambda x: int(x.getMetaDataEntry("position"))) profile_or_list = [global_profile] + extruder_profiles if not global_profile: Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name) return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)} profile_definition = global_profile.getMetaDataEntry("definition") # Make sure we have a profile_definition in the file: if profile_definition is None: break machine_definition = self.findDefinitionContainers(id = profile_definition) if not machine_definition: Logger.log("e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition) return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name) } machine_definition = machine_definition[0] # Get the expected machine definition. # i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode... profile_definition = getMachineDefinitionIDForQualitySearch(machine_definition) expected_machine_definition = getMachineDefinitionIDForQualitySearch(global_stack.definition) # And check if the profile_definition matches either one (showing error if not): if profile_definition != expected_machine_definition: Logger.log("e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition) return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "The machine defined in profile <filename>{0}</filename> ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)} # Fix the global quality profile's definition field in case it's not correct global_profile.setMetaDataEntry("definition", expected_machine_definition) quality_name = global_profile.getName() quality_type = global_profile.getMetaDataEntry("quality_type") name_seed = os.path.splitext(os.path.basename(file_name))[0] new_name = self.uniqueName(name_seed) # Ensure it is always a list of profiles if type(profile_or_list) is not list: profile_or_list = [profile_or_list] # Make sure that there are also extruder stacks' quality_changes, not just one for the global stack if len(profile_or_list) == 1: global_profile = profile_or_list[0] extruder_profiles = [] for idx, extruder in enumerate(global_stack.extruders.values()): profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1)) profile = InstanceContainer(profile_id) profile.setName(quality_name) profile.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.SettingVersion) profile.setMetaDataEntry("type", "quality_changes") profile.setMetaDataEntry("definition", expected_machine_definition) profile.setMetaDataEntry("quality_type", quality_type) profile.setMetaDataEntry("position", "0") profile.setDirty(True) if idx == 0: # move all per-extruder settings to the first extruder's quality_changes for qc_setting_key in global_profile.getAllKeys(): settable_per_extruder = global_stack.getProperty(qc_setting_key, "settable_per_extruder") if settable_per_extruder: setting_value = global_profile.getProperty(qc_setting_key, "value") setting_definition = global_stack.getSettingDefinition(qc_setting_key) new_instance = SettingInstance(setting_definition, profile) new_instance.setProperty("value", setting_value) new_instance.resetState() # Ensure that the state is not seen as a user state. profile.addInstance(new_instance) profile.setDirty(True) global_profile.removeInstance(qc_setting_key, postpone_emit=True) extruder_profiles.append(profile) for profile in extruder_profiles: profile_or_list.append(profile) # Import all profiles for profile_index, profile in enumerate(profile_or_list): if profile_index == 0: # This is assumed to be the global profile profile_id = (global_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_") elif profile_index < len(machine_extruders) + 1: # This is assumed to be an extruder profile extruder_id = machine_extruders[profile_index - 1].definition.getId() extruder_position = str(profile_index - 1) if not profile.getMetaDataEntry("position"): profile.setMetaDataEntry("position", extruder_position) else: profile.setMetaDataEntry("position", extruder_position) profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_") else: #More extruders in the imported file than in the machine. continue #Delete the additional profiles. result = self._configureProfile(profile, profile_id, new_name, expected_machine_definition) if result is not None: return {"status": "error", "message": catalog.i18nc( "@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>:", file_name) + " <message>" + result + "</message>"} return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())} # This message is throw when the profile reader doesn't find any profile in the file return {"status": "error", "message": catalog.i18nc("@info:status", "File {0} does not contain any valid profile.", file_name)} # If it hasn't returned by now, none of the plugins loaded the profile successfully. return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name)}
def __init__(self, key, address: str, port, properties, parent = None): super().__init__(device_id = key, address = address, properties = properties, parent = parent) self._address = address self._port = port self._path = properties.get(b"path", b"/").decode("utf-8") if self._path[-1:] != "/": self._path += "/" self._key = key self._properties = properties # Properties dict as provided by zero conf self._gcode = None self._auto_print = True self._forced_queue = False # We start with a single extruder, but update this when we get data from octoprint self._number_of_extruders_set = False self._number_of_extruders = 1 # Try to get version information from plugin.json plugin_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "plugin.json") try: with open(plugin_file_path) as plugin_file: plugin_info = json.load(plugin_file) plugin_version = plugin_info["version"] except: # The actual version info is not critical to have so we can continue plugin_version = "Unknown" Logger.logException("w", "Could not get version information for the plugin") self._user_agent_header = "User-Agent".encode() self._user_agent = ("%s/%s %s/%s" % ( Application.getInstance().getApplicationName(), Application.getInstance().getVersion(), "OctoPrintPlugin", Application.getInstance().getVersion() )).encode() self._api_prefix = "api/" self._api_header = "X-Api-Key".encode() self._api_key = None self._protocol = "https" if properties.get(b'useHttps') == b"true" else "http" self._base_url = "%s://%s:%d%s" % (self._protocol, self._address, self._port, self._path) self._api_url = self._base_url + self._api_prefix self._basic_auth_header = "Authorization".encode() self._basic_auth_data = None basic_auth_username = properties.get(b"userName", b"").decode("utf-8") basic_auth_password = properties.get(b"password", b"").decode("utf-8") if basic_auth_username and basic_auth_password: data = base64.b64encode(("%s:%s" % (basic_auth_username, basic_auth_password)).encode()).decode("utf-8") self._basic_auth_data = ("basic %s" % data).encode() self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml") self.setPriority(2) # Make sure the output device gets selected above local file output self.setName(key) self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print with OctoPrint")) self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print with OctoPrint")) self.setIconName("print") self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected to OctoPrint on {0}").format(self._key)) # QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly # hook itself into the event loop, which results in events never being fired / done. self._manager = QNetworkAccessManager() self._manager.finished.connect(self._onRequestFinished) ## Ensure that the qt networking stuff isn't garbage collected (unless we want it to) self._settings_reply = None self._printer_reply = None self._job_reply = None self._command_reply = None self._post_reply = None self._post_multi_part = None self._progress_message = None self._error_message = None self._connection_message = None self._queued_gcode_commands = [] self._queued_gcode_timer = QTimer() self._queued_gcode_timer.setInterval(0) self._queued_gcode_timer.setSingleShot(True) self._queued_gcode_timer.timeout.connect(self._sendQueuedGcode) self._update_timer = QTimer() self._update_timer.setInterval(2000) # TODO; Add preference for update interval self._update_timer.setSingleShot(False) self._update_timer.timeout.connect(self._update) self._camera_mirror = "" self._camera_rotation = 0 self._camera_url = "" self._camera_shares_proxy = False self._sd_supported = False self._connection_state_before_timeout = None self._last_response_time = None self._last_request_time = None self._response_timeout_time = 5 self._recreate_network_manager_time = 30 # If we have no connection, re-create network manager every 30 sec. self._recreate_network_manager_count = 1 self._output_controller = GenericOutputController(self)
def importProfile(self, file_name): Logger.log("d", "Attempting to import profile %s", file_name) if not file_name: return { "status": "error", "message": catalog.i18nc( "@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path") } plugin_registry = PluginRegistry.getInstance() extension = file_name.split(".")[-1] global_container_stack = Application.getInstance( ).getGlobalContainerStack() if not global_container_stack: return machine_extruders = list( ExtruderManager.getInstance().getMachineExtruders( global_container_stack.getId())) machine_extruders.sort(key=lambda k: k.getMetaDataEntry("position")) for plugin_id, meta_data in self._getIOPlugins("profile_reader"): if meta_data["profile_reader"][0]["extension"] != extension: continue profile_reader = plugin_registry.getPluginObject(plugin_id) try: profile_or_list = profile_reader.read( file_name) # Try to open the file with the profile reader. except Exception as e: # Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None. Logger.log( "e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name, profile_reader.getPluginId(), str(e)) return { "status": "error", "message": catalog.i18nc( "@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)) } if profile_or_list: # Success! name_seed = os.path.splitext(os.path.basename(file_name))[0] new_name = self.uniqueName(name_seed) if type(profile_or_list) is not list: profile = profile_or_list self._configureProfile(profile, name_seed, new_name) return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile.getName()) } else: profile_index = -1 global_profile = None for profile in profile_or_list: if profile_index >= 0: if len(machine_extruders) > profile_index: extruder_id = Application.getInstance( ).getMachineManager().getQualityDefinitionId( machine_extruders[profile_index].getBottom( )) # Ensure the extruder profiles get non-conflicting names # NB: these are not user-facing if "extruder" in profile.getMetaData(): profile.setMetaDataEntry( "extruder", extruder_id) else: profile.addMetaDataEntry( "extruder", extruder_id) profile_id = (extruder_id + "_" + name_seed).lower().replace( " ", "_") elif profile_index == 0: # Importing a multiextrusion profile into a single extrusion machine; merge 1st extruder profile into global profile profile._id = self.uniqueName( "temporary_profile") self.addContainer(profile) ContainerManager.getInstance().mergeContainers( global_profile.getId(), profile.getId()) self.removeContainer(profile.getId()) break else: # The imported composite profile has a profile for an extruder that this machine does not have. Ignore this extruder-profile break else: global_profile = profile profile_id = ( global_container_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_") self._configureProfile(profile, profile_id, new_name) profile_index += 1 return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName()) } # If it hasn't returned by now, none of the plugins loaded the profile successfully. return { "status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type.", file_name) }