mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-12 17:59:00 +08:00
Merge branch 'Ultimaker:main' into main
This commit is contained in:
commit
816d71bfd8
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@ -45,7 +45,7 @@ env:
|
|||||||
STAGING: ${{ inputs.staging || false }}
|
STAGING: ${{ inputs.staging || false }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
windows-installer:
|
installer:
|
||||||
uses: ultimaker/cura-workflows/.github/workflows/cura-installer-linux.yml@main
|
uses: ultimaker/cura-workflows/.github/workflows/cura-installer-linux.yml@main
|
||||||
with:
|
with:
|
||||||
cura_conan_version: ${{ inputs.cura_conan_version }}
|
cura_conan_version: ${{ inputs.cura_conan_version }}
|
||||||
|
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
@ -49,7 +49,7 @@ env:
|
|||||||
STAGING: ${{ inputs.staging || false }}
|
STAGING: ${{ inputs.staging || false }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
windows-installer:
|
installer:
|
||||||
uses: ultimaker/cura-workflows/.github/workflows/cura-installer-macos.yml@main
|
uses: ultimaker/cura-workflows/.github/workflows/cura-installer-macos.yml@main
|
||||||
with:
|
with:
|
||||||
cura_conan_version: ${{ inputs.cura_conan_version }}
|
cura_conan_version: ${{ inputs.cura_conan_version }}
|
||||||
|
2
.github/workflows/windows.yml
vendored
2
.github/workflows/windows.yml
vendored
@ -45,7 +45,7 @@ env:
|
|||||||
STAGING: ${{ inputs.staging || false }}
|
STAGING: ${{ inputs.staging || false }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
windows-installer:
|
installer:
|
||||||
uses: ultimaker/cura-workflows/.github/workflows/cura-installer-windows.yml@main
|
uses: ultimaker/cura-workflows/.github/workflows/cura-installer-windows.yml@main
|
||||||
with:
|
with:
|
||||||
cura_conan_version: ${{ inputs.cura_conan_version }}
|
cura_conan_version: ${{ inputs.cura_conan_version }}
|
||||||
|
@ -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': ['*'],
|
||||||
|
@ -5,7 +5,7 @@ requirements:
|
|||||||
- "cura_binary_data/(latest)@ultimaker/testing"
|
- "cura_binary_data/(latest)@ultimaker/testing"
|
||||||
- "fdm_materials/(latest)@ultimaker/testing"
|
- "fdm_materials/(latest)@ultimaker/testing"
|
||||||
- "curaengine_plugin_gradual_flow/(latest)@ultimaker/stable"
|
- "curaengine_plugin_gradual_flow/(latest)@ultimaker/stable"
|
||||||
- "dulcificum/(latest)@ultimaker/testing"
|
- "dulcificum/latest@ultimaker/testing"
|
||||||
- "pyarcus/5.3.0"
|
- "pyarcus/5.3.0"
|
||||||
- "pysavitar/5.3.0"
|
- "pysavitar/5.3.0"
|
||||||
- "pynest2d/5.3.0"
|
- "pynest2d/5.3.0"
|
||||||
|
@ -242,7 +242,7 @@ class CuraConan(ConanFile):
|
|||||||
self.output.warning(f"Source path for binary {binary['binary']} does not exist")
|
self.output.warning(f"Source path for binary {binary['binary']} does not exist")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for bin in Path(src_path).glob(binary["binary"] + "*[.exe|.dll|.so|.dylib|.so.]*"):
|
for bin in Path(src_path).glob(binary["binary"] + "*[.exe|.dll|.so|.dylib|.so.|.pdb]*"):
|
||||||
binaries.append((str(bin), binary["dst"]))
|
binaries.append((str(bin), binary["dst"]))
|
||||||
for bin in Path(src_path).glob(binary["binary"]):
|
for bin in Path(src_path).glob(binary["binary"]):
|
||||||
binaries.append((str(bin), binary["dst"]))
|
binaries.append((str(bin), binary["dst"]))
|
||||||
|
@ -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,7 +331,11 @@ 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:
|
||||||
self._files_to_open.append(os.path.abspath(filename))
|
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:
|
def initialize(self) -> None:
|
||||||
self.__addExpectedResourceDirsAndSearchPaths() # Must be added before init of super
|
self.__addExpectedResourceDirsAndSearchPaths() # Must be added before init of super
|
||||||
@ -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:
|
||||||
self._openFile(event.file())
|
if event.file():
|
||||||
|
self._openFile(event.file())
|
||||||
|
if event.url():
|
||||||
|
self._openUrl(event.url())
|
||||||
else:
|
else:
|
||||||
self._open_file_queue.append(event.file())
|
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.
|
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,58 @@ 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(*args, **kwargs):
|
||||||
|
Logger.log("w", "Could not download file from {0}".format(model_url.url()))
|
||||||
|
Message("Could not download file: " + str(model_url.url()),
|
||||||
|
title= "Loading Model failed",
|
||||||
|
message_type=Message.MessageType.ERROR).show()
|
||||||
|
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
|
||||||
|
@ -67,7 +67,7 @@ class LayerPolygon:
|
|||||||
# Buffering the colors shouldn't be necessary as it is not
|
# Buffering the colors shouldn't be necessary as it is not
|
||||||
# re-used and can save a lot of memory usage.
|
# re-used and can save a lot of memory usage.
|
||||||
self._color_map = LayerPolygon.getColorMap()
|
self._color_map = LayerPolygon.getColorMap()
|
||||||
self._colors = self._color_map[self._types] # type: numpy.ndarray
|
self._colors: numpy.ndarray = self._color_map[self._types]
|
||||||
|
|
||||||
# When type is used as index returns true if type == LayerPolygon.InfillType
|
# When type is used as index returns true if type == LayerPolygon.InfillType
|
||||||
# or type == LayerPolygon.SkinType
|
# or type == LayerPolygon.SkinType
|
||||||
@ -75,8 +75,8 @@ class LayerPolygon:
|
|||||||
# Should be generated in better way, not hardcoded.
|
# Should be generated in better way, not hardcoded.
|
||||||
self._is_infill_or_skin_type_map = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype=bool)
|
self._is_infill_or_skin_type_map = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype=bool)
|
||||||
|
|
||||||
self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
|
self._build_cache_line_mesh_mask: Optional[numpy.ndarray] = None
|
||||||
self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
|
self._build_cache_needed_points: Optional[numpy.ndarray] = None
|
||||||
|
|
||||||
def buildCache(self) -> None:
|
def buildCache(self) -> None:
|
||||||
# For the line mesh we do not draw Infill or Jumps. Therefore those lines are filtered out.
|
# For the line mesh we do not draw Infill or Jumps. Therefore those lines are filtered out.
|
||||||
|
@ -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>
|
||||||
|
@ -33,6 +33,8 @@ message Slice
|
|||||||
repeated Extruder extruders = 3; // The settings sent to each extruder object
|
repeated Extruder extruders = 3; // The settings sent to each extruder object
|
||||||
repeated SettingExtruder limit_to_extruder = 4; // From which stack the setting would inherit if not defined per object
|
repeated SettingExtruder limit_to_extruder = 4; // From which stack the setting would inherit if not defined per object
|
||||||
repeated EnginePlugin engine_plugins = 5;
|
repeated EnginePlugin engine_plugins = 5;
|
||||||
|
string sentry_id = 6; // The anonymized Sentry user id that requested the slice
|
||||||
|
string cura_version = 7; // The version of Cura that requested the slice
|
||||||
}
|
}
|
||||||
|
|
||||||
message Extruder
|
message Extruder
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# Copyright (c) 2023 UltiMaker
|
# Copyright (c) 2023 UltiMaker
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
import uuid
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
@ -30,6 +32,7 @@ from cura.CuraApplication import CuraApplication
|
|||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
from cura.OneAtATimeIterator import OneAtATimeIterator
|
from cura.OneAtATimeIterator import OneAtATimeIterator
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
from cura.CuraVersion import CuraVersion
|
||||||
|
|
||||||
|
|
||||||
NON_PRINTING_MESH_SETTINGS = ["anti_overhang_mesh", "infill_mesh", "cutting_mesh"]
|
NON_PRINTING_MESH_SETTINGS = ["anti_overhang_mesh", "infill_mesh", "cutting_mesh"]
|
||||||
@ -332,6 +335,11 @@ class StartSliceJob(Job):
|
|||||||
self._buildGlobalSettingsMessage(stack)
|
self._buildGlobalSettingsMessage(stack)
|
||||||
self._buildGlobalInheritsStackMessage(stack)
|
self._buildGlobalInheritsStackMessage(stack)
|
||||||
|
|
||||||
|
user_id = uuid.getnode() # On all of Cura's supported platforms, this returns the MAC address which is pseudonymical information (!= anonymous).
|
||||||
|
user_id %= 2 ** 16 # So to make it anonymous, apply a bitmask selecting only the last 16 bits. This prevents it from being traceable to a specific user but still gives somewhat of an idea of whether it's just the same user hitting the same crash over and over again, or if it's widespread.
|
||||||
|
self._slice_message.sentry_id = "{user_id}"
|
||||||
|
self._slice_message.cura_version = CuraVersion
|
||||||
|
|
||||||
# Build messages for extruder stacks
|
# Build messages for extruder stacks
|
||||||
for extruder_stack in global_stack.extruderList:
|
for extruder_stack in global_stack.extruderList:
|
||||||
self._buildExtruderMessage(extruder_stack)
|
self._buildExtruderMessage(extruder_stack)
|
||||||
|
@ -35,7 +35,7 @@ class SimulationPass(RenderPass):
|
|||||||
self._nozzle_shader = None
|
self._nozzle_shader = None
|
||||||
self._disabled_shader = None
|
self._disabled_shader = None
|
||||||
self._old_current_layer = 0
|
self._old_current_layer = 0
|
||||||
self._old_current_path = 0
|
self._old_current_path: float = 0.0
|
||||||
self._switching_layers = True # Tracking whether the user is moving across layers (True) or across paths (False). If false, lower layers render as shadowy.
|
self._switching_layers = True # Tracking whether the user is moving across layers (True) or across paths (False). If false, lower layers render as shadowy.
|
||||||
self._gl = OpenGL.getInstance().getBindingsObject()
|
self._gl = OpenGL.getInstance().getBindingsObject()
|
||||||
self._scene = Application.getInstance().getController().getScene()
|
self._scene = Application.getInstance().getController().getScene()
|
||||||
@ -139,7 +139,7 @@ class SimulationPass(RenderPass):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Render all layers below a certain number as line mesh instead of vertices.
|
# Render all layers below a certain number as line mesh instead of vertices.
|
||||||
if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())):
|
if self._layer_view.getCurrentLayer() > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())):
|
||||||
start = 0
|
start = 0
|
||||||
end = 0
|
end = 0
|
||||||
element_counts = layer_data.getElementCounts()
|
element_counts = layer_data.getElementCounts()
|
||||||
@ -147,7 +147,7 @@ class SimulationPass(RenderPass):
|
|||||||
# In the current layer, we show just the indicated paths
|
# In the current layer, we show just the indicated paths
|
||||||
if layer == self._layer_view._current_layer_num:
|
if layer == self._layer_view._current_layer_num:
|
||||||
# We look for the position of the head, searching the point of the current path
|
# We look for the position of the head, searching the point of the current path
|
||||||
index = self._layer_view._current_path_num
|
index = int(self._layer_view.getCurrentPath())
|
||||||
offset = 0
|
offset = 0
|
||||||
for polygon in layer_data.getLayer(layer).polygons:
|
for polygon in layer_data.getLayer(layer).polygons:
|
||||||
# The size indicates all values in the two-dimension array, and the second dimension is
|
# The size indicates all values in the two-dimension array, and the second dimension is
|
||||||
@ -157,23 +157,33 @@ class SimulationPass(RenderPass):
|
|||||||
offset = 1 # This is to avoid the first point when there is more than one polygon, since has the same value as the last point in the previous polygon
|
offset = 1 # This is to avoid the first point when there is more than one polygon, since has the same value as the last point in the previous polygon
|
||||||
continue
|
continue
|
||||||
# The head position is calculated and translated
|
# The head position is calculated and translated
|
||||||
head_position = Vector(polygon.data[index+offset][0], polygon.data[index+offset][1], polygon.data[index+offset][2]) + node.getWorldPosition()
|
ratio = self._layer_view.getCurrentPath() - index
|
||||||
|
pos_a = Vector(polygon.data[index + offset][0], polygon.data[index + offset][1],
|
||||||
|
polygon.data[index + offset][2])
|
||||||
|
if ratio <= 0.0001 or index + offset < len(polygon.data):
|
||||||
|
head_position = pos_a + node.getWorldPosition()
|
||||||
|
else:
|
||||||
|
pos_b = Vector(polygon.data[index + offset + 1][0],
|
||||||
|
polygon.data[index + offset + 1][1],
|
||||||
|
polygon.data[index + offset + 1][2])
|
||||||
|
vec = pos_a * (1.0 - ratio) + pos_b * ratio
|
||||||
|
head_position = vec + node.getWorldPosition()
|
||||||
break
|
break
|
||||||
break
|
break
|
||||||
if self._layer_view._minimum_layer_num > layer:
|
if self._layer_view.getMinimumLayer() > layer:
|
||||||
start += element_counts[layer]
|
start += element_counts[layer]
|
||||||
end += element_counts[layer]
|
end += element_counts[layer]
|
||||||
|
|
||||||
# Calculate the range of paths in the last layer
|
# Calculate the range of paths in the last layer
|
||||||
current_layer_start = end
|
current_layer_start = end
|
||||||
current_layer_end = end + self._layer_view._current_path_num * 2 # Because each point is used twice
|
current_layer_end = end + int( self._layer_view.getCurrentPath()) * 2 # Because each point is used twice
|
||||||
|
|
||||||
# This uses glDrawRangeElements internally to only draw a certain range of lines.
|
# This uses glDrawRangeElements internally to only draw a certain range of lines.
|
||||||
# All the layers but the current selected layer are rendered first
|
# All the layers but the current selected layer are rendered first
|
||||||
if self._old_current_path != self._layer_view._current_path_num:
|
if self._old_current_path != self._layer_view.getCurrentPath():
|
||||||
self._current_shader = self._layer_shadow_shader
|
self._current_shader = self._layer_shadow_shader
|
||||||
self._switching_layers = False
|
self._switching_layers = False
|
||||||
if not self._layer_view.isSimulationRunning() and self._old_current_layer != self._layer_view._current_layer_num:
|
if not self._layer_view.isSimulationRunning() and self._old_current_layer != self._layer_view.getCurrentLayer():
|
||||||
self._current_shader = self._layer_shader
|
self._current_shader = self._layer_shader
|
||||||
self._switching_layers = True
|
self._switching_layers = True
|
||||||
|
|
||||||
@ -193,8 +203,8 @@ class SimulationPass(RenderPass):
|
|||||||
current_layer_batch.addItem(node.getWorldTransformation(), layer_data)
|
current_layer_batch.addItem(node.getWorldTransformation(), layer_data)
|
||||||
current_layer_batch.render(self._scene.getActiveCamera())
|
current_layer_batch.render(self._scene.getActiveCamera())
|
||||||
|
|
||||||
self._old_current_layer = self._layer_view._current_layer_num
|
self._old_current_layer = self._layer_view.getCurrentLayer()
|
||||||
self._old_current_path = self._layer_view._current_path_num
|
self._old_current_path = self._layer_view.getCurrentPath()
|
||||||
|
|
||||||
# Create a new batch that is not range-limited
|
# Create a new batch that is not range-limited
|
||||||
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid)
|
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid)
|
||||||
@ -230,4 +240,4 @@ class SimulationPass(RenderPass):
|
|||||||
if changed_object.callDecoration("getLayerData"): # Any layer data has changed.
|
if changed_object.callDecoration("getLayerData"): # Any layer data has changed.
|
||||||
self._switching_layers = True
|
self._switching_layers = True
|
||||||
self._old_current_layer = 0
|
self._old_current_layer = 0
|
||||||
self._old_current_path = 0
|
self._old_current_path = 0.0
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# Copyright (c) 2021 Ultimaker B.V.
|
# Copyright (c) 2021 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from PyQt6.QtCore import Qt
|
from PyQt6.QtCore import Qt
|
||||||
@ -58,6 +57,7 @@ class SimulationView(CuraView):
|
|||||||
LAYER_VIEW_TYPE_LINE_TYPE = 1
|
LAYER_VIEW_TYPE_LINE_TYPE = 1
|
||||||
LAYER_VIEW_TYPE_FEEDRATE = 2
|
LAYER_VIEW_TYPE_FEEDRATE = 2
|
||||||
LAYER_VIEW_TYPE_THICKNESS = 3
|
LAYER_VIEW_TYPE_THICKNESS = 3
|
||||||
|
SIMULATION_FACTOR = 3
|
||||||
|
|
||||||
_no_layers_warning_preference = "view/no_layers_warning"
|
_no_layers_warning_preference = "view/no_layers_warning"
|
||||||
|
|
||||||
@ -74,21 +74,20 @@ class SimulationView(CuraView):
|
|||||||
self._old_max_layers = 0
|
self._old_max_layers = 0
|
||||||
|
|
||||||
self._max_paths = 0
|
self._max_paths = 0
|
||||||
self._current_path_num = 0
|
self._current_path_num: float = 0.0
|
||||||
|
self._current_time = 0.0
|
||||||
self._minimum_path_num = 0
|
self._minimum_path_num = 0
|
||||||
self.currentLayerNumChanged.connect(self._onCurrentLayerNumChanged)
|
self.currentLayerNumChanged.connect(self._onCurrentLayerNumChanged)
|
||||||
|
|
||||||
self._current_feedrates = {}
|
|
||||||
self._lengths_of_polyline ={}
|
|
||||||
self._busy = False
|
self._busy = False
|
||||||
self._simulation_running = False
|
self._simulation_running = False
|
||||||
|
|
||||||
self._ghost_shader = None # type: Optional["ShaderProgram"]
|
self._ghost_shader: Optional["ShaderProgram"] = None
|
||||||
self._layer_pass = None # type: Optional[SimulationPass]
|
self._layer_pass: Optional[SimulationPass] = None
|
||||||
self._composite_pass = None # type: Optional[CompositePass]
|
self._composite_pass: Optional[CompositePass] = None
|
||||||
self._old_layer_bindings = None # type: Optional[List[str]]
|
self._old_layer_bindings: Optional[List[str]] = None
|
||||||
self._simulationview_composite_shader = None # type: Optional["ShaderProgram"]
|
self._simulationview_composite_shader: Optional["ShaderProgram"] = None
|
||||||
self._old_composite_shader = None # type: Optional["ShaderProgram"]
|
self._old_composite_shader: Optional["ShaderProgram"] = None
|
||||||
|
|
||||||
self._max_feedrate = sys.float_info.min
|
self._max_feedrate = sys.float_info.min
|
||||||
self._min_feedrate = sys.float_info.max
|
self._min_feedrate = sys.float_info.max
|
||||||
@ -98,14 +97,15 @@ class SimulationView(CuraView):
|
|||||||
self._min_line_width = sys.float_info.max
|
self._min_line_width = sys.float_info.max
|
||||||
self._min_flow_rate = sys.float_info.max
|
self._min_flow_rate = sys.float_info.max
|
||||||
self._max_flow_rate = sys.float_info.min
|
self._max_flow_rate = sys.float_info.min
|
||||||
|
self._cumulative_line_duration = {}
|
||||||
|
|
||||||
self._global_container_stack = None # type: Optional[ContainerStack]
|
self._global_container_stack: Optional[ContainerStack] = None
|
||||||
self._proxy = None
|
self._proxy = None
|
||||||
|
|
||||||
self._resetSettings()
|
self._resetSettings()
|
||||||
self._legend_items = None
|
self._legend_items = None
|
||||||
self._show_travel_moves = False
|
self._show_travel_moves = False
|
||||||
self._nozzle_node = None # type: Optional[NozzleNode]
|
self._nozzle_node: Optional[NozzleNode] = None
|
||||||
|
|
||||||
Application.getInstance().getPreferences().addPreference("view/top_layer_count", 5)
|
Application.getInstance().getPreferences().addPreference("view/top_layer_count", 5)
|
||||||
Application.getInstance().getPreferences().addPreference("view/only_show_top_layers", False)
|
Application.getInstance().getPreferences().addPreference("view/only_show_top_layers", False)
|
||||||
@ -127,13 +127,12 @@ class SimulationView(CuraView):
|
|||||||
self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers"))
|
self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers"))
|
||||||
self._compatibility_mode = self._evaluateCompatibilityMode()
|
self._compatibility_mode = self._evaluateCompatibilityMode()
|
||||||
|
|
||||||
self._slice_first_warning_message = Message(catalog.i18nc("@info:status",
|
self._slice_first_warning_message = Message(catalog.i18nc("@info:status", "Nothing is shown because you need to slice first."),
|
||||||
"Nothing is shown because you need to slice first."),
|
title=catalog.i18nc("@info:title", "No layers to show"),
|
||||||
title = catalog.i18nc("@info:title", "No layers to show"),
|
option_text=catalog.i18nc("@info:option_text",
|
||||||
option_text = catalog.i18nc("@info:option_text",
|
"Do not show this message again"),
|
||||||
"Do not show this message again"),
|
option_state=False,
|
||||||
option_state = False,
|
message_type=Message.MessageType.WARNING)
|
||||||
message_type = Message.MessageType.WARNING)
|
|
||||||
self._slice_first_warning_message.optionToggled.connect(self._onDontAskMeAgain)
|
self._slice_first_warning_message.optionToggled.connect(self._onDontAskMeAgain)
|
||||||
CuraApplication.getInstance().getPreferences().addPreference(self._no_layers_warning_preference, True)
|
CuraApplication.getInstance().getPreferences().addPreference(self._no_layers_warning_preference, True)
|
||||||
|
|
||||||
@ -189,9 +188,85 @@ class SimulationView(CuraView):
|
|||||||
def getMaxLayers(self) -> int:
|
def getMaxLayers(self) -> int:
|
||||||
return self._max_layers
|
return self._max_layers
|
||||||
|
|
||||||
def getCurrentPath(self) -> int:
|
def getCurrentPath(self) -> float:
|
||||||
return self._current_path_num
|
return self._current_path_num
|
||||||
|
|
||||||
|
def setTime(self, time: float) -> None:
|
||||||
|
cumulative_line_duration = self.cumulativeLineDuration()
|
||||||
|
if len(cumulative_line_duration) > 0:
|
||||||
|
self._current_time = time
|
||||||
|
left_i = 0
|
||||||
|
right_i = self._max_paths - 1
|
||||||
|
total_duration = cumulative_line_duration[-1]
|
||||||
|
# make an educated guess about where to start
|
||||||
|
i = int(right_i * max(0.0, min(1.0, self._current_time / total_duration)))
|
||||||
|
# binary search for the correct path
|
||||||
|
while left_i < right_i:
|
||||||
|
if cumulative_line_duration[i] <= self._current_time:
|
||||||
|
left_i = i + 1
|
||||||
|
else:
|
||||||
|
right_i = i
|
||||||
|
i = int((left_i + right_i) / 2)
|
||||||
|
|
||||||
|
left_value = cumulative_line_duration[i - 1] if i > 0 else 0.0
|
||||||
|
right_value = cumulative_line_duration[i]
|
||||||
|
|
||||||
|
assert (left_value <= self._current_time <= right_value)
|
||||||
|
|
||||||
|
fractional_value = (self._current_time - left_value) / (right_value - left_value)
|
||||||
|
|
||||||
|
self.setPath(i + fractional_value)
|
||||||
|
|
||||||
|
def advanceTime(self, time_increase: float) -> bool:
|
||||||
|
"""
|
||||||
|
Advance the time by the given amount.
|
||||||
|
|
||||||
|
:param time_increase: The amount of time to advance (in seconds).
|
||||||
|
:return: True if the time was advanced, False if the end of the simulation was reached.
|
||||||
|
"""
|
||||||
|
total_duration = 0.0
|
||||||
|
if len(self.cumulativeLineDuration()) > 0:
|
||||||
|
total_duration = self.cumulativeLineDuration()[-1]
|
||||||
|
|
||||||
|
if self._current_time + time_increase > total_duration:
|
||||||
|
# If we have reached the end of the simulation, go to the next layer.
|
||||||
|
if self.getCurrentLayer() == self.getMaxLayers():
|
||||||
|
# If we are already at the last layer, go to the first layer.
|
||||||
|
self.setTime(total_duration)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# advance to the next layer, and reset the time
|
||||||
|
self.setLayer(self.getCurrentLayer() + 1)
|
||||||
|
self.setTime(0.0)
|
||||||
|
else:
|
||||||
|
self.setTime(self._current_time + time_increase)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def cumulativeLineDuration(self) -> List[float]:
|
||||||
|
# Make sure _cumulative_line_duration is initialized properly
|
||||||
|
if self.getCurrentLayer() not in self._cumulative_line_duration:
|
||||||
|
#clear cache
|
||||||
|
self._cumulative_line_duration = {}
|
||||||
|
self._cumulative_line_duration[self.getCurrentLayer()] = []
|
||||||
|
total_duration = 0.0
|
||||||
|
polylines = self.getLayerData()
|
||||||
|
if polylines is not None:
|
||||||
|
for polyline in polylines.polygons:
|
||||||
|
for line_duration in list((polyline.lineLengths / polyline.lineFeedrates)[0]):
|
||||||
|
total_duration += line_duration / SimulationView.SIMULATION_FACTOR
|
||||||
|
self._cumulative_line_duration[self.getCurrentLayer()].append(total_duration)
|
||||||
|
|
||||||
|
return self._cumulative_line_duration[self.getCurrentLayer()]
|
||||||
|
|
||||||
|
def getLayerData(self) -> Optional["LayerData"]:
|
||||||
|
scene = self.getController().getScene()
|
||||||
|
for node in DepthFirstIterator(scene.getRoot()): # type: ignore
|
||||||
|
layer_data = node.callDecoration("getLayerData")
|
||||||
|
if not layer_data:
|
||||||
|
continue
|
||||||
|
return layer_data.getLayer(self.getCurrentLayer())
|
||||||
|
return None
|
||||||
|
|
||||||
def getMinimumPath(self) -> int:
|
def getMinimumPath(self) -> int:
|
||||||
return self._minimum_path_num
|
return self._minimum_path_num
|
||||||
|
|
||||||
@ -279,7 +354,7 @@ class SimulationView(CuraView):
|
|||||||
self._startUpdateTopLayers()
|
self._startUpdateTopLayers()
|
||||||
self.currentLayerNumChanged.emit()
|
self.currentLayerNumChanged.emit()
|
||||||
|
|
||||||
def setPath(self, value: int) -> None:
|
def setPath(self, value: float) -> None:
|
||||||
"""
|
"""
|
||||||
Set the upper end of the range of visible paths on the current layer.
|
Set the upper end of the range of visible paths on the current layer.
|
||||||
|
|
||||||
@ -289,6 +364,9 @@ class SimulationView(CuraView):
|
|||||||
if self._current_path_num != value:
|
if self._current_path_num != value:
|
||||||
self._current_path_num = min(max(value, 0), self._max_paths)
|
self._current_path_num = min(max(value, 0), self._max_paths)
|
||||||
self._minimum_path_num = min(self._minimum_path_num, self._current_path_num)
|
self._minimum_path_num = min(self._minimum_path_num, self._current_path_num)
|
||||||
|
# update _current time when the path is changed by user
|
||||||
|
if self._current_path_num < self._max_paths and round(self._current_path_num)== self._current_path_num:
|
||||||
|
self._current_time = self.cumulativeLineDuration()[int(self._current_path_num)]
|
||||||
|
|
||||||
self._startUpdateTopLayers()
|
self._startUpdateTopLayers()
|
||||||
self.currentPathNumChanged.emit()
|
self.currentPathNumChanged.emit()
|
||||||
@ -402,15 +480,6 @@ class SimulationView(CuraView):
|
|||||||
def getMaxFeedrate(self) -> float:
|
def getMaxFeedrate(self) -> float:
|
||||||
return self._max_feedrate
|
return self._max_feedrate
|
||||||
|
|
||||||
def getSimulationTime(self, currentIndex) -> float:
|
|
||||||
try:
|
|
||||||
return (self._lengths_of_polyline[self._current_layer_num][currentIndex] / self._current_feedrates[self._current_layer_num][currentIndex])[0]
|
|
||||||
|
|
||||||
except:
|
|
||||||
# In case of change in layers, currentIndex comes one more than the items in the lengths_of_polyline
|
|
||||||
# We give 1 second time for layer change
|
|
||||||
return 1.0
|
|
||||||
|
|
||||||
def getMinThickness(self) -> float:
|
def getMinThickness(self) -> float:
|
||||||
if abs(self._min_thickness - sys.float_info.max) < 10: # Some lenience due to floating point rounding.
|
if abs(self._min_thickness - sys.float_info.max) < 10: # Some lenience due to floating point rounding.
|
||||||
return 0.0 # If it's still max-float, there are no measurements. Use 0 then.
|
return 0.0 # If it's still max-float, there are no measurements. Use 0 then.
|
||||||
@ -503,6 +572,7 @@ class SimulationView(CuraView):
|
|||||||
self._max_thickness = sys.float_info.min
|
self._max_thickness = sys.float_info.min
|
||||||
self._min_flow_rate = sys.float_info.max
|
self._min_flow_rate = sys.float_info.max
|
||||||
self._max_flow_rate = sys.float_info.min
|
self._max_flow_rate = sys.float_info.min
|
||||||
|
self._cumulative_line_duration = {}
|
||||||
|
|
||||||
# The colour scheme is only influenced by the visible lines, so filter the lines by if they should be visible.
|
# The colour scheme is only influenced by the visible lines, so filter the lines by if they should be visible.
|
||||||
visible_line_types = []
|
visible_line_types = []
|
||||||
@ -535,10 +605,8 @@ class SimulationView(CuraView):
|
|||||||
visible_indicies_with_extrusion = numpy.where(numpy.isin(polyline.types, visible_line_types_with_extrusion))[0]
|
visible_indicies_with_extrusion = numpy.where(numpy.isin(polyline.types, visible_line_types_with_extrusion))[0]
|
||||||
if visible_indices.size == 0: # No items to take maximum or minimum of.
|
if visible_indices.size == 0: # No items to take maximum or minimum of.
|
||||||
continue
|
continue
|
||||||
self._lengths_of_polyline[layer_index] = polyline.lineLengths
|
|
||||||
visible_feedrates = numpy.take(polyline.lineFeedrates, visible_indices)
|
visible_feedrates = numpy.take(polyline.lineFeedrates, visible_indices)
|
||||||
visible_feedrates_with_extrusion = numpy.take(polyline.lineFeedrates, visible_indicies_with_extrusion)
|
visible_feedrates_with_extrusion = numpy.take(polyline.lineFeedrates, visible_indicies_with_extrusion)
|
||||||
self._current_feedrates[layer_index] = polyline.lineFeedrates
|
|
||||||
visible_linewidths = numpy.take(polyline.lineWidths, visible_indices)
|
visible_linewidths = numpy.take(polyline.lineWidths, visible_indices)
|
||||||
visible_linewidths_with_extrusion = numpy.take(polyline.lineWidths, visible_indicies_with_extrusion)
|
visible_linewidths_with_extrusion = numpy.take(polyline.lineWidths, visible_indicies_with_extrusion)
|
||||||
visible_thicknesses = numpy.take(polyline.lineThicknesses, visible_indices)
|
visible_thicknesses = numpy.take(polyline.lineThicknesses, visible_indices)
|
||||||
|
@ -127,6 +127,7 @@ Item
|
|||||||
function resumeSimulation()
|
function resumeSimulation()
|
||||||
{
|
{
|
||||||
UM.SimulationView.setSimulationRunning(true)
|
UM.SimulationView.setSimulationRunning(true)
|
||||||
|
UM.SimulationView.setCurrentPath(UM.SimulationView.currentPath)
|
||||||
simulationTimer.start()
|
simulationTimer.start()
|
||||||
layerSlider.manuallyChanged = false
|
layerSlider.manuallyChanged = false
|
||||||
pathSlider.manuallyChanged = false
|
pathSlider.manuallyChanged = false
|
||||||
@ -136,54 +137,19 @@ Item
|
|||||||
Timer
|
Timer
|
||||||
{
|
{
|
||||||
id: simulationTimer
|
id: simulationTimer
|
||||||
interval: UM.SimulationView.simulationTime
|
interval: 1000 / 15
|
||||||
running: false
|
running: false
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered:
|
onTriggered:
|
||||||
{
|
{
|
||||||
var currentPath = UM.SimulationView.currentPath
|
// divide by 1000 to account for ms to s conversion
|
||||||
var numPaths = UM.SimulationView.numPaths
|
const advance_time = simulationTimer.interval / 1000.0;
|
||||||
var currentLayer = UM.SimulationView.currentLayer
|
if (!UM.SimulationView.advanceTime(advance_time)) {
|
||||||
var numLayers = UM.SimulationView.numLayers
|
playButton.pauseSimulation();
|
||||||
|
|
||||||
// When the user plays the simulation, if the path slider is at the end of this layer, we start
|
|
||||||
// the simulation at the beginning of the current layer.
|
|
||||||
if (!isSimulationPlaying)
|
|
||||||
{
|
|
||||||
if (currentPath >= numPaths)
|
|
||||||
{
|
|
||||||
UM.SimulationView.setCurrentPath(0)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UM.SimulationView.setCurrentPath(currentPath + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the simulation is already playing and we reach the end of a layer, then it automatically
|
|
||||||
// starts at the beginning of the next layer.
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (currentPath >= numPaths)
|
|
||||||
{
|
|
||||||
// At the end of the model, the simulation stops
|
|
||||||
if (currentLayer >= numLayers)
|
|
||||||
{
|
|
||||||
playButton.pauseSimulation()
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UM.SimulationView.setCurrentLayer(currentLayer + 1)
|
|
||||||
UM.SimulationView.setCurrentPath(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UM.SimulationView.setCurrentPath(currentPath + 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// The status must be set here instead of in the resumeSimulation function otherwise it won't work
|
// The status must be set here instead of in the resumeSimulation function otherwise it won't work
|
||||||
// correctly, because part of the logic is in this trigger function.
|
// correctly, because part of the logic is in this trigger function.
|
||||||
isSimulationPlaying = true
|
isSimulationPlaying = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,11 +11,6 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class SimulationViewProxy(QObject):
|
class SimulationViewProxy(QObject):
|
||||||
|
|
||||||
S_TO_MS = 1000
|
|
||||||
SPEED_OF_SIMULATION = 10
|
|
||||||
FACTOR = S_TO_MS/SPEED_OF_SIMULATION
|
|
||||||
|
|
||||||
def __init__(self, simulation_view: "SimulationView", parent=None) -> None:
|
def __init__(self, simulation_view: "SimulationView", parent=None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._simulation_view = simulation_view
|
self._simulation_view = simulation_view
|
||||||
@ -55,17 +50,13 @@ class SimulationViewProxy(QObject):
|
|||||||
def numPaths(self):
|
def numPaths(self):
|
||||||
return self._simulation_view.getMaxPaths()
|
return self._simulation_view.getMaxPaths()
|
||||||
|
|
||||||
@pyqtProperty(int, notify=currentPathChanged)
|
@pyqtProperty(float, notify=currentPathChanged)
|
||||||
def currentPath(self):
|
def currentPath(self):
|
||||||
return self._simulation_view.getCurrentPath()
|
return self._simulation_view.getCurrentPath()
|
||||||
|
|
||||||
@pyqtProperty(int, notify=currentPathChanged)
|
@pyqtSlot(float, result=bool)
|
||||||
def simulationTime(self):
|
def advanceTime(self, duration: float) -> bool:
|
||||||
# Extracts the currents paths simulation time (in seconds) for the current path from the dict of simulation time of the current layer.
|
return self._simulation_view.advanceTime(duration)
|
||||||
# We multiply the time with 100 to make it to ms from s.(Should be 1000 in real time). This scaling makes the simulation time 10x faster than the real time.
|
|
||||||
simulationTimeOfpath = self._simulation_view.getSimulationTime(self._simulation_view.getCurrentPath()) * SimulationViewProxy.FACTOR
|
|
||||||
# Since the timer cannot process time less than 1 ms, we put a lower limit here
|
|
||||||
return int(max(1, simulationTimeOfpath))
|
|
||||||
|
|
||||||
@pyqtProperty(int, notify=currentPathChanged)
|
@pyqtProperty(int, notify=currentPathChanged)
|
||||||
def minimumPath(self):
|
def minimumPath(self):
|
||||||
@ -91,8 +82,8 @@ class SimulationViewProxy(QObject):
|
|||||||
def setMinimumLayer(self, layer_num):
|
def setMinimumLayer(self, layer_num):
|
||||||
self._simulation_view.setMinimumLayer(layer_num)
|
self._simulation_view.setMinimumLayer(layer_num)
|
||||||
|
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(float)
|
||||||
def setCurrentPath(self, path_num):
|
def setCurrentPath(self, path_num: float):
|
||||||
self._simulation_view.setPath(path_num)
|
self._simulation_view.setPath(path_num)
|
||||||
|
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
@ -228,4 +219,3 @@ class SimulationViewProxy(QObject):
|
|||||||
self._simulation_view.activityChanged.disconnect(self._onActivityChanged)
|
self._simulation_view.activityChanged.disconnect(self._onActivityChanged)
|
||||||
self._simulation_view.globalStackChanged.disconnect(self._onGlobalStackChanged)
|
self._simulation_view.globalStackChanged.disconnect(self._onGlobalStackChanged)
|
||||||
self._simulation_view.preferencesChanged.disconnect(self._onPreferencesChanged)
|
self._simulation_view.preferencesChanged.disconnect(self._onPreferencesChanged)
|
||||||
|
|
||||||
|
@ -331,7 +331,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
[printer, *_] = self._printers
|
[printer, *_] = self._printers
|
||||||
return printer.pinterType in ("ultimaker_methodx", "ultimaker_methodxl")
|
return printer.name in ("ultimaker_methodx", "ultimaker_methodxl")
|
||||||
|
|
||||||
@pyqtProperty(bool, notify=_cloudClusterPrintersChanged)
|
@pyqtProperty(bool, notify=_cloudClusterPrintersChanged)
|
||||||
def supportsPrintJobActions(self) -> bool:
|
def supportsPrintJobActions(self) -> bool:
|
||||||
|
@ -1,41 +1,47 @@
|
|||||||
### Direct requirements for Uranium and libCharon ###
|
### Direct requirements for Uranium and libCharon ###
|
||||||
PyQt6-sip==13.4.1 \
|
PyQt6-sip==13.6.0 \
|
||||||
--hash=sha256:0df998f2b6ceeacfd10de773441572e215be0c9cae566cc7dd36e231bf714a12 \
|
--hash=sha256:0dfd22cfedd87e96f9d51e0778ca2ba3dc0be83e424e9e0f98f6994d8d9c90f0 \
|
||||||
--hash=sha256:224575e84805c4317bacd5d1b8e93e0ad5c48685dadbbe1e902d4ebe16f22828 \
|
--hash=sha256:13885361ca2cb2f5085d50359ba61b3fabd41b139fb58f37332acbe631ef2357 \
|
||||||
--hash=sha256:36ae29cdc223cacc1257d0f5075cf81474550c6d26b728f922487a2aa935f130 \
|
--hash=sha256:24441032a29791e82beb7dfd76878339058def0e97fdb7c1cea517f3a0e6e96b \
|
||||||
--hash=sha256:3a674c591d4274d4ea8127205290e927a7dab0eb87a0038d4f4ea1d430782649 \
|
--hash=sha256:2486e1588071943d4f6657ba09096dc9fffd2322ad2c30041e78ea3f037b5778 \
|
||||||
--hash=sha256:3ef9392e4ae29d393b79237d85840cdc6b8831f36eed5d56c7d9b329b380cc8d \
|
--hash=sha256:3075d8b325382750829e6cde6971c943352309d35768a4d4da0587459606d562 \
|
||||||
--hash=sha256:43935873d60f57719632840d517afee04ef8f30e92cfe0dadc7e6326691920fc \
|
--hash=sha256:33ea771fe777eb0d1a2c3ef35bcc3f7a286eb3ff09cd5b2fdd3d87d1f392d7e8 \
|
||||||
--hash=sha256:5731f22618435654352ef07684549a17be82b75254227fc80b4b5b0b59fc6656 \
|
--hash=sha256:39854dba35f8e5a4288da26ecb5f40b4c5ec1932efffb3f49d5ea435a7f37fb3 \
|
||||||
--hash=sha256:5bc4beb6fb1de4c9ba8beee7b1a4a813fa888c3b095206dafcd25d7e6e4ed2a7 \
|
--hash=sha256:3bf03e130fbfd75c9c06e687b86ba375410c7a9e835e4e03285889e61dd4b0c4 \
|
||||||
--hash=sha256:5c36ab984402e96792eebf4b031abfaa589aa20af3190a79c54502c16964d97e \
|
--hash=sha256:43fb8551796030aae3d66d6e35e277494071ec6172cd182c9569ab7db268a2f5 \
|
||||||
--hash=sha256:a2a0461992c6657f343308b150c4d6b57e9e7a0e5c2f79538434e7fb869ea827 \
|
--hash=sha256:58f68a48400e0b3d1ccb18090090299bad26e3aed7ccb7057c65887b79b8aeea \
|
||||||
--hash=sha256:a81490ee84d7a41a126b116081bd97d758f41bf706aee0a8cec24d6e4c660184 \
|
--hash=sha256:5b9c6b6f9cfccb48cbb78a59603145a698fb4ffd176764d7083e5bf47631d8df \
|
||||||
--hash=sha256:e00e287ea05bbc293fc6e2198301962af9b7b622bd2daf4288f925a88ae35dc9 \
|
--hash=sha256:747f6ca44af81777a2c696bd501bc4815a53ec6fc94d4e25830e10bc1391f8ab \
|
||||||
--hash=sha256:e670a7b2fb7e32204ce67d274017bfff3e21139d217d60cebbfcb75b019c91ee \
|
--hash=sha256:86a7b67c64436e32bffa9c28c9f21bf14a9faa54991520b12c3f6f435f24df7f \
|
||||||
--hash=sha256:ee06f255787a0b4957f357f93b78d2a11ca3761916833e3afa83f1381d4d1a46 \
|
--hash=sha256:8c282062125eea5baf830c6998587d98c50be7c3a817a057fb95fef647184012 \
|
||||||
--hash=sha256:fbee0d554e0e98f56dbf6d94b00a28cc32425938ad7ae98fd91f8822c5b24d45 \
|
--hash=sha256:8f9df9f7ccd8a9f0f1d36948c686f03ce1a1281543a3e636b7b7d5e086e1a436 \
|
||||||
--hash=sha256:fcc6d78314783f4a193f02353f431b7ea4d357f47c3c7a7d0740e723f69c64dc
|
--hash=sha256:98bf954103b087162fa63b3a78f30b0b63da22fd6450b610ec1b851dbb798228 \
|
||||||
PyQt6==6.4.2 \
|
--hash=sha256:9adf672f9114687533a74d5c2d4c03a9a929ad5ad9c3e88098a7da1a440ab916 \
|
||||||
--hash=sha256:18d1daf98d9236d55102cdadafd1056f5802f3c9288fcf7238569937b71a89f0 \
|
--hash=sha256:a6ce80bc24618d8a41be8ca51ad9f10e8bc4296dd90ab2809573df30a23ae0e5 \
|
||||||
--hash=sha256:25bd399b4a95dce65d5f937c1aa85d3c7e14a21745ae2a4ca14c0116cd104290 \
|
--hash=sha256:d6b5f699aaed0ac1fcd23e8fbca70d8a77965831b7c1ce474b81b1678817a49d \
|
||||||
--hash=sha256:740244f608fe15ee1d89695c43f31a14caeca41c4f02ac36c86dfba4a5d5813d \
|
--hash=sha256:fa759b6339ff7e25f9afe2a6b651b775f0a36bcb3f5fa85e81a90d3b033c83f4 \
|
||||||
--hash=sha256:c128bc0f17833e324593e3db83e99470d451a197dd17ff0333927b946c935bd9
|
--hash=sha256:fa7b10af7488efc5e53b41dd42c0f421bde6c2865a107af7ae259aff9d841da9
|
||||||
PyQt6-Qt6==6.4.2 \
|
PyQt6==6.6.0 \
|
||||||
--hash=sha256:9f07c3c100cb46cca4074965e7494d4df4f0fc016497d5303c1fe135822876e1 \
|
--hash=sha256:33655db05ac2de699320f035250c21434c77144a6a2943aca3f4c579dabc3f7b \
|
||||||
--hash=sha256:a29b8c858babd523e80c8db5f8fd19792641588ec04eab49af18b7a4423eb99f \
|
--hash=sha256:3ef68830a9b32050c30f7962c56a5927802c9193b68eaf405faecb8ce9ae10a8 \
|
||||||
--hash=sha256:c0e91d0275d428496cacff717a9b719c52bfa52b21f124d638b79cc2217bc81e \
|
--hash=sha256:d41512d66044c2df9c5f515a56a922170d68a37b3406ffddc8b4adc57181b576 \
|
||||||
--hash=sha256:d19c4e72615762cd6f0b043f23fa5f0b02656091427ce6de1efccd58e10e6a53
|
--hash=sha256:fc7185d65755f26d7a6842492ec5398c92544dc4eafbbcbef1b1922aca585c96
|
||||||
PyQt6-NetworkAuth==6.4.0 \
|
PyQt6-Qt6==6.6.0 \
|
||||||
--hash=sha256:ab6178b3b2902ae9939a148555cfcee8c7803d6b0d5924cd1bd8f3407b8b9210 \
|
--hash=sha256:1b079a33088d32ff47872cdb37fd15aa42101f0be46c3340244483849b781438 \
|
||||||
--hash=sha256:c16ec80232d88024b60d04386a23cc93067e5644a65f47f26ffb13d84dcd4a6d \
|
--hash=sha256:8cb30d64a4d32465ea1686bc827cbe452225fb387c4873356b0fa7b9ae63534f \
|
||||||
--hash=sha256:c302cd0d838c7229eda5e26e0b1b3d3ec4f8720f8d9379472bce5a89ff0735c2 \
|
--hash=sha256:a151f34712cd645111e89cb30b02e5fb69c9dcc3603ab3c03a561e874bd7cbcf \
|
||||||
--hash=sha256:d948fc0cf43b64afbda2acb5bf2392f785a1e7a2950d79ea850c1a3f4ae12f1a
|
--hash=sha256:e5483ae04bf107411c7469f1be9f9e2eb9840303e788b3ac524fe30af90d45f4
|
||||||
PyQt6-NetworkAuth-Qt6==6.4.2 \
|
PyQt6-NetworkAuth==6.6.0 \
|
||||||
--hash=sha256:179094bcb4d4d056316c22d3d067cd94d4591da23f804461bfb025ccfa29b2b4 \
|
--hash=sha256:7b90b81792fe53105287c8cbb5e4b22bc44a482268ffb7d3e33f852807f86182 \
|
||||||
--hash=sha256:1de6abbb5fa6585b97ae49d3f64b0dfad40bd56b1a31744d9775ff26247241c8 \
|
--hash=sha256:c7e2335159aa795e2fe6fb069ccce6308672ab80f26c50fab57caf957371cbb5 \
|
||||||
--hash=sha256:79ec4b0fc9450bbedbff03541b93b10d1c7e761cd2cc16ce70d2b09dcdf8c720 \
|
--hash=sha256:cdfc0bfaea16a9e09f075bdafefb996aa9fdec392052ba4fb3cbac233c1958fb \
|
||||||
--hash=sha256:d96d557fe61edb9b68d189f270f0393d6579c8d308e6b0d41bc0699371d7cb4e
|
--hash=sha256:f60ff9a62f5129dc2a9d4c495fb47f9a03e4dfb666b50fb7d61f46e89bf7b6a2
|
||||||
|
PyQt6-NetworkAuth-Qt6==6.6.0 \
|
||||||
|
--hash=sha256:481d9093e1fb1ac6843d8beabcd359cc34b74b9a2cbb3e2b68d96bd3f178d4e0 \
|
||||||
|
--hash=sha256:4cc48fd375730a0ba5fbed9d64abb2914f587377560a78a63aff893f9e276a45 \
|
||||||
|
--hash=sha256:5006deabf55304d4a3e0b3c954f93e5835546b11e789d14653a2493d12d3a063 \
|
||||||
|
--hash=sha256:bcd56bfc892fec961c51eba3c0bf32ba8317a762d9e254d3830569611ed569d6
|
||||||
|
|
||||||
certifi==2023.5.7; \
|
certifi==2023.5.7; \
|
||||||
--hash=sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716
|
--hash=sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716
|
||||||
cryptography==41.0.1 \
|
cryptography==41.0.1 \
|
||||||
|
@ -8305,6 +8305,88 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ppr":
|
||||||
|
{
|
||||||
|
"label": "Print Process Reporting",
|
||||||
|
"type": "category",
|
||||||
|
"icon": "DocumentFilled",
|
||||||
|
"description": "Reporting events that go out of set thresholds",
|
||||||
|
"enabled": false,
|
||||||
|
"children":
|
||||||
|
{
|
||||||
|
"ppr_enable":
|
||||||
|
{
|
||||||
|
"label": "Enable Print Process Reporting",
|
||||||
|
"description": "Enable print process reporting for setting threshold values for possible fault detection.",
|
||||||
|
"type": "bool",
|
||||||
|
"enabled": false,
|
||||||
|
"default_value": false,
|
||||||
|
"value": false,
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": false
|
||||||
|
},
|
||||||
|
"flow_warn_limit":
|
||||||
|
{
|
||||||
|
"label": "Flow Warning",
|
||||||
|
"description": "Limit on the flow warning for detection.",
|
||||||
|
"default_value": "15.0",
|
||||||
|
"enabled": "ppr_enable",
|
||||||
|
"unit": "%",
|
||||||
|
"type": "float",
|
||||||
|
"settable_per_extruder": true
|
||||||
|
},
|
||||||
|
"flow_anomaly_limit":
|
||||||
|
{
|
||||||
|
"label": "Flow Limit",
|
||||||
|
"description": "Limit on flow anomaly for detection.",
|
||||||
|
"default_value": "25.0",
|
||||||
|
"enabled": "ppr_enable",
|
||||||
|
"unit": "%",
|
||||||
|
"type": "float",
|
||||||
|
"settable_per_extruder": true
|
||||||
|
},
|
||||||
|
"print_temp_warn_limit":
|
||||||
|
{
|
||||||
|
"label": "Print temperature Warning",
|
||||||
|
"description": "Limit on Print temperature warning for detection.",
|
||||||
|
"unit": "\u00b0C",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": "3.0",
|
||||||
|
"enabled": "ppr_enable",
|
||||||
|
"settable_per_extruder": true
|
||||||
|
},
|
||||||
|
"print_temp_anomaly_limit":
|
||||||
|
{
|
||||||
|
"label": "Print temperature Limit",
|
||||||
|
"description": "Limit on Print Temperature anomaly for detection.",
|
||||||
|
"unit": "\u00b0C",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": "7.0",
|
||||||
|
"enabled": "ppr_enable",
|
||||||
|
"settable_per_extruder": true
|
||||||
|
},
|
||||||
|
"bv_temp_warn_limit":
|
||||||
|
{
|
||||||
|
"label": "Build Volume temperature Warning",
|
||||||
|
"description": "Limit on Build Volume Temperature warning for detection.",
|
||||||
|
"unit": "\u00b0C",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": "7.5",
|
||||||
|
"enabled": "ppr_enable",
|
||||||
|
"settable_per_extruder": false
|
||||||
|
},
|
||||||
|
"bv_temp_anomaly_limit":
|
||||||
|
{
|
||||||
|
"label": "Build Volume temperature Limit",
|
||||||
|
"description": "Limit on Build Volume temperature Anomaly for detection.",
|
||||||
|
"unit": "\u00b0C",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": "10.0",
|
||||||
|
"enabled": "ppr_enable",
|
||||||
|
"settable_per_extruder": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"command_line_settings":
|
"command_line_settings":
|
||||||
{
|
{
|
||||||
"label": "Command Line Settings",
|
"label": "Command Line Settings",
|
||||||
|
@ -354,11 +354,12 @@
|
|||||||
"print_sequence": { "enabled": false },
|
"print_sequence": { "enabled": false },
|
||||||
"raft_base_line_spacing": { "value": "2*raft_base_line_width" },
|
"raft_base_line_spacing": { "value": "2*raft_base_line_width" },
|
||||||
"raft_base_line_width": { "value": 1.4 },
|
"raft_base_line_width": { "value": 1.4 },
|
||||||
"raft_base_speed": { "value": 5 },
|
"raft_base_speed": { "value": 10 },
|
||||||
"raft_base_thickness": { "value": 0.8 },
|
"raft_base_thickness": { "value": 0.8 },
|
||||||
"raft_interface_extruder_nr": { "value": "raft_surface_extruder_nr" },
|
"raft_interface_extruder_nr": { "value": "raft_surface_extruder_nr" },
|
||||||
"raft_interface_layers": { "value": 2 },
|
"raft_interface_layers": { "value": 2 },
|
||||||
"raft_interface_line_width": { "value": 1.2 },
|
"raft_interface_line_width": { "value": 0.7 },
|
||||||
|
"raft_interface_speed": { "value": 90 },
|
||||||
"raft_interface_thickness": { "value": 0.3 },
|
"raft_interface_thickness": { "value": 0.3 },
|
||||||
"raft_margin": { "value": 3 },
|
"raft_margin": { "value": 3 },
|
||||||
"raft_surface_extruder_nr": { "value": "int(anyExtruderWithMaterial('material_is_support_material')) if support_enable and extruderValue(support_extruder_nr,'material_is_support_material') else raft_base_extruder_nr" },
|
"raft_surface_extruder_nr": { "value": "int(anyExtruderWithMaterial('material_is_support_material')) if support_enable and extruderValue(support_extruder_nr,'material_is_support_material') else raft_base_extruder_nr" },
|
||||||
|
@ -11,7 +11,7 @@ Cura.ExpandablePopup
|
|||||||
{
|
{
|
||||||
id: machineSelector
|
id: machineSelector
|
||||||
|
|
||||||
property Cura.MachineManager machineManager
|
property var machineManager: Cura.MachineManager
|
||||||
property bool isNetworkPrinter: machineManager.activeMachineHasNetworkConnection
|
property bool isNetworkPrinter: machineManager.activeMachineHasNetworkConnection
|
||||||
property bool isConnectedCloudPrinter: machineManager.activeMachineHasCloudConnection
|
property bool isConnectedCloudPrinter: machineManager.activeMachineHasCloudConnection
|
||||||
property bool isCloudRegistered: machineManager.activeMachineHasCloudRegistration
|
property bool isCloudRegistered: machineManager.activeMachineHasCloudRegistration
|
||||||
@ -107,6 +107,7 @@ Cura.ExpandablePopup
|
|||||||
{
|
{
|
||||||
return UM.Theme.getIcon("Printer", "medium")
|
return UM.Theme.getIcon("Printer", "medium")
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return ""
|
return ""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user