Merge branch 'main' into CURA-7647-proof-of-concept

This commit is contained in:
Saumya Jain 2023-12-21 17:30:48 +01:00 committed by GitHub
commit a9a26859e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 135 additions and 7 deletions

View File

@ -266,6 +266,10 @@ app = UMBUNDLE(
'CFBundlePackageType': 'APPL',
'CFBundleVersionString': {{ version }},
'CFBundleShortVersionString': {{ short_version }},
'CFBundleURLTypes': [{
'CFBundleURLName': '{{ display_name }}',
'CFBundleURLSchemes': ['cura', 'slicer'],
}],
'CFBundleDocumentTypes': [{
'CFBundleTypeRole': 'Viewer',
'CFBundleTypeExtensions': ['*'],

View File

@ -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

View File

@ -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
######################################################################

View File

@ -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="&quot;[APPLICATIONFOLDER]\{{ main_app }}&quot; &quot;%1&quot;"/>
</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="&quot;[APPLICATIONFOLDER]\{{ main_app }}&quot; &quot;%1&quot;"/>
</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>