mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-04-22 13:49:39 +08:00
Merge branch 'main' into CURA-7647-proof-of-concept
This commit is contained in:
commit
a9a26859e7
@ -266,6 +266,10 @@ app = UMBUNDLE(
|
|||||||
'CFBundlePackageType': 'APPL',
|
'CFBundlePackageType': 'APPL',
|
||||||
'CFBundleVersionString': {{ version }},
|
'CFBundleVersionString': {{ version }},
|
||||||
'CFBundleShortVersionString': {{ short_version }},
|
'CFBundleShortVersionString': {{ short_version }},
|
||||||
|
'CFBundleURLTypes': [{
|
||||||
|
'CFBundleURLName': '{{ display_name }}',
|
||||||
|
'CFBundleURLSchemes': ['cura', 'slicer'],
|
||||||
|
}],
|
||||||
'CFBundleDocumentTypes': [{
|
'CFBundleDocumentTypes': [{
|
||||||
'CFBundleTypeRole': 'Viewer',
|
'CFBundleTypeRole': 'Viewer',
|
||||||
'CFBundleTypeExtensions': ['*'],
|
'CFBundleTypeExtensions': ['*'],
|
||||||
|
@ -2,15 +2,18 @@
|
|||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
import enum
|
import enum
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import platform
|
import platform
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any, Dict
|
from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any, Dict
|
||||||
|
import requests
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from PyQt6.QtCore import QObject, QTimer, QUrl, pyqtSignal, pyqtProperty, QEvent, pyqtEnum, QCoreApplication
|
from PyQt6.QtCore import QObject, QTimer, QUrl, QUrlQuery, pyqtSignal, pyqtProperty, QEvent, pyqtEnum, QCoreApplication, \
|
||||||
|
QByteArray
|
||||||
from PyQt6.QtGui import QColor, QIcon
|
from PyQt6.QtGui import QColor, QIcon
|
||||||
from PyQt6.QtQml import qmlRegisterUncreatableType, qmlRegisterUncreatableMetaObject, qmlRegisterSingletonType, qmlRegisterType
|
from PyQt6.QtQml import qmlRegisterUncreatableType, qmlRegisterUncreatableMetaObject, qmlRegisterSingletonType, qmlRegisterType
|
||||||
from PyQt6.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
@ -250,7 +253,7 @@ class CuraApplication(QtApplication):
|
|||||||
self._additional_components = {} # Components to add to certain areas in the interface
|
self._additional_components = {} # Components to add to certain areas in the interface
|
||||||
|
|
||||||
self._open_file_queue = [] # A list of files to open (after the application has started)
|
self._open_file_queue = [] # A list of files to open (after the application has started)
|
||||||
|
self._open_url_queue = [] # A list of urls to open (after the application has started)
|
||||||
self._update_platform_activity_timer = None
|
self._update_platform_activity_timer = None
|
||||||
|
|
||||||
self._sidebar_custom_menu_items = [] # type: list # Keeps list of custom menu items for the side bar
|
self._sidebar_custom_menu_items = [] # type: list # Keeps list of custom menu items for the side bar
|
||||||
@ -274,6 +277,8 @@ class CuraApplication(QtApplication):
|
|||||||
self._conan_installs = ApplicationMetadata.CONAN_INSTALLS
|
self._conan_installs = ApplicationMetadata.CONAN_INSTALLS
|
||||||
self._python_installs = ApplicationMetadata.PYTHON_INSTALLS
|
self._python_installs = ApplicationMetadata.PYTHON_INSTALLS
|
||||||
|
|
||||||
|
self._supported_url_schemes: List[str] = ["cura", "slicer"]
|
||||||
|
|
||||||
@pyqtProperty(str, constant=True)
|
@pyqtProperty(str, constant=True)
|
||||||
def ultimakerCloudApiRootUrl(self) -> str:
|
def ultimakerCloudApiRootUrl(self) -> str:
|
||||||
return UltimakerCloudConstants.CuraCloudAPIRoot
|
return UltimakerCloudConstants.CuraCloudAPIRoot
|
||||||
@ -326,6 +331,10 @@ class CuraApplication(QtApplication):
|
|||||||
assert not "This crash is triggered by the trigger_early_crash command line argument."
|
assert not "This crash is triggered by the trigger_early_crash command line argument."
|
||||||
|
|
||||||
for filename in self._cli_args.file:
|
for filename in self._cli_args.file:
|
||||||
|
url = QUrl(filename)
|
||||||
|
if url.scheme() in self._supported_url_schemes:
|
||||||
|
self._open_url_queue.append(url)
|
||||||
|
else:
|
||||||
self._files_to_open.append(os.path.abspath(filename))
|
self._files_to_open.append(os.path.abspath(filename))
|
||||||
|
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
@ -947,6 +956,8 @@ class CuraApplication(QtApplication):
|
|||||||
self.callLater(self._openFile, file_name)
|
self.callLater(self._openFile, file_name)
|
||||||
for file_name in self._open_file_queue: # Open all the files that were queued up while plug-ins were loading.
|
for file_name in self._open_file_queue: # Open all the files that were queued up while plug-ins were loading.
|
||||||
self.callLater(self._openFile, file_name)
|
self.callLater(self._openFile, file_name)
|
||||||
|
for url in self._open_url_queue:
|
||||||
|
self.callLater(self._openUrl, url)
|
||||||
|
|
||||||
initializationFinished = pyqtSignal()
|
initializationFinished = pyqtSignal()
|
||||||
showAddPrintersUncancellableDialog = pyqtSignal() # Used to show the add printers dialog with a greyed background
|
showAddPrintersUncancellableDialog = pyqtSignal() # Used to show the add printers dialog with a greyed background
|
||||||
@ -1156,9 +1167,15 @@ class CuraApplication(QtApplication):
|
|||||||
|
|
||||||
if event.type() == QEvent.Type.FileOpen:
|
if event.type() == QEvent.Type.FileOpen:
|
||||||
if self._plugins_loaded:
|
if self._plugins_loaded:
|
||||||
|
if event.file():
|
||||||
self._openFile(event.file())
|
self._openFile(event.file())
|
||||||
|
if event.url():
|
||||||
|
self._openUrl(event.url())
|
||||||
else:
|
else:
|
||||||
|
if event.file():
|
||||||
self._open_file_queue.append(event.file())
|
self._open_file_queue.append(event.file())
|
||||||
|
if event.url():
|
||||||
|
self._open_url_queue.append(event.url())
|
||||||
|
|
||||||
if int(event.type()) == 20: # 'QEvent.Type.Quit' enum isn't there, even though it should be according to docs.
|
if int(event.type()) == 20: # 'QEvent.Type.Quit' enum isn't there, even though it should be according to docs.
|
||||||
# Once we're at this point, everything should have been flushed already (past OnExitCallbackManager).
|
# Once we're at this point, everything should have been flushed already (past OnExitCallbackManager).
|
||||||
@ -1542,7 +1559,7 @@ class CuraApplication(QtApplication):
|
|||||||
if not nodes:
|
if not nodes:
|
||||||
return
|
return
|
||||||
|
|
||||||
objects_in_filename = {} # type: Dict[str, List[CuraSceneNode]]
|
objects_in_filename: Dict[str, List[CuraSceneNode]] = {}
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
mesh_data = node.getMeshData()
|
mesh_data = node.getMeshData()
|
||||||
if mesh_data:
|
if mesh_data:
|
||||||
@ -1783,6 +1800,55 @@ class CuraApplication(QtApplication):
|
|||||||
def _openFile(self, filename):
|
def _openFile(self, filename):
|
||||||
self.readLocalFile(QUrl.fromLocalFile(filename))
|
self.readLocalFile(QUrl.fromLocalFile(filename))
|
||||||
|
|
||||||
|
def _openUrl(self, url: QUrl) -> None:
|
||||||
|
if url.scheme() not in self._supported_url_schemes:
|
||||||
|
# only handle cura:// and slicer:// urls schemes
|
||||||
|
return
|
||||||
|
|
||||||
|
match url.host() + url.path():
|
||||||
|
case "open" | "open/":
|
||||||
|
query = QUrlQuery(url.query())
|
||||||
|
model_url = QUrl(query.queryItemValue("file", options=QUrl.ComponentFormattingOption.FullyDecoded))
|
||||||
|
|
||||||
|
def on_finish(response):
|
||||||
|
content_disposition_header_key = QByteArray("content-disposition".encode())
|
||||||
|
|
||||||
|
if not response.hasRawHeader(content_disposition_header_key):
|
||||||
|
Logger.log("w", "Could not find Content-Disposition header in response from {0}".format(
|
||||||
|
model_url.url()))
|
||||||
|
# Use the last part of the url as the filename, and assume it is an STL file
|
||||||
|
filename = model_url.path().split("/")[-1] + ".stl"
|
||||||
|
else:
|
||||||
|
# content_disposition is in the format
|
||||||
|
# ```
|
||||||
|
# content_disposition attachment; "filename=[FILENAME]"
|
||||||
|
# ```
|
||||||
|
# Use a regex to extract the filename
|
||||||
|
content_disposition = str(response.rawHeader(content_disposition_header_key).data(),
|
||||||
|
encoding='utf-8')
|
||||||
|
content_disposition_match = re.match(r'attachment; filename="(?P<filename>.*)"',
|
||||||
|
content_disposition)
|
||||||
|
assert content_disposition_match is not None
|
||||||
|
filename = content_disposition_match.group("filename")
|
||||||
|
|
||||||
|
tmp = tempfile.NamedTemporaryFile(suffix=filename, delete=False)
|
||||||
|
with open(tmp.name, "wb") as f:
|
||||||
|
f.write(response.readAll())
|
||||||
|
|
||||||
|
self.readLocalFile(QUrl.fromLocalFile(tmp.name), add_to_recent_files=False)
|
||||||
|
|
||||||
|
def on_error():
|
||||||
|
Logger.log("w", "Could not download file from {0}".format(model_url.url()))
|
||||||
|
return
|
||||||
|
|
||||||
|
self.getHttpRequestManager().get(
|
||||||
|
model_url.url(),
|
||||||
|
callback=on_finish,
|
||||||
|
error_callback=on_error,
|
||||||
|
)
|
||||||
|
case path:
|
||||||
|
Logger.log("w", "Unsupported url scheme path: {0}".format(path))
|
||||||
|
|
||||||
def _addProfileReader(self, profile_reader):
|
def _addProfileReader(self, profile_reader):
|
||||||
# TODO: Add the profile reader to the list of plug-ins that can be used when importing profiles.
|
# TODO: Add the profile reader to the list of plug-ins that can be used when importing profiles.
|
||||||
pass
|
pass
|
||||||
|
@ -144,6 +144,23 @@ SectionEnd
|
|||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
Section UrlProtocol
|
||||||
|
|
||||||
|
WriteRegStr HKCR "cura" "" "URL:cura"
|
||||||
|
WriteRegStr HKCR "cura" "URL Protocol" ""
|
||||||
|
WriteRegStr HKCR "cura\DefaultIcon" "" "$INSTDIR\${MAIN_APP_EXE},1"
|
||||||
|
WriteRegStr HKCR "cura\shell" "" "open"
|
||||||
|
WriteRegStr HKCR "cura\shell\open\command" "" '"$INSTDIR\${MAIN_APP_EXE}" "%1"'
|
||||||
|
|
||||||
|
WriteRegStr HKCR "slicer" "" "URL:slicer"
|
||||||
|
WriteRegStr HKCR "slicer" "URL Protocol" ""
|
||||||
|
WriteRegStr HKCR "slicer\DefaultIcon" "" "$INSTDIR\${MAIN_APP_EXE},1"
|
||||||
|
WriteRegStr HKCR "slicer\shell" "" "open"
|
||||||
|
WriteRegStr HKCR "slicer\shell\open\command" "" '"$INSTDIR\${MAIN_APP_EXE}" "%1"'
|
||||||
|
|
||||||
|
SectionEnd
|
||||||
|
######################################################################
|
||||||
|
|
||||||
Section Uninstall
|
Section Uninstall
|
||||||
${INSTALL_TYPE}{% for files in mapped_out_paths.values() %}{% for file in files %}
|
${INSTALL_TYPE}{% for files in mapped_out_paths.values() %}{% for file in files %}
|
||||||
Delete "{{ file[1] }}"{% endfor %}{% endfor %}{% for rem_dir in rmdir_paths %}
|
Delete "{{ file[1] }}"{% endfor %}{% endfor %}{% for rem_dir in rmdir_paths %}
|
||||||
@ -187,8 +204,13 @@ RmDir "$SMPROGRAMS\{{ app_name }}"
|
|||||||
!insertmacro APP_UNASSOCIATE "stl" "Cura.model"
|
!insertmacro APP_UNASSOCIATE "stl" "Cura.model"
|
||||||
!insertmacro APP_UNASSOCIATE "3mf" "Cura.project"
|
!insertmacro APP_UNASSOCIATE "3mf" "Cura.project"
|
||||||
|
|
||||||
|
; Unassociate file associations for 'cura' protocol
|
||||||
|
DeleteRegKey HKCR "cura"
|
||||||
|
|
||||||
|
; Unassociate file associations for 'slicer' protocol
|
||||||
|
DeleteRegKey HKCR "slicer"
|
||||||
|
|
||||||
DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}"
|
DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}"
|
||||||
DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}"
|
DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}"
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
######################################################################
|
|
||||||
|
@ -33,6 +33,21 @@
|
|||||||
/>
|
/>
|
||||||
</Upgrade>
|
</Upgrade>
|
||||||
|
|
||||||
|
<Property Id="ASSOCIATE_URL_PROTOCOLS">
|
||||||
|
<RegistrySearch Id="CheckCuraProtocolHandler"
|
||||||
|
Type="raw"
|
||||||
|
Root="HKCR"
|
||||||
|
Key="cura"
|
||||||
|
Name="URL Protocol"
|
||||||
|
/>
|
||||||
|
<RegistrySearch Id="CheckSlicerProtocolHandler"
|
||||||
|
Type="raw"
|
||||||
|
Root="HKCR"
|
||||||
|
Key="slicer"
|
||||||
|
Name="URL Protocol"
|
||||||
|
/>
|
||||||
|
</Property>
|
||||||
|
|
||||||
{% if "Enterprise" in app_name %}
|
{% if "Enterprise" in app_name %}
|
||||||
<Property Id="PREVIOUS_413_INSTALLED" Secure="yes" />
|
<Property Id="PREVIOUS_413_INSTALLED" Secure="yes" />
|
||||||
<Upgrade Id="53C603BB-2B17-4206-A609-29C2E0D0B0AE">
|
<Upgrade Id="53C603BB-2B17-4206-A609-29C2E0D0B0AE">
|
||||||
@ -144,11 +159,32 @@
|
|||||||
</Component>
|
</Component>
|
||||||
</DirectoryRef>
|
</DirectoryRef>
|
||||||
|
|
||||||
|
<!--Url Scheme-->
|
||||||
|
<Component Id="CuraRegistration" Guid="*" Directory="APPLICATIONFOLDER">
|
||||||
|
<RegistryKey Root="HKCR" Key="cura">
|
||||||
|
<RegistryValue Type="string" Value="URL:Cura Protocol"/>
|
||||||
|
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
|
||||||
|
<RegistryValue Type="string" Key="DefaultIcon" Value="[APPLICATIONFOLDER]\{{ main_app }},1"/>
|
||||||
|
<RegistryValue Type="string" Key="shell\open\command" Value=""[APPLICATIONFOLDER]\{{ main_app }}" "%1""/>
|
||||||
|
</RegistryKey>
|
||||||
|
</Component>
|
||||||
|
|
||||||
|
<Component Id="SlicerRegistration" Guid="*" Directory="APPLICATIONFOLDER">
|
||||||
|
<RegistryKey Root="HKCR" Key="slicer">
|
||||||
|
<RegistryValue Type="string" Value="URL:Slicer Protocol"/>
|
||||||
|
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
|
||||||
|
<RegistryValue Type="string" Key="DefaultIcon" Value="[APPLICATIONFOLDER]\{{ main_app }},1"/>
|
||||||
|
<RegistryValue Type="string" Key="shell\open\command" Value=""[APPLICATIONFOLDER]\{{ main_app }}" "%1""/>
|
||||||
|
</RegistryKey>
|
||||||
|
</Component>
|
||||||
|
|
||||||
<Feature Id="ProductFeature" Title="{{ app_name }}" Level="1" ConfigurableDirectory="APPLICATIONFOLDER">
|
<Feature Id="ProductFeature" Title="{{ app_name }}" Level="1" ConfigurableDirectory="APPLICATIONFOLDER">
|
||||||
<ComponentRef Id="CMP_UltiMaker_Cura_exe" />
|
<ComponentRef Id="CMP_UltiMaker_Cura_exe" />
|
||||||
<ComponentRef Id="CMP_CuraEngine_exe" />
|
<ComponentRef Id="CMP_CuraEngine_exe" />
|
||||||
<ComponentGroupRef Id="NewFilesGroup" />
|
<ComponentGroupRef Id="NewFilesGroup" />
|
||||||
<ComponentRef Id="CMP_Shortcuts" />
|
<ComponentRef Id="CMP_Shortcuts" />
|
||||||
|
<ComponentRef Id="CuraRegistration"/>
|
||||||
|
<ComponentRef Id="SlicerRegistration"/>
|
||||||
</Feature>
|
</Feature>
|
||||||
<Feature Id="UninstallOlderVersionFeature" Title="Uninstall previous versions" Level="{{ 1 if "Enterprise" in app_name else 0 }}" Description="..."/>
|
<Feature Id="UninstallOlderVersionFeature" Title="Uninstall previous versions" Level="{{ 1 if "Enterprise" in app_name else 0 }}" Description="..."/>
|
||||||
</Product>
|
</Product>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user