Merge branch 'Ultimaker:main' into main

This commit is contained in:
dmitrygribenchuk 2023-12-28 11:59:47 +02:00 committed by GitHub
commit 816d71bfd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 421 additions and 156 deletions

View File

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

View File

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

View File

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

View File

@ -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': ['*'],

View File

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

View File

@ -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"]))

View File

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

View File

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

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

View File

@ -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="&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"> <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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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" },

View File

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