mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-09 23:05:01 -06:00
Merge branch 'master' into mypy_fixes
Contributes to CURA-5330
This commit is contained in:
commit
e5e96bc600
98 changed files with 19713 additions and 16495 deletions
|
@ -3,6 +3,7 @@
|
|||
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.Polygon import Polygon
|
||||
from UM.Math.Vector import Vector
|
||||
from cura.Arranging.ShapeArray import ShapeArray
|
||||
from cura.Scene import ZOffsetDecorator
|
||||
|
@ -45,7 +46,7 @@ class Arrange:
|
|||
# \param scene_root Root for finding all scene nodes
|
||||
# \param fixed_nodes Scene nodes to be placed
|
||||
@classmethod
|
||||
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250):
|
||||
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250, min_offset = 8):
|
||||
arranger = Arrange(x, y, x // 2, y // 2, scale = scale)
|
||||
arranger.centerFirst()
|
||||
|
||||
|
@ -58,9 +59,10 @@ class Arrange:
|
|||
|
||||
# Place all objects fixed nodes
|
||||
for fixed_node in fixed_nodes:
|
||||
vertices = fixed_node.callDecoration("getConvexHull")
|
||||
vertices = fixed_node.callDecoration("getConvexHullHead") or fixed_node.callDecoration("getConvexHull")
|
||||
if not vertices:
|
||||
continue
|
||||
vertices = vertices.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
|
||||
points = copy.deepcopy(vertices._points)
|
||||
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
|
||||
arranger.place(0, 0, shape_arr)
|
||||
|
@ -81,12 +83,12 @@ class Arrange:
|
|||
## Find placement for a node (using offset shape) and place it (using hull shape)
|
||||
# return the nodes that should be placed
|
||||
# \param node
|
||||
# \param offset_shape_arr ShapeArray with offset, used to find location
|
||||
# \param hull_shape_arr ShapeArray without offset, for placing the shape
|
||||
# \param offset_shape_arr ShapeArray with offset, for placing the shape
|
||||
# \param hull_shape_arr ShapeArray without offset, used to find location
|
||||
def findNodePlacement(self, node, offset_shape_arr, hull_shape_arr, step = 1):
|
||||
new_node = copy.deepcopy(node)
|
||||
best_spot = self.bestSpot(
|
||||
offset_shape_arr, start_prio = self._last_priority, step = step)
|
||||
hull_shape_arr, start_prio = self._last_priority, step = step)
|
||||
x, y = best_spot.x, best_spot.y
|
||||
|
||||
# Save the last priority.
|
||||
|
@ -102,7 +104,7 @@ class Arrange:
|
|||
if x is not None: # We could find a place
|
||||
new_node.setPosition(Vector(x, center_y, y))
|
||||
found_spot = True
|
||||
self.place(x, y, hull_shape_arr) # place the object in arranger
|
||||
self.place(x, y, offset_shape_arr) # place the object in arranger
|
||||
else:
|
||||
Logger.log("d", "Could not find spot!"),
|
||||
found_spot = False
|
||||
|
|
|
@ -110,7 +110,7 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
|
|||
arrange_array.add()
|
||||
arranger = arrange_array.get(current_build_plate_number)
|
||||
|
||||
best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority)
|
||||
best_spot = arranger.bestSpot(hull_shape_arr, start_prio=start_priority)
|
||||
x, y = best_spot.x, best_spot.y
|
||||
node.removeDecorator(ZOffsetDecorator)
|
||||
if node.getBoundingBox():
|
||||
|
@ -118,7 +118,7 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
|
|||
else:
|
||||
center_y = 0
|
||||
if x is not None: # We could find a place
|
||||
arranger.place(x, y, hull_shape_arr) # place the object in the arranger
|
||||
arranger.place(x, y, offset_shape_arr) # place the object in the arranger
|
||||
|
||||
node.callDecoration("setBuildPlateNumber", current_build_plate_number)
|
||||
grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True))
|
||||
|
|
|
@ -37,7 +37,7 @@ class ArrangeObjectsJob(Job):
|
|||
machine_width = global_container_stack.getProperty("machine_width", "value")
|
||||
machine_depth = global_container_stack.getProperty("machine_depth", "value")
|
||||
|
||||
arranger = Arrange.create(x = machine_width, y = machine_depth, fixed_nodes = self._fixed_nodes)
|
||||
arranger = Arrange.create(x = machine_width, y = machine_depth, fixed_nodes = self._fixed_nodes, min_offset = self._min_offset)
|
||||
|
||||
# Collect nodes to be placed
|
||||
nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr)
|
||||
|
@ -66,7 +66,7 @@ class ArrangeObjectsJob(Job):
|
|||
start_priority = last_priority
|
||||
else:
|
||||
start_priority = 0
|
||||
best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority)
|
||||
best_spot = arranger.bestSpot(hull_shape_arr, start_prio = start_priority)
|
||||
x, y = best_spot.x, best_spot.y
|
||||
node.removeDecorator(ZOffsetDecorator)
|
||||
if node.getBoundingBox():
|
||||
|
@ -77,7 +77,7 @@ class ArrangeObjectsJob(Job):
|
|||
last_size = size
|
||||
last_priority = best_spot.priority
|
||||
|
||||
arranger.place(x, y, hull_shape_arr) # take place before the next one
|
||||
arranger.place(x, y, offset_shape_arr) # take place before the next one
|
||||
grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True))
|
||||
else:
|
||||
Logger.log("d", "Arrange all: could not find spot!")
|
||||
|
|
|
@ -199,7 +199,6 @@ class CuraApplication(QtApplication):
|
|||
self._platform_activity = False
|
||||
self._scene_bounding_box = AxisAlignedBox.Null
|
||||
|
||||
self._job_name = None
|
||||
self._center_after_select = False
|
||||
self._camera_animation = None
|
||||
self._cura_actions = None
|
||||
|
@ -261,20 +260,16 @@ class CuraApplication(QtApplication):
|
|||
self._files_to_open.append(os.path.abspath(filename))
|
||||
|
||||
def initialize(self) -> None:
|
||||
self.__addExpectedResourceDirsAndSearchPaths() # Must be added before init of super
|
||||
|
||||
super().initialize()
|
||||
|
||||
self.__sendCommandToSingleInstance()
|
||||
self.__addExpectedResourceDirsAndSearchPaths()
|
||||
self.__initializeSettingDefinitionsAndFunctions()
|
||||
self.__addAllResourcesAndContainerResources()
|
||||
self.__addAllEmptyContainers()
|
||||
self.__setLatestResouceVersionsForVersionUpgrade()
|
||||
|
||||
# Initialize the package manager to remove and install scheduled packages.
|
||||
from cura.CuraPackageManager import CuraPackageManager
|
||||
self._cura_package_manager = CuraPackageManager(self)
|
||||
self._cura_package_manager.initialize()
|
||||
|
||||
self._machine_action_manager = MachineActionManager.MachineActionManager(self)
|
||||
self._machine_action_manager.initialize()
|
||||
|
||||
|
@ -325,6 +320,7 @@ class CuraApplication(QtApplication):
|
|||
SettingFunction.registerOperator("extruderValues", ExtruderManager.getExtruderValues)
|
||||
SettingFunction.registerOperator("extruderValue", ExtruderManager.getExtruderValue)
|
||||
SettingFunction.registerOperator("resolveOrValue", ExtruderManager.getResolveOrValue)
|
||||
SettingFunction.registerOperator("defaultExtruderPosition", ExtruderManager.getDefaultExtruderPosition)
|
||||
|
||||
# Adds all resources and container related resources.
|
||||
def __addAllResourcesAndContainerResources(self) -> None:
|
||||
|
@ -408,6 +404,43 @@ class CuraApplication(QtApplication):
|
|||
}
|
||||
)
|
||||
|
||||
"""
|
||||
self._currently_loading_files = []
|
||||
self._non_sliceable_extensions = []
|
||||
|
||||
self._machine_action_manager = MachineActionManager.MachineActionManager()
|
||||
self._machine_manager = None # This is initialized on demand.
|
||||
self._extruder_manager = None
|
||||
self._material_manager = None
|
||||
self._quality_manager = None
|
||||
self._object_manager = None
|
||||
self._build_plate_model = None
|
||||
self._multi_build_plate_model = None
|
||||
self._setting_visibility_presets_model = None
|
||||
self._setting_inheritance_manager = None
|
||||
self._simple_mode_settings_manager = None
|
||||
self._cura_scene_controller = None
|
||||
self._machine_error_checker = None
|
||||
self._auto_save = None
|
||||
self._save_data_enabled = True
|
||||
|
||||
self._additional_components = {} # Components to add to certain areas in the interface
|
||||
|
||||
super().__init__(name = "cura",
|
||||
version = CuraVersion,
|
||||
buildtype = CuraBuildType,
|
||||
is_debug_mode = CuraDebugMode,
|
||||
tray_icon_name = "cura-icon-32.png",
|
||||
**kwargs)
|
||||
|
||||
# FOR TESTING ONLY
|
||||
if kwargs["parsed_command_line"].get("trigger_early_crash", False):
|
||||
assert not "This crash is triggered by the trigger_early_crash command line argument."
|
||||
|
||||
self._variant_manager = None
|
||||
|
||||
self.default_theme = "cura-light"
|
||||
"""
|
||||
# Runs preparations that needs to be done before the starting process.
|
||||
def startSplashWindowPhase(self):
|
||||
super().startSplashWindowPhase()
|
||||
|
@ -788,10 +821,6 @@ class CuraApplication(QtApplication):
|
|||
self._extruder_manager = ExtruderManager()
|
||||
return self._extruder_manager
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getCuraPackageManager(self, *args):
|
||||
return self._cura_package_manager
|
||||
|
||||
def getVariantManager(self, *args):
|
||||
return self._variant_manager
|
||||
|
||||
|
|
|
@ -1,363 +1,14 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional, Dict, Any
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
import tempfile
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject, pyqtSignal, QUrl
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Resources import Resources
|
||||
from UM.Version import Version
|
||||
from cura.CuraApplication import CuraApplication #To find some resource types.
|
||||
from UM.PackageManager import PackageManager #The class we're extending.
|
||||
from UM.Resources import Resources #To find storage paths for some resource types.
|
||||
|
||||
|
||||
class CuraPackageManager(QObject):
|
||||
Version = 1
|
||||
|
||||
class CuraPackageManager(PackageManager):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._application = Application.getInstance()
|
||||
self._container_registry = self._application.getContainerRegistry()
|
||||
self._plugin_registry = self._application.getPluginRegistry()
|
||||
|
||||
#JSON files that keep track of all installed packages.
|
||||
self._user_package_management_file_path = None #type: str
|
||||
self._bundled_package_management_file_path = None #type: str
|
||||
for search_path in Resources.getSearchPaths():
|
||||
candidate_bundled_path = os.path.join(search_path, "bundled_packages.json")
|
||||
if os.path.exists(candidate_bundled_path):
|
||||
self._bundled_package_management_file_path = candidate_bundled_path
|
||||
for search_path in (Resources.getDataStoragePath(), Resources.getConfigStoragePath()):
|
||||
candidate_user_path = os.path.join(search_path, "packages.json")
|
||||
if os.path.exists(candidate_user_path):
|
||||
self._user_package_management_file_path = candidate_user_path
|
||||
if self._user_package_management_file_path is None: #Doesn't exist yet.
|
||||
self._user_package_management_file_path = os.path.join(Resources.getDataStoragePath(), "packages.json")
|
||||
|
||||
self._bundled_package_dict = {} # A dict of all bundled packages
|
||||
self._installed_package_dict = {} # A dict of all installed packages
|
||||
self._to_remove_package_set = set() # A set of packages that need to be removed at the next start
|
||||
self._to_install_package_dict = {} # A dict of packages that need to be installed at the next start
|
||||
|
||||
installedPackagesChanged = pyqtSignal() # Emitted whenever the installed packages collection have been changed.
|
||||
|
||||
def initialize(self):
|
||||
self._loadManagementData()
|
||||
self._removeAllScheduledPackages()
|
||||
self._installAllScheduledPackages()
|
||||
|
||||
# (for initialize) Loads the package management file if exists
|
||||
def _loadManagementData(self) -> None:
|
||||
# The bundles package management file should always be there
|
||||
if not os.path.exists(self._bundled_package_management_file_path):
|
||||
Logger.log("w", "Bundled package management file could not be found!")
|
||||
return
|
||||
# Load the bundled packages:
|
||||
with open(self._bundled_package_management_file_path, "r", encoding = "utf-8") as f:
|
||||
self._bundled_package_dict = json.load(f, encoding = "utf-8")
|
||||
Logger.log("i", "Loaded bundled packages data from %s", self._bundled_package_management_file_path)
|
||||
|
||||
# Load the user package management file
|
||||
if not os.path.exists(self._user_package_management_file_path):
|
||||
Logger.log("i", "User package management file %s doesn't exist, do nothing", self._user_package_management_file_path)
|
||||
return
|
||||
|
||||
# Need to use the file lock here to prevent concurrent I/O from other processes/threads
|
||||
container_registry = self._application.getContainerRegistry()
|
||||
with container_registry.lockFile():
|
||||
|
||||
# Load the user packages:
|
||||
with open(self._user_package_management_file_path, "r", encoding="utf-8") as f:
|
||||
management_dict = json.load(f, encoding="utf-8")
|
||||
self._installed_package_dict = management_dict.get("installed", {})
|
||||
self._to_remove_package_set = set(management_dict.get("to_remove", []))
|
||||
self._to_install_package_dict = management_dict.get("to_install", {})
|
||||
Logger.log("i", "Loaded user packages management file from %s", self._user_package_management_file_path)
|
||||
|
||||
def _saveManagementData(self) -> None:
|
||||
# Need to use the file lock here to prevent concurrent I/O from other processes/threads
|
||||
container_registry = self._application.getContainerRegistry()
|
||||
with container_registry.lockFile():
|
||||
with open(self._user_package_management_file_path, "w", encoding = "utf-8") as f:
|
||||
data_dict = {"version": CuraPackageManager.Version,
|
||||
"installed": self._installed_package_dict,
|
||||
"to_remove": list(self._to_remove_package_set),
|
||||
"to_install": self._to_install_package_dict}
|
||||
json.dump(data_dict, f, sort_keys = True, indent = 4)
|
||||
Logger.log("i", "Package management file %s was saved", self._user_package_management_file_path)
|
||||
|
||||
# (for initialize) Removes all packages that have been scheduled to be removed.
|
||||
def _removeAllScheduledPackages(self) -> None:
|
||||
for package_id in self._to_remove_package_set:
|
||||
self._purgePackage(package_id)
|
||||
del self._installed_package_dict[package_id]
|
||||
self._to_remove_package_set.clear()
|
||||
self._saveManagementData()
|
||||
|
||||
# (for initialize) Installs all packages that have been scheduled to be installed.
|
||||
def _installAllScheduledPackages(self) -> None:
|
||||
while self._to_install_package_dict:
|
||||
package_id, package_info = list(self._to_install_package_dict.items())[0]
|
||||
self._installPackage(package_info)
|
||||
self._installed_package_dict[package_id] = self._to_install_package_dict[package_id]
|
||||
del self._to_install_package_dict[package_id]
|
||||
self._saveManagementData()
|
||||
|
||||
def getBundledPackageInfo(self, package_id: str) -> Optional[dict]:
|
||||
package_info = None
|
||||
if package_id in self._bundled_package_dict:
|
||||
package_info = self._bundled_package_dict[package_id]["package_info"]
|
||||
return package_info
|
||||
|
||||
# Checks the given package is installed. If so, return a dictionary that contains the package's information.
|
||||
def getInstalledPackageInfo(self, package_id: str) -> Optional[dict]:
|
||||
if package_id in self._to_remove_package_set:
|
||||
return None
|
||||
|
||||
if package_id in self._to_install_package_dict:
|
||||
package_info = self._to_install_package_dict[package_id]["package_info"]
|
||||
return package_info
|
||||
|
||||
if package_id in self._installed_package_dict:
|
||||
package_info = self._installed_package_dict[package_id]["package_info"]
|
||||
return package_info
|
||||
|
||||
if package_id in self._bundled_package_dict:
|
||||
package_info = self._bundled_package_dict[package_id]["package_info"]
|
||||
return package_info
|
||||
|
||||
return None
|
||||
|
||||
def getAllInstalledPackagesInfo(self) -> dict:
|
||||
# Add bundled, installed, and to-install packages to the set of installed package IDs
|
||||
all_installed_ids = set()
|
||||
|
||||
if self._bundled_package_dict.keys():
|
||||
all_installed_ids = all_installed_ids.union(set(self._bundled_package_dict.keys()))
|
||||
if self._installed_package_dict.keys():
|
||||
all_installed_ids = all_installed_ids.union(set(self._installed_package_dict.keys()))
|
||||
all_installed_ids = all_installed_ids.difference(self._to_remove_package_set)
|
||||
# If it's going to be installed and to be removed, then the package is being updated and it should be listed.
|
||||
if self._to_install_package_dict.keys():
|
||||
all_installed_ids = all_installed_ids.union(set(self._to_install_package_dict.keys()))
|
||||
|
||||
# map of <package_type> -> <package_id> -> <package_info>
|
||||
installed_packages_dict = {}
|
||||
for package_id in all_installed_ids:
|
||||
# Skip required plugins as they should not be tampered with
|
||||
if package_id in Application.getInstance().getRequiredPlugins():
|
||||
continue
|
||||
|
||||
package_info = None
|
||||
# Add bundled plugins
|
||||
if package_id in self._bundled_package_dict:
|
||||
package_info = self._bundled_package_dict[package_id]["package_info"]
|
||||
|
||||
# Add installed plugins
|
||||
if package_id in self._installed_package_dict:
|
||||
package_info = self._installed_package_dict[package_id]["package_info"]
|
||||
|
||||
# Add to install plugins
|
||||
if package_id in self._to_install_package_dict:
|
||||
package_info = self._to_install_package_dict[package_id]["package_info"]
|
||||
|
||||
if package_info is None:
|
||||
continue
|
||||
|
||||
# We also need to get information from the plugin registry such as if a plugin is active
|
||||
package_info["is_active"] = self._plugin_registry.isActivePlugin(package_id)
|
||||
|
||||
# If the package ID is in bundled, label it as such
|
||||
package_info["is_bundled"] = package_info["package_id"] in self._bundled_package_dict.keys() and not self.isUserInstalledPackage(package_info["package_id"])
|
||||
|
||||
# If there is not a section in the dict for this type, add it
|
||||
if package_info["package_type"] not in installed_packages_dict:
|
||||
installed_packages_dict[package_info["package_type"]] = []
|
||||
|
||||
# Finally, add the data
|
||||
installed_packages_dict[package_info["package_type"]].append(package_info)
|
||||
|
||||
return installed_packages_dict
|
||||
|
||||
# Checks if the given package is installed (at all).
|
||||
def isPackageInstalled(self, package_id: str) -> bool:
|
||||
return self.getInstalledPackageInfo(package_id) is not None
|
||||
|
||||
# This is called by drag-and-dropping curapackage files.
|
||||
@pyqtSlot(QUrl)
|
||||
def installPackageViaDragAndDrop(self, file_url: str) -> None:
|
||||
filename = QUrl(file_url).toLocalFile()
|
||||
return self.installPackage(filename)
|
||||
|
||||
# Schedules the given package file to be installed upon the next start.
|
||||
@pyqtSlot(str)
|
||||
def installPackage(self, filename: str) -> None:
|
||||
has_changes = False
|
||||
try:
|
||||
# Get package information
|
||||
package_info = self.getPackageInfo(filename)
|
||||
if not package_info:
|
||||
return
|
||||
package_id = package_info["package_id"]
|
||||
|
||||
# Check if it is installed
|
||||
installed_package_info = self.getInstalledPackageInfo(package_info["package_id"])
|
||||
to_install_package = installed_package_info is None # Install if the package has not been installed
|
||||
if installed_package_info is not None:
|
||||
# Compare versions and only schedule the installation if the given package is newer
|
||||
new_version = package_info["package_version"]
|
||||
installed_version = installed_package_info["package_version"]
|
||||
if Version(new_version) > Version(installed_version):
|
||||
Logger.log("i", "Package [%s] version [%s] is newer than the installed version [%s], update it.",
|
||||
package_id, new_version, installed_version)
|
||||
to_install_package = True
|
||||
|
||||
if to_install_package:
|
||||
# Need to use the lock file to prevent concurrent I/O issues.
|
||||
with self._container_registry.lockFile():
|
||||
Logger.log("i", "Package [%s] version [%s] is scheduled to be installed.",
|
||||
package_id, package_info["package_version"])
|
||||
# Copy the file to cache dir so we don't need to rely on the original file to be present
|
||||
package_cache_dir = os.path.join(os.path.abspath(Resources.getCacheStoragePath()), "cura_packages")
|
||||
if not os.path.exists(package_cache_dir):
|
||||
os.makedirs(package_cache_dir, exist_ok=True)
|
||||
|
||||
target_file_path = os.path.join(package_cache_dir, package_id + ".curapackage")
|
||||
shutil.copy2(filename, target_file_path)
|
||||
|
||||
self._to_install_package_dict[package_id] = {"package_info": package_info,
|
||||
"filename": target_file_path}
|
||||
has_changes = True
|
||||
except:
|
||||
Logger.logException("c", "Failed to install package file '%s'", filename)
|
||||
finally:
|
||||
self._saveManagementData()
|
||||
if has_changes:
|
||||
self.installedPackagesChanged.emit()
|
||||
|
||||
# Schedules the given package to be removed upon the next start.
|
||||
# \param package_id id of the package
|
||||
# \param force_add is used when updating. In that case you actually want to uninstall & install
|
||||
@pyqtSlot(str)
|
||||
def removePackage(self, package_id: str, force_add: bool = False) -> None:
|
||||
# Check the delayed installation and removal lists first
|
||||
if not self.isPackageInstalled(package_id):
|
||||
Logger.log("i", "Attempt to remove package [%s] that is not installed, do nothing.", package_id)
|
||||
return
|
||||
|
||||
# Extra safety check
|
||||
if package_id not in self._installed_package_dict and package_id in self._bundled_package_dict:
|
||||
Logger.log("i", "Not uninstalling [%s] because it is a bundled package.")
|
||||
return
|
||||
|
||||
if package_id not in self._to_install_package_dict or force_add:
|
||||
# Schedule for a delayed removal:
|
||||
self._to_remove_package_set.add(package_id)
|
||||
else:
|
||||
if package_id in self._to_install_package_dict:
|
||||
# Remove from the delayed installation list if present
|
||||
del self._to_install_package_dict[package_id]
|
||||
|
||||
self._saveManagementData()
|
||||
self.installedPackagesChanged.emit()
|
||||
|
||||
## Is the package an user installed package?
|
||||
def isUserInstalledPackage(self, package_id: str):
|
||||
return package_id in self._installed_package_dict
|
||||
|
||||
# Removes everything associated with the given package ID.
|
||||
def _purgePackage(self, package_id: str) -> None:
|
||||
# Iterate through all directories in the data storage directory and look for sub-directories that belong to
|
||||
# the package we need to remove, that is the sub-dirs with the package_id as names, and remove all those dirs.
|
||||
data_storage_dir = os.path.abspath(Resources.getDataStoragePath())
|
||||
|
||||
for root, dir_names, _ in os.walk(data_storage_dir):
|
||||
for dir_name in dir_names:
|
||||
package_dir = os.path.join(root, dir_name, package_id)
|
||||
if os.path.exists(package_dir):
|
||||
Logger.log("i", "Removing '%s' for package [%s]", package_dir, package_id)
|
||||
shutil.rmtree(package_dir)
|
||||
break
|
||||
|
||||
# Installs all files associated with the given package.
|
||||
def _installPackage(self, installation_package_data: dict):
|
||||
package_info = installation_package_data["package_info"]
|
||||
filename = installation_package_data["filename"]
|
||||
|
||||
package_id = package_info["package_id"]
|
||||
|
||||
if not os.path.exists(filename):
|
||||
Logger.log("w", "Package [%s] file '%s' is missing, cannot install this package", package_id, filename)
|
||||
return
|
||||
|
||||
Logger.log("i", "Installing package [%s] from file [%s]", package_id, filename)
|
||||
|
||||
# If it's installed, remove it first and then install
|
||||
if package_id in self._installed_package_dict:
|
||||
self._purgePackage(package_id)
|
||||
|
||||
# Install the package
|
||||
with zipfile.ZipFile(filename, "r") as archive:
|
||||
|
||||
temp_dir = tempfile.TemporaryDirectory()
|
||||
archive.extractall(temp_dir.name)
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
installation_dirs_dict = {
|
||||
"materials": Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer),
|
||||
"qualities": Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer),
|
||||
"plugins": os.path.abspath(Resources.getStoragePath(Resources.Plugins)),
|
||||
}
|
||||
|
||||
for sub_dir_name, installation_root_dir in installation_dirs_dict.items():
|
||||
src_dir_path = os.path.join(temp_dir.name, "files", sub_dir_name)
|
||||
dst_dir_path = os.path.join(installation_root_dir, package_id)
|
||||
|
||||
if not os.path.exists(src_dir_path):
|
||||
continue
|
||||
self.__installPackageFiles(package_id, src_dir_path, dst_dir_path)
|
||||
|
||||
# Remove the file
|
||||
os.remove(filename)
|
||||
|
||||
def __installPackageFiles(self, package_id: str, src_dir: str, dst_dir: str) -> None:
|
||||
Logger.log("i", "Moving package {package_id} from {src_dir} to {dst_dir}".format(package_id=package_id, src_dir=src_dir, dst_dir=dst_dir))
|
||||
shutil.move(src_dir, dst_dir)
|
||||
|
||||
# Gets package information from the given file.
|
||||
def getPackageInfo(self, filename: str) -> Dict[str, Any]:
|
||||
with zipfile.ZipFile(filename) as archive:
|
||||
try:
|
||||
# All information is in package.json
|
||||
with archive.open("package.json") as f:
|
||||
package_info_dict = json.loads(f.read().decode("utf-8"))
|
||||
return package_info_dict
|
||||
except Exception as e:
|
||||
Logger.logException("w", "Could not get package information from file '%s': %s" % (filename, e))
|
||||
return {}
|
||||
|
||||
# Gets the license file content if present in the given package file.
|
||||
# Returns None if there is no license file found.
|
||||
def getPackageLicense(self, filename: str) -> Optional[str]:
|
||||
license_string = None
|
||||
with zipfile.ZipFile(filename) as archive:
|
||||
# Go through all the files and use the first successful read as the result
|
||||
for file_info in archive.infolist():
|
||||
if file_info.filename.endswith("LICENSE"):
|
||||
Logger.log("d", "Found potential license file '%s'", file_info.filename)
|
||||
try:
|
||||
with archive.open(file_info.filename, "r") as f:
|
||||
data = f.read()
|
||||
license_string = data.decode("utf-8")
|
||||
break
|
||||
except:
|
||||
Logger.logException("e", "Failed to load potential license file '%s' as text file.",
|
||||
file_info.filename)
|
||||
license_string = None
|
||||
return license_string
|
||||
self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer)
|
||||
self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer)
|
||||
|
|
|
@ -47,22 +47,38 @@ class BrandMaterialsModel(ListModel):
|
|||
self.addRoleName(self.MaterialsRole, "materials")
|
||||
|
||||
self._extruder_position = 0
|
||||
self._extruder_stack = None
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
self._machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
|
||||
self._material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||
|
||||
self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
|
||||
self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines.
|
||||
self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
|
||||
self._update()
|
||||
|
||||
def _updateExtruderStack(self):
|
||||
global_stack = self._machine_manager.activeMachine
|
||||
if global_stack is None:
|
||||
return
|
||||
|
||||
if self._extruder_stack is not None:
|
||||
self._extruder_stack.pyqtContainersChanged.disconnect(self._update)
|
||||
self._extruder_stack = global_stack.extruders.get(str(self._extruder_position))
|
||||
if self._extruder_stack is not None:
|
||||
self._extruder_stack.pyqtContainersChanged.connect(self._update)
|
||||
# Force update the model when the extruder stack changes
|
||||
self._update()
|
||||
|
||||
def setExtruderPosition(self, position: int):
|
||||
if self._extruder_position != position:
|
||||
if self._extruder_stack is None or self._extruder_position != position:
|
||||
self._extruder_position = position
|
||||
self._updateExtruderStack()
|
||||
self.extruderPositionChanged.emit()
|
||||
|
||||
@pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged)
|
||||
@pyqtProperty(int, fset=setExtruderPosition, notify=extruderPositionChanged)
|
||||
def extruderPosition(self) -> int:
|
||||
return self._extruder_position
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ class MultiplyObjectsJob(Job):
|
|||
|
||||
root = scene.getRoot()
|
||||
scale = 0.5
|
||||
arranger = Arrange.create(x = machine_width, y = machine_depth, scene_root = root, scale = scale)
|
||||
arranger = Arrange.create(x = machine_width, y = machine_depth, scene_root = root, scale = scale, min_offset = self._min_offset)
|
||||
processed_nodes = []
|
||||
nodes = []
|
||||
|
||||
|
|
|
@ -299,7 +299,7 @@ class PrintInformation(QObject):
|
|||
|
||||
def _updateJobName(self):
|
||||
if self._base_name == "":
|
||||
self._job_name = ""
|
||||
self._job_name = "unnamed"
|
||||
self._is_user_specified_job_name = False
|
||||
self.jobNameChanged.emit()
|
||||
return
|
||||
|
@ -351,17 +351,17 @@ class PrintInformation(QObject):
|
|||
if is_gcode or is_project_file or (is_empty or (self._base_name == "" and self._base_name != check_name)):
|
||||
# Only take the file name part, Note : file name might have 'dot' in name as well
|
||||
|
||||
data = ''
|
||||
data = ""
|
||||
try:
|
||||
mime_type = MimeTypeDatabase.getMimeTypeForFile(name)
|
||||
data = mime_type.stripExtension(name)
|
||||
except:
|
||||
Logger.log("w", "Unsupported Mime Type Database file extension")
|
||||
Logger.log("w", "Unsupported Mime Type Database file extension %s", name)
|
||||
|
||||
if data is not None and check_name is not None:
|
||||
self._base_name = data
|
||||
else:
|
||||
self._base_name = ''
|
||||
self._base_name = ""
|
||||
|
||||
self._updateJobName()
|
||||
|
||||
|
|
|
@ -254,8 +254,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._last_manager_create_time = time()
|
||||
self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
|
||||
|
||||
machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||
machine_manager.checkCorrectGroupName(self.getId(), self.name)
|
||||
if self._properties.get(b"temporary", b"false") != b"true":
|
||||
Application.getInstance().getMachineManager().checkCorrectGroupName(self.getId(), self.name)
|
||||
|
||||
def _registerOnFinishedCallback(self, reply: QNetworkReply, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||
if on_finished is not None:
|
||||
|
|
|
@ -20,7 +20,7 @@ class CuraSceneNode(SceneNode):
|
|||
def __init__(self, parent: Optional["SceneNode"] = None, visible: bool = True, name: str = "", no_setting_override: bool = False) -> None:
|
||||
super().__init__(parent = parent, visible = visible, name = name)
|
||||
if not no_setting_override:
|
||||
self.addDecorator(SettingOverrideDecorator())
|
||||
self.addDecorator(SettingOverrideDecorator()) # now we always have a getActiveExtruderPosition, unless explicitly disabled
|
||||
self._outside_buildarea = False
|
||||
|
||||
def setOutsideBuildArea(self, new_value: bool) -> None:
|
||||
|
|
|
@ -475,7 +475,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
extruder_definition = extruder_definitions[0]
|
||||
unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) if create_new_ids else machine.getName() + " " + new_extruder_id
|
||||
|
||||
extruder_stack = ExtruderStack.ExtruderStack(unique_name, parent = machine)
|
||||
extruder_stack = ExtruderStack.ExtruderStack(unique_name)
|
||||
extruder_stack.setName(extruder_definition.getName())
|
||||
extruder_stack.setDefinition(extruder_definition)
|
||||
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
|
||||
|
|
|
@ -540,6 +540,11 @@ class ExtruderManager(QObject):
|
|||
|
||||
return result
|
||||
|
||||
## Return the default extruder position from the machine manager
|
||||
@staticmethod
|
||||
def getDefaultExtruderPosition() -> str:
|
||||
return Application.getInstance().getMachineManager().defaultExtruderPosition
|
||||
|
||||
## Get all extruder values for a certain setting.
|
||||
#
|
||||
# This is exposed to qml for display purposes
|
||||
|
|
|
@ -655,6 +655,15 @@ class MachineManager(QObject):
|
|||
|
||||
return ""
|
||||
|
||||
@pyqtProperty(str, notify = activeVariantChanged)
|
||||
def activeVariantId(self) -> str:
|
||||
if self._active_container_stack:
|
||||
variant = self._active_container_stack.variant
|
||||
if variant:
|
||||
return variant.getId()
|
||||
|
||||
return ""
|
||||
|
||||
@pyqtProperty(str, notify = activeVariantChanged)
|
||||
def activeVariantBuildplateName(self) -> str:
|
||||
if self._global_container_stack:
|
||||
|
@ -983,6 +992,14 @@ class MachineManager(QObject):
|
|||
container = extruder.userChanges
|
||||
container.setProperty(setting_name, property_name, property_value)
|
||||
|
||||
## Reset all setting properties of a setting for all extruders.
|
||||
# \param setting_name The ID of the setting to reset.
|
||||
@pyqtSlot(str)
|
||||
def resetSettingForAllExtruders(self, setting_name: str) -> None:
|
||||
for key, extruder in self._global_container_stack.extruders.items():
|
||||
container = extruder.userChanges
|
||||
container.removeInstance(setting_name)
|
||||
|
||||
@pyqtProperty("QVariantList", notify = globalContainerChanged)
|
||||
def currentExtruderPositions(self) -> List[str]:
|
||||
if self._global_container_stack is None:
|
||||
|
@ -1239,6 +1256,8 @@ class MachineManager(QObject):
|
|||
# If there is no machine, then create a new one and set it to the non-hidden instance
|
||||
if not new_machine:
|
||||
new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id)
|
||||
if not new_machine:
|
||||
return
|
||||
new_machine.addMetaDataEntry("um_network_key", self.activeMachineNetworkKey)
|
||||
new_machine.addMetaDataEntry("connect_group_name", self.activeMachineNetworkGroupName)
|
||||
new_machine.addMetaDataEntry("hidden", False)
|
||||
|
@ -1300,8 +1319,8 @@ class MachineManager(QObject):
|
|||
# Check if the connect_group_name is correct. If not, update all the containers connected to the same printer
|
||||
if self.activeMachineNetworkGroupName != group_name:
|
||||
metadata_filter = {"um_network_key": self.activeMachineNetworkKey}
|
||||
hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
||||
for container in hidden_containers:
|
||||
containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
||||
for container in containers:
|
||||
container.setMetaDataEntry("connect_group_name", group_name)
|
||||
|
||||
## This method checks if there is an instance connected to the given network_key
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue