diff --git a/UltiMaker-Cura.spec.jinja b/UltiMaker-Cura.spec.jinja index cd939cf736..99bfa80078 100644 --- a/UltiMaker-Cura.spec.jinja +++ b/UltiMaker-Cura.spec.jinja @@ -55,7 +55,8 @@ exe = EXE( target_arch={{ target_arch }}, codesign_identity=os.getenv('CODESIGN_IDENTITY', None), entitlements_file={{ entitlements_file }}, - icon={{ icon }} + icon={{ icon }}, + contents_directory='.' ) coll = COLLECT( @@ -70,188 +71,7 @@ coll = COLLECT( ) {% if macos == true %} -# PyInstaller seems to copy everything in the resource folder for the MacOS, this causes issues with codesigning and notarizing -# The folder structure should adhere to the one specified in Table 2-5 -# https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1 -# The class below is basically ducktyping the BUNDLE class of PyInstaller and using our own `assemble` method for more fine-grain and specific -# control. Some code of the method below is copied from: -# https://github.com/pyinstaller/pyinstaller/blob/22d1d2a5378228744cc95f14904dae1664df32c4/PyInstaller/building/osx.py#L115 -#----------------------------------------------------------------------------- -# Copyright (c) 2005-2022, PyInstaller Development Team. -# -# Distributed under the terms of the GNU General Public License (version 2 -# or later) with exception for distributing the bootloader. -# -# The full license is in the file COPYING.txt, distributed with this software. -# -# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception) -#----------------------------------------------------------------------------- - -import plistlib -import shutil -import PyInstaller.utils.osx as osxutils -from pathlib import Path -from PyInstaller.building.osx import BUNDLE -from PyInstaller.building.utils import (_check_path_overlap, _rmtree, add_suffix_to_extension, checkCache) -from PyInstaller.building.datastruct import logger -from PyInstaller.building.icon import normalize_icon_type - - -class UMBUNDLE(BUNDLE): - def assemble(self): - from PyInstaller.config import CONF - - if _check_path_overlap(self.name) and os.path.isdir(self.name): - _rmtree(self.name) - logger.info("Building BUNDLE %s", self.tocbasename) - - # Create a minimal Mac bundle structure. - macos_path = Path(self.name, "Contents", "MacOS") - resources_path = Path(self.name, "Contents", "Resources") - frameworks_path = Path(self.name, "Contents", "Frameworks") - os.makedirs(macos_path) - os.makedirs(resources_path) - os.makedirs(frameworks_path) - - # Makes sure the icon exists and attempts to convert to the proper format if applicable - self.icon = normalize_icon_type(self.icon, ("icns",), "icns", CONF["workpath"]) - - # Ensure icon path is absolute - self.icon = os.path.abspath(self.icon) - - # Copy icns icon to Resources directory. - shutil.copy(self.icon, os.path.join(self.name, 'Contents', 'Resources')) - - # Key/values for a minimal Info.plist file - info_plist_dict = { - "CFBundleDisplayName": self.appname, - "CFBundleName": self.appname, - - # Required by 'codesign' utility. - # The value for CFBundleIdentifier is used as the default unique name of your program for Code Signing - # purposes. It even identifies the APP for access to restricted OS X areas like Keychain. - # - # The identifier used for signing must be globally unique. The usual form for this identifier is a - # hierarchical name in reverse DNS notation, starting with the toplevel domain, followed by the company - # name, followed by the department within the company, and ending with the product name. Usually in the - # form: com.mycompany.department.appname - # CLI option --osx-bundle-identifier sets this value. - "CFBundleIdentifier": self.bundle_identifier, - "CFBundleExecutable": os.path.basename(self.exename), - "CFBundleIconFile": os.path.basename(self.icon), - "CFBundleInfoDictionaryVersion": "6.0", - "CFBundlePackageType": "APPL", - "CFBundleVersionString": self.version, - "CFBundleShortVersionString": self.version, - } - - # Set some default values. But they still can be overwritten by the user. - if self.console: - # Setting EXE console=True implies LSBackgroundOnly=True. - info_plist_dict['LSBackgroundOnly'] = True - else: - # Let's use high resolution by default. - info_plist_dict['NSHighResolutionCapable'] = True - - # Merge info_plist settings from spec file - if isinstance(self.info_plist, dict) and self.info_plist: - info_plist_dict.update(self.info_plist) - - plist_filename = os.path.join(self.name, "Contents", "Info.plist") - with open(plist_filename, "wb") as plist_fh: - plistlib.dump(info_plist_dict, plist_fh) - - links = [] - _QT_BASE_PATH = {'PySide2', 'PySide6', 'PyQt5', 'PyQt6', 'PySide6'} - for inm, fnm, typ in self.toc: - # Adjust name for extensions, if applicable - inm, fnm, typ = add_suffix_to_extension(inm, fnm, typ) - inm = Path(inm) - fnm = Path(fnm) - # Copy files from cache. This ensures that are used files with relative paths to dynamic library - # dependencies (@executable_path) - if typ in ('EXTENSION', 'BINARY') or (typ == 'DATA' and inm.suffix == '.so'): - if any(['.' in p for p in inm.parent.parts]): - inm = Path(inm.name) - fnm = Path(checkCache( - str(fnm), - strip = self.strip, - upx = self.upx, - upx_exclude = self.upx_exclude, - dist_nm = str(inm), - target_arch = self.target_arch, - codesign_identity = self.codesign_identity, - entitlements_file = self.entitlements_file, - strict_arch_validation = (typ == 'EXTENSION'), - )) - frame_dst = frameworks_path.joinpath(inm) - if not frame_dst.exists(): - if frame_dst.is_dir(): - os.makedirs(frame_dst, exist_ok = True) - else: - os.makedirs(frame_dst.parent, exist_ok = True) - shutil.copy(fnm, frame_dst, follow_symlinks = True) - macos_dst = macos_path.joinpath(inm) - if not macos_dst.exists(): - if macos_dst.is_dir(): - os.makedirs(macos_dst, exist_ok = True) - else: - os.makedirs(macos_dst.parent, exist_ok = True) - - # Create relative symlink to the framework - symlink_to = Path(*[".." for p in macos_dst.relative_to(macos_path).parts], "Frameworks").joinpath( - frame_dst.relative_to(frameworks_path)) - try: - macos_dst.symlink_to(symlink_to) - except FileExistsError: - pass - else: - if typ == 'DATA': - if any(['.' in p for p in inm.parent.parts]) or inm.suffix == '.so': - # Skip info dist egg and some not needed folders in tcl and tk, since they all contain dots in their files - logger.warning(f"Skipping DATA file {inm}") - continue - res_dst = resources_path.joinpath(inm) - if not res_dst.exists(): - if res_dst.is_dir(): - os.makedirs(res_dst, exist_ok = True) - else: - os.makedirs(res_dst.parent, exist_ok = True) - shutil.copy(fnm, res_dst, follow_symlinks = True) - macos_dst = macos_path.joinpath(inm) - if not macos_dst.exists(): - if macos_dst.is_dir(): - os.makedirs(macos_dst, exist_ok = True) - else: - os.makedirs(macos_dst.parent, exist_ok = True) - - # Create relative symlink to the resource - symlink_to = Path(*[".." for p in macos_dst.relative_to(macos_path).parts], "Resources").joinpath( - res_dst.relative_to(resources_path)) - try: - macos_dst.symlink_to(symlink_to) - except FileExistsError: - pass - else: - macos_dst = macos_path.joinpath(inm) - if not macos_dst.exists(): - if macos_dst.is_dir(): - os.makedirs(macos_dst, exist_ok = True) - else: - os.makedirs(macos_dst.parent, exist_ok = True) - shutil.copy(fnm, macos_dst, follow_symlinks = True) - - # Sign the bundle - logger.info('Signing the BUNDLE...') - try: - osxutils.sign_binary(self.name, self.codesign_identity, self.entitlements_file, deep = True) - except Exception as e: - logger.warning(f"Error while signing the bundle: {e}") - logger.warning("You will need to sign the bundle manually!") - - logger.info(f"Building BUNDLE {self.tocbasename} completed successfully.") - -app = UMBUNDLE( +app = BUNDLE( coll, name='{{ display_name }}.app', icon={{ icon }}, @@ -271,9 +91,10 @@ app = UMBUNDLE( 'CFBundleURLSchemes': ['cura', 'slicer'], }], 'CFBundleDocumentTypes': [{ - 'CFBundleTypeRole': 'Viewer', - 'CFBundleTypeExtensions': ['*'], - 'CFBundleTypeName': 'Model Files', - }] - }, -){% endif %} + 'CFBundleTypeRole': 'Viewer', + 'CFBundleTypeExtensions': ['*'], + 'CFBundleTypeName': 'Model Files', + }] + }, +) +{% endif %} diff --git a/conandata.yml b/conandata.yml index 486a64c26e..2485dd8bd0 100644 --- a/conandata.yml +++ b/conandata.yml @@ -118,7 +118,6 @@ pyinstaller: - "sqlite3" - "trimesh" - "win32ctypes" - - "PyQt6" - "PyQt6.QtNetwork" - "PyQt6.sip" - "stl" diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index bae609c07b..8a87ba448c 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -104,7 +104,8 @@ from cura.Settings.SettingInheritanceManager import SettingInheritanceManager from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager -from cura.UI import CuraSplashScreen, MachineActionManager, PrintInformation +from cura.UI import CuraSplashScreen, PrintInformation +from cura.UI.MachineActionManager import MachineActionManager from cura.UI.AddPrinterPagesModel import AddPrinterPagesModel from cura.UI.MachineSettingsManager import MachineSettingsManager from cura.UI.ObjectsModel import ObjectsModel @@ -186,7 +187,7 @@ class CuraApplication(QtApplication): self._cura_formula_functions = None # type: Optional[CuraFormulaFunctions] - self._machine_action_manager = None # type: Optional[MachineActionManager.MachineActionManager] + self._machine_action_manager: Optional[MachineActionManager] = None self.empty_container = None # type: EmptyInstanceContainer self.empty_definition_changes_container = None # type: EmptyInstanceContainer @@ -352,7 +353,7 @@ class CuraApplication(QtApplication): self.__addAllEmptyContainers() self.__setLatestResouceVersionsForVersionUpgrade() - self._machine_action_manager = MachineActionManager.MachineActionManager(self) + self._machine_action_manager = MachineActionManager(self) self._machine_action_manager.initialize() def __sendCommandToSingleInstance(self): @@ -373,9 +374,15 @@ class CuraApplication(QtApplication): Resources.addExpectedDirNameInData(dir_name) app_root = os.path.abspath(os.path.join(os.path.dirname(sys.executable))) - Resources.addSecureSearchPath(os.path.join(app_root, "share", "cura", "resources")) - Resources.addSecureSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources")) + if platform.system() == "Darwin": + Resources.addSecureSearchPath(os.path.join(app_root, "Resources", "share", "cura", "resources")) + Resources.addSecureSearchPath( + os.path.join(self._app_install_dir, "Resources", "share", "cura", "resources")) + else: + Resources.addSecureSearchPath(os.path.join(app_root, "share", "cura", "resources")) + Resources.addSecureSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources")) + if not hasattr(sys, "frozen"): cura_data_root = os.environ.get('CURA_DATA_ROOT', None) if cura_data_root: @@ -1129,18 +1136,16 @@ class CuraApplication(QtApplication): self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager() return self._setting_inheritance_manager - def getMachineActionManager(self, *args: Any) -> MachineActionManager.MachineActionManager: + @pyqtSlot(result = QObject) + def getMachineActionManager(self, *args: Any) -> MachineActionManager: """Get the machine action manager We ignore any *args given to this, as we also register the machine manager as qml singleton. It wants to give this function an engine and script engine, but we don't care about that. """ - return cast(MachineActionManager.MachineActionManager, self._machine_action_manager) + return self._machine_action_manager - @pyqtSlot(result = QObject) - def getMachineActionManagerQml(self)-> MachineActionManager.MachineActionManager: - return cast(QObject, self._machine_action_manager) @pyqtSlot(result = QObject) def getMaterialManagementModel(self) -> MaterialManagementModel: @@ -1264,7 +1269,7 @@ class CuraApplication(QtApplication): qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, self.getIntentManager, "IntentManager") qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, self.getSettingInheritanceManager, "SettingInheritanceManager") qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, self.getSimpleModeSettingsManagerWrapper, "SimpleModeSettingsManager") - qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, self.getMachineActionManagerWrapper, "MachineActionManager") + qmlRegisterSingletonType(MachineActionManager, "Cura", 1, 0, self.getMachineActionManagerWrapper, "MachineActionManager") self.processEvents() qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil") diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 0c4003fadc..66949eeaf5 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -76,6 +76,7 @@ class CuraEngineBackend(QObject, Backend): self._default_engine_location = executable_name search_path = [ + os.path.abspath(os.path.join(os.path.dirname(sys.executable), "..", "Resources")), os.path.abspath(os.path.dirname(sys.executable)), os.path.abspath(os.path.join(os.path.dirname(sys.executable), "bin")), os.path.abspath(os.path.join(os.path.dirname(sys.executable), "..")), diff --git a/requirements-dev.txt b/requirements-dev.txt index a1b191009a..162b33bf15 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ pytest -pyinstaller==5.8.0 +pyinstaller==6.3.0 pyinstaller-hooks-contrib pyyaml sip==6.5.1 diff --git a/resources/qml/Preferences/MachinesPage.qml b/resources/qml/Preferences/MachinesPage.qml index 971de03696..845c6cf492 100644 --- a/resources/qml/Preferences/MachinesPage.qml +++ b/resources/qml/Preferences/MachinesPage.qml @@ -12,7 +12,7 @@ import Cura 1.0 as Cura UM.ManagementPage { id: base - property var machineActionManager: CuraApplication.getMachineActionManagerQml() + property var machineActionManager: CuraApplication.getMachineActionManager() Item { enabled: false; UM.I18nCatalog { id: catalog; name: "cura"} } title: catalog.i18nc("@title:tab", "Printers") @@ -63,7 +63,7 @@ UM.ManagementPage Repeater { id: machineActionRepeater - model: base.currentItem ? machineActionManager.getSupportedActions(Cura.MachineManager.getDefinitionByMachineId(base.currentItem.id)) : null + model: base.currentItem.id ? machineActionManager.getSupportedActions(Cura.MachineManager.getDefinitionByMachineId(base.currentItem.id)) : null Item {