mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-04-22 05:39:37 +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',
|
||||
'CFBundleVersionString': {{ version }},
|
||||
'CFBundleShortVersionString': {{ short_version }},
|
||||
'CFBundleURLTypes': [{
|
||||
'CFBundleURLName': '{{ display_name }}',
|
||||
'CFBundleURLSchemes': ['cura', 'slicer'],
|
||||
}],
|
||||
'CFBundleDocumentTypes': [{
|
||||
'CFBundleTypeRole': 'Viewer',
|
||||
'CFBundleTypeExtensions': ['*'],
|
||||
|
@ -2,15 +2,18 @@
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import enum
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any, Dict
|
||||
import requests
|
||||
|
||||
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.QtQml import qmlRegisterUncreatableType, qmlRegisterUncreatableMetaObject, qmlRegisterSingletonType, qmlRegisterType
|
||||
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._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._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._python_installs = ApplicationMetadata.PYTHON_INSTALLS
|
||||
|
||||
self._supported_url_schemes: List[str] = ["cura", "slicer"]
|
||||
|
||||
@pyqtProperty(str, constant=True)
|
||||
def ultimakerCloudApiRootUrl(self) -> str:
|
||||
return UltimakerCloudConstants.CuraCloudAPIRoot
|
||||
@ -326,6 +331,10 @@ class CuraApplication(QtApplication):
|
||||
assert not "This crash is triggered by the trigger_early_crash command line argument."
|
||||
|
||||
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))
|
||||
|
||||
def initialize(self) -> None:
|
||||
@ -947,6 +956,8 @@ class CuraApplication(QtApplication):
|
||||
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.
|
||||
self.callLater(self._openFile, file_name)
|
||||
for url in self._open_url_queue:
|
||||
self.callLater(self._openUrl, url)
|
||||
|
||||
initializationFinished = pyqtSignal()
|
||||
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 self._plugins_loaded:
|
||||
if event.file():
|
||||
self._openFile(event.file())
|
||||
if event.url():
|
||||
self._openUrl(event.url())
|
||||
else:
|
||||
if 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.
|
||||
# Once we're at this point, everything should have been flushed already (past OnExitCallbackManager).
|
||||
@ -1542,7 +1559,7 @@ class CuraApplication(QtApplication):
|
||||
if not nodes:
|
||||
return
|
||||
|
||||
objects_in_filename = {} # type: Dict[str, List[CuraSceneNode]]
|
||||
objects_in_filename: Dict[str, List[CuraSceneNode]] = {}
|
||||
for node in nodes:
|
||||
mesh_data = node.getMeshData()
|
||||
if mesh_data:
|
||||
@ -1783,6 +1800,55 @@ class CuraApplication(QtApplication):
|
||||
def _openFile(self, 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):
|
||||
# TODO: Add the profile reader to the list of plug-ins that can be used when importing profiles.
|
||||
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
|
||||
${INSTALL_TYPE}{% for files in mapped_out_paths.values() %}{% for file in files %}
|
||||
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 "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} "${UNINSTALL_PATH}"
|
||||
SectionEnd
|
||||
|
||||
######################################################################
|
||||
|
@ -33,6 +33,21 @@
|
||||
/>
|
||||
</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 %}
|
||||
<Property Id="PREVIOUS_413_INSTALLED" Secure="yes" />
|
||||
<Upgrade Id="53C603BB-2B17-4206-A609-29C2E0D0B0AE">
|
||||
@ -144,11 +159,32 @@
|
||||
</Component>
|
||||
</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">
|
||||
<ComponentRef Id="CMP_UltiMaker_Cura_exe" />
|
||||
<ComponentRef Id="CMP_CuraEngine_exe" />
|
||||
<ComponentGroupRef Id="NewFilesGroup" />
|
||||
<ComponentRef Id="CMP_Shortcuts" />
|
||||
<ComponentRef Id="CuraRegistration"/>
|
||||
<ComponentRef Id="SlicerRegistration"/>
|
||||
</Feature>
|
||||
<Feature Id="UninstallOlderVersionFeature" Title="Uninstall previous versions" Level="{{ 1 if "Enterprise" in app_name else 0 }}" Description="..."/>
|
||||
</Product>
|
||||
|
Loading…
x
Reference in New Issue
Block a user