mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-12 02:38:59 +08:00
Merge branch 'main' into CURA-11596-open-url-singleinstance
This commit is contained in:
commit
799ef9ef51
@ -55,7 +55,8 @@ exe = EXE(
|
|||||||
target_arch={{ target_arch }},
|
target_arch={{ target_arch }},
|
||||||
codesign_identity=os.getenv('CODESIGN_IDENTITY', None),
|
codesign_identity=os.getenv('CODESIGN_IDENTITY', None),
|
||||||
entitlements_file={{ entitlements_file }},
|
entitlements_file={{ entitlements_file }},
|
||||||
icon={{ icon }}
|
icon={{ icon }},
|
||||||
|
contents_directory='.'
|
||||||
)
|
)
|
||||||
|
|
||||||
coll = COLLECT(
|
coll = COLLECT(
|
||||||
@ -70,188 +71,7 @@ coll = COLLECT(
|
|||||||
)
|
)
|
||||||
|
|
||||||
{% if macos == true %}
|
{% if macos == true %}
|
||||||
# PyInstaller seems to copy everything in the resource folder for the MacOS, this causes issues with codesigning and notarizing
|
app = BUNDLE(
|
||||||
# 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(
|
|
||||||
coll,
|
coll,
|
||||||
name='{{ display_name }}.app',
|
name='{{ display_name }}.app',
|
||||||
icon={{ icon }},
|
icon={{ icon }},
|
||||||
@ -271,9 +91,10 @@ app = UMBUNDLE(
|
|||||||
'CFBundleURLSchemes': ['cura', 'slicer'],
|
'CFBundleURLSchemes': ['cura', 'slicer'],
|
||||||
}],
|
}],
|
||||||
'CFBundleDocumentTypes': [{
|
'CFBundleDocumentTypes': [{
|
||||||
'CFBundleTypeRole': 'Viewer',
|
'CFBundleTypeRole': 'Viewer',
|
||||||
'CFBundleTypeExtensions': ['*'],
|
'CFBundleTypeExtensions': ['*'],
|
||||||
'CFBundleTypeName': 'Model Files',
|
'CFBundleTypeName': 'Model Files',
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
){% endif %}
|
)
|
||||||
|
{% endif %}
|
||||||
|
@ -118,7 +118,6 @@ pyinstaller:
|
|||||||
- "sqlite3"
|
- "sqlite3"
|
||||||
- "trimesh"
|
- "trimesh"
|
||||||
- "win32ctypes"
|
- "win32ctypes"
|
||||||
- "PyQt6"
|
|
||||||
- "PyQt6.QtNetwork"
|
- "PyQt6.QtNetwork"
|
||||||
- "PyQt6.sip"
|
- "PyQt6.sip"
|
||||||
- "stl"
|
- "stl"
|
||||||
|
@ -104,7 +104,8 @@ from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
|
|||||||
from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel
|
from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel
|
||||||
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
|
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
|
||||||
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
|
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.AddPrinterPagesModel import AddPrinterPagesModel
|
||||||
from cura.UI.MachineSettingsManager import MachineSettingsManager
|
from cura.UI.MachineSettingsManager import MachineSettingsManager
|
||||||
from cura.UI.ObjectsModel import ObjectsModel
|
from cura.UI.ObjectsModel import ObjectsModel
|
||||||
@ -186,7 +187,7 @@ class CuraApplication(QtApplication):
|
|||||||
|
|
||||||
self._cura_formula_functions = None # type: Optional[CuraFormulaFunctions]
|
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_container = None # type: EmptyInstanceContainer
|
||||||
self.empty_definition_changes_container = None # type: EmptyInstanceContainer
|
self.empty_definition_changes_container = None # type: EmptyInstanceContainer
|
||||||
@ -352,7 +353,7 @@ class CuraApplication(QtApplication):
|
|||||||
self.__addAllEmptyContainers()
|
self.__addAllEmptyContainers()
|
||||||
self.__setLatestResouceVersionsForVersionUpgrade()
|
self.__setLatestResouceVersionsForVersionUpgrade()
|
||||||
|
|
||||||
self._machine_action_manager = MachineActionManager.MachineActionManager(self)
|
self._machine_action_manager = MachineActionManager(self)
|
||||||
self._machine_action_manager.initialize()
|
self._machine_action_manager.initialize()
|
||||||
|
|
||||||
def __sendCommandToSingleInstance(self):
|
def __sendCommandToSingleInstance(self):
|
||||||
@ -373,9 +374,15 @@ class CuraApplication(QtApplication):
|
|||||||
Resources.addExpectedDirNameInData(dir_name)
|
Resources.addExpectedDirNameInData(dir_name)
|
||||||
|
|
||||||
app_root = os.path.abspath(os.path.join(os.path.dirname(sys.executable)))
|
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"):
|
if not hasattr(sys, "frozen"):
|
||||||
cura_data_root = os.environ.get('CURA_DATA_ROOT', None)
|
cura_data_root = os.environ.get('CURA_DATA_ROOT', None)
|
||||||
if cura_data_root:
|
if cura_data_root:
|
||||||
@ -1129,18 +1136,16 @@ class CuraApplication(QtApplication):
|
|||||||
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
|
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
|
||||||
return self._setting_inheritance_manager
|
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
|
"""Get the machine action manager
|
||||||
|
|
||||||
We ignore any *args given to this, as we also register the machine manager as qml singleton.
|
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.
|
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)
|
@pyqtSlot(result = QObject)
|
||||||
def getMaterialManagementModel(self) -> MaterialManagementModel:
|
def getMaterialManagementModel(self) -> MaterialManagementModel:
|
||||||
@ -1264,7 +1269,7 @@ class CuraApplication(QtApplication):
|
|||||||
qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, self.getIntentManager, "IntentManager")
|
qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, self.getIntentManager, "IntentManager")
|
||||||
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, self.getSettingInheritanceManager, "SettingInheritanceManager")
|
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, self.getSettingInheritanceManager, "SettingInheritanceManager")
|
||||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, self.getSimpleModeSettingsManagerWrapper, "SimpleModeSettingsManager")
|
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()
|
self.processEvents()
|
||||||
qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil")
|
qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil")
|
||||||
|
@ -76,6 +76,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
self._default_engine_location = executable_name
|
self._default_engine_location = executable_name
|
||||||
|
|
||||||
search_path = [
|
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.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), "bin")),
|
||||||
os.path.abspath(os.path.join(os.path.dirname(sys.executable), "..")),
|
os.path.abspath(os.path.join(os.path.dirname(sys.executable), "..")),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
pytest
|
pytest
|
||||||
pyinstaller==5.8.0
|
pyinstaller==6.3.0
|
||||||
pyinstaller-hooks-contrib
|
pyinstaller-hooks-contrib
|
||||||
pyyaml
|
pyyaml
|
||||||
sip==6.5.1
|
sip==6.5.1
|
||||||
|
@ -12,7 +12,7 @@ import Cura 1.0 as Cura
|
|||||||
UM.ManagementPage
|
UM.ManagementPage
|
||||||
{
|
{
|
||||||
id: base
|
id: base
|
||||||
property var machineActionManager: CuraApplication.getMachineActionManagerQml()
|
property var machineActionManager: CuraApplication.getMachineActionManager()
|
||||||
Item { enabled: false; UM.I18nCatalog { id: catalog; name: "cura"} }
|
Item { enabled: false; UM.I18nCatalog { id: catalog; name: "cura"} }
|
||||||
|
|
||||||
title: catalog.i18nc("@title:tab", "Printers")
|
title: catalog.i18nc("@title:tab", "Printers")
|
||||||
@ -63,7 +63,7 @@ UM.ManagementPage
|
|||||||
Repeater
|
Repeater
|
||||||
{
|
{
|
||||||
id: machineActionRepeater
|
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
|
Item
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user