mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-12 07:59:13 +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 }}
|
||||
|
||||
jobs:
|
||||
windows-installer:
|
||||
installer:
|
||||
uses: ultimaker/cura-workflows/.github/workflows/cura-installer-linux.yml@main
|
||||
with:
|
||||
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 }}
|
||||
|
||||
jobs:
|
||||
windows-installer:
|
||||
installer:
|
||||
uses: ultimaker/cura-workflows/.github/workflows/cura-installer-macos.yml@main
|
||||
with:
|
||||
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 }}
|
||||
|
||||
jobs:
|
||||
windows-installer:
|
||||
installer:
|
||||
uses: ultimaker/cura-workflows/.github/workflows/cura-installer-windows.yml@main
|
||||
with:
|
||||
cura_conan_version: ${{ inputs.cura_conan_version }}
|
||||
|
@ -266,6 +266,10 @@ app = UMBUNDLE(
|
||||
'CFBundlePackageType': 'APPL',
|
||||
'CFBundleVersionString': {{ version }},
|
||||
'CFBundleShortVersionString': {{ short_version }},
|
||||
'CFBundleURLTypes': [{
|
||||
'CFBundleURLName': '{{ display_name }}',
|
||||
'CFBundleURLSchemes': ['cura', 'slicer'],
|
||||
}],
|
||||
'CFBundleDocumentTypes': [{
|
||||
'CFBundleTypeRole': 'Viewer',
|
||||
'CFBundleTypeExtensions': ['*'],
|
||||
|
@ -5,7 +5,7 @@ requirements:
|
||||
- "cura_binary_data/(latest)@ultimaker/testing"
|
||||
- "fdm_materials/(latest)@ultimaker/testing"
|
||||
- "curaengine_plugin_gradual_flow/(latest)@ultimaker/stable"
|
||||
- "dulcificum/(latest)@ultimaker/testing"
|
||||
- "dulcificum/latest@ultimaker/testing"
|
||||
- "pyarcus/5.3.0"
|
||||
- "pysavitar/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")
|
||||
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"]))
|
||||
for bin in Path(src_path).glob(binary["binary"]):
|
||||
binaries.append((str(bin), binary["dst"]))
|
||||
|
@ -2,15 +2,18 @@
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import enum
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any, Dict
|
||||
import requests
|
||||
|
||||
import numpy
|
||||
from PyQt6.QtCore import QObject, QTimer, QUrl, pyqtSignal, pyqtProperty, QEvent, pyqtEnum, QCoreApplication
|
||||
from PyQt6.QtCore import QObject, QTimer, QUrl, QUrlQuery, pyqtSignal, pyqtProperty, QEvent, pyqtEnum, QCoreApplication, \
|
||||
QByteArray
|
||||
from PyQt6.QtGui import QColor, QIcon
|
||||
from PyQt6.QtQml import qmlRegisterUncreatableType, qmlRegisterUncreatableMetaObject, qmlRegisterSingletonType, qmlRegisterType
|
||||
from PyQt6.QtWidgets import QMessageBox
|
||||
@ -250,7 +253,7 @@ class CuraApplication(QtApplication):
|
||||
self._additional_components = {} # Components to add to certain areas in the interface
|
||||
|
||||
self._open_file_queue = [] # A list of files to open (after the application has started)
|
||||
|
||||
self._open_url_queue = [] # A list of urls to open (after the application has started)
|
||||
self._update_platform_activity_timer = None
|
||||
|
||||
self._sidebar_custom_menu_items = [] # type: list # Keeps list of custom menu items for the side bar
|
||||
@ -274,6 +277,8 @@ class CuraApplication(QtApplication):
|
||||
self._conan_installs = ApplicationMetadata.CONAN_INSTALLS
|
||||
self._python_installs = ApplicationMetadata.PYTHON_INSTALLS
|
||||
|
||||
self._supported_url_schemes: List[str] = ["cura", "slicer"]
|
||||
|
||||
@pyqtProperty(str, constant=True)
|
||||
def ultimakerCloudApiRootUrl(self) -> str:
|
||||
return UltimakerCloudConstants.CuraCloudAPIRoot
|
||||
@ -326,7 +331,11 @@ class CuraApplication(QtApplication):
|
||||
assert not "This crash is triggered by the trigger_early_crash command line argument."
|
||||
|
||||
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:
|
||||
self.__addExpectedResourceDirsAndSearchPaths() # Must be added before init of super
|
||||
@ -947,6 +956,8 @@ class CuraApplication(QtApplication):
|
||||
self.callLater(self._openFile, file_name)
|
||||
for file_name in self._open_file_queue: # Open all the files that were queued up while plug-ins were loading.
|
||||
self.callLater(self._openFile, file_name)
|
||||
for url in self._open_url_queue:
|
||||
self.callLater(self._openUrl, url)
|
||||
|
||||
initializationFinished = pyqtSignal()
|
||||
showAddPrintersUncancellableDialog = pyqtSignal() # Used to show the add printers dialog with a greyed background
|
||||
@ -1156,9 +1167,15 @@ class CuraApplication(QtApplication):
|
||||
|
||||
if event.type() == QEvent.Type.FileOpen:
|
||||
if self._plugins_loaded:
|
||||
self._openFile(event.file())
|
||||
if event.file():
|
||||
self._openFile(event.file())
|
||||
if event.url():
|
||||
self._openUrl(event.url())
|
||||
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.
|
||||
# Once we're at this point, everything should have been flushed already (past OnExitCallbackManager).
|
||||
@ -1542,7 +1559,7 @@ class CuraApplication(QtApplication):
|
||||
if not nodes:
|
||||
return
|
||||
|
||||
objects_in_filename = {} # type: Dict[str, List[CuraSceneNode]]
|
||||
objects_in_filename: Dict[str, List[CuraSceneNode]] = {}
|
||||
for node in nodes:
|
||||
mesh_data = node.getMeshData()
|
||||
if mesh_data:
|
||||
@ -1783,6 +1800,58 @@ class CuraApplication(QtApplication):
|
||||
def _openFile(self, filename):
|
||||
self.readLocalFile(QUrl.fromLocalFile(filename))
|
||||
|
||||
def _openUrl(self, url: QUrl) -> None:
|
||||
if url.scheme() not in self._supported_url_schemes:
|
||||
# only handle cura:// and slicer:// urls schemes
|
||||
return
|
||||
|
||||
match url.host() + url.path():
|
||||
case "open" | "open/":
|
||||
query = QUrlQuery(url.query())
|
||||
model_url = QUrl(query.queryItemValue("file", options=QUrl.ComponentFormattingOption.FullyDecoded))
|
||||
|
||||
def on_finish(response):
|
||||
content_disposition_header_key = QByteArray("content-disposition".encode())
|
||||
|
||||
if not response.hasRawHeader(content_disposition_header_key):
|
||||
Logger.log("w", "Could not find Content-Disposition header in response from {0}".format(
|
||||
model_url.url()))
|
||||
# Use the last part of the url as the filename, and assume it is an STL file
|
||||
filename = model_url.path().split("/")[-1] + ".stl"
|
||||
else:
|
||||
# content_disposition is in the format
|
||||
# ```
|
||||
# content_disposition attachment; "filename=[FILENAME]"
|
||||
# ```
|
||||
# Use a regex to extract the filename
|
||||
content_disposition = str(response.rawHeader(content_disposition_header_key).data(),
|
||||
encoding='utf-8')
|
||||
content_disposition_match = re.match(r'attachment; filename="(?P<filename>.*)"',
|
||||
content_disposition)
|
||||
assert content_disposition_match is not None
|
||||
filename = content_disposition_match.group("filename")
|
||||
|
||||
tmp = tempfile.NamedTemporaryFile(suffix=filename, delete=False)
|
||||
with open(tmp.name, "wb") as f:
|
||||
f.write(response.readAll())
|
||||
|
||||
self.readLocalFile(QUrl.fromLocalFile(tmp.name), add_to_recent_files=False)
|
||||
|
||||
def on_error(*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):
|
||||
# TODO: Add the profile reader to the list of plug-ins that can be used when importing profiles.
|
||||
pass
|
||||
|
@ -67,7 +67,7 @@ class LayerPolygon:
|
||||
# Buffering the colors shouldn't be necessary as it is not
|
||||
# re-used and can save a lot of memory usage.
|
||||
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
|
||||
# or type == LayerPolygon.SkinType
|
||||
@ -75,8 +75,8 @@ class LayerPolygon:
|
||||
# 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._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
|
||||
self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
|
||||
self._build_cache_line_mesh_mask: Optional[numpy.ndarray] = None
|
||||
self._build_cache_needed_points: Optional[numpy.ndarray] = None
|
||||
|
||||
def buildCache(self) -> None:
|
||||
# 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
|
||||
${INSTALL_TYPE}{% for files in mapped_out_paths.values() %}{% for file in files %}
|
||||
Delete "{{ file[1] }}"{% endfor %}{% endfor %}{% for rem_dir in rmdir_paths %}
|
||||
@ -187,8 +204,13 @@ RmDir "$SMPROGRAMS\{{ app_name }}"
|
||||
!insertmacro APP_UNASSOCIATE "stl" "Cura.model"
|
||||
!insertmacro APP_UNASSOCIATE "3mf" "Cura.project"
|
||||
|
||||
; Unassociate file associations for 'cura' protocol
|
||||
DeleteRegKey HKCR "cura"
|
||||
|
||||
; Unassociate file associations for 'slicer' protocol
|
||||
DeleteRegKey HKCR "slicer"
|
||||
|
||||
DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}"
|
||||
DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}"
|
||||
SectionEnd
|
||||
|
||||
######################################################################
|
||||
|
@ -33,6 +33,21 @@
|
||||
/>
|
||||
</Upgrade>
|
||||
|
||||
<Property Id="ASSOCIATE_URL_PROTOCOLS">
|
||||
<RegistrySearch Id="CheckCuraProtocolHandler"
|
||||
Type="raw"
|
||||
Root="HKCR"
|
||||
Key="cura"
|
||||
Name="URL Protocol"
|
||||
/>
|
||||
<RegistrySearch Id="CheckSlicerProtocolHandler"
|
||||
Type="raw"
|
||||
Root="HKCR"
|
||||
Key="slicer"
|
||||
Name="URL Protocol"
|
||||
/>
|
||||
</Property>
|
||||
|
||||
{% if "Enterprise" in app_name %}
|
||||
<Property Id="PREVIOUS_413_INSTALLED" Secure="yes" />
|
||||
<Upgrade Id="53C603BB-2B17-4206-A609-29C2E0D0B0AE">
|
||||
@ -144,11 +159,32 @@
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<!--Url Scheme-->
|
||||
<Component Id="CuraRegistration" Guid="*" Directory="APPLICATIONFOLDER">
|
||||
<RegistryKey Root="HKCR" Key="cura">
|
||||
<RegistryValue Type="string" Value="URL:Cura Protocol"/>
|
||||
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
|
||||
<RegistryValue Type="string" Key="DefaultIcon" Value="[APPLICATIONFOLDER]\{{ main_app }},1"/>
|
||||
<RegistryValue Type="string" Key="shell\open\command" Value=""[APPLICATIONFOLDER]\{{ main_app }}" "%1""/>
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
<Component Id="SlicerRegistration" Guid="*" Directory="APPLICATIONFOLDER">
|
||||
<RegistryKey Root="HKCR" Key="slicer">
|
||||
<RegistryValue Type="string" Value="URL:Slicer Protocol"/>
|
||||
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
|
||||
<RegistryValue Type="string" Key="DefaultIcon" Value="[APPLICATIONFOLDER]\{{ main_app }},1"/>
|
||||
<RegistryValue Type="string" Key="shell\open\command" Value=""[APPLICATIONFOLDER]\{{ main_app }}" "%1""/>
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
<Feature Id="ProductFeature" Title="{{ app_name }}" Level="1" ConfigurableDirectory="APPLICATIONFOLDER">
|
||||
<ComponentRef Id="CMP_UltiMaker_Cura_exe" />
|
||||
<ComponentRef Id="CMP_CuraEngine_exe" />
|
||||
<ComponentGroupRef Id="NewFilesGroup" />
|
||||
<ComponentRef Id="CMP_Shortcuts" />
|
||||
<ComponentRef Id="CuraRegistration"/>
|
||||
<ComponentRef Id="SlicerRegistration"/>
|
||||
</Feature>
|
||||
<Feature Id="UninstallOlderVersionFeature" Title="Uninstall previous versions" Level="{{ 1 if "Enterprise" in app_name else 0 }}" Description="..."/>
|
||||
</Product>
|
||||
|
@ -33,6 +33,8 @@ message Slice
|
||||
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 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
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Copyright (c) 2023 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import uuid
|
||||
|
||||
import os
|
||||
|
||||
import numpy
|
||||
@ -30,6 +32,7 @@ from cura.CuraApplication import CuraApplication
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from cura.OneAtATimeIterator import OneAtATimeIterator
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.CuraVersion import CuraVersion
|
||||
|
||||
|
||||
NON_PRINTING_MESH_SETTINGS = ["anti_overhang_mesh", "infill_mesh", "cutting_mesh"]
|
||||
@ -332,6 +335,11 @@ class StartSliceJob(Job):
|
||||
self._buildGlobalSettingsMessage(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
|
||||
for extruder_stack in global_stack.extruderList:
|
||||
self._buildExtruderMessage(extruder_stack)
|
||||
|
@ -35,7 +35,7 @@ class SimulationPass(RenderPass):
|
||||
self._nozzle_shader = None
|
||||
self._disabled_shader = None
|
||||
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._gl = OpenGL.getInstance().getBindingsObject()
|
||||
self._scene = Application.getInstance().getController().getScene()
|
||||
@ -139,7 +139,7 @@ class SimulationPass(RenderPass):
|
||||
continue
|
||||
|
||||
# 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
|
||||
end = 0
|
||||
element_counts = layer_data.getElementCounts()
|
||||
@ -147,7 +147,7 @@ class SimulationPass(RenderPass):
|
||||
# In the current layer, we show just the indicated paths
|
||||
if layer == self._layer_view._current_layer_num:
|
||||
# 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
|
||||
for polygon in layer_data.getLayer(layer).polygons:
|
||||
# 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
|
||||
continue
|
||||
# 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
|
||||
if self._layer_view._minimum_layer_num > layer:
|
||||
if self._layer_view.getMinimumLayer() > layer:
|
||||
start += element_counts[layer]
|
||||
end += element_counts[layer]
|
||||
|
||||
# Calculate the range of paths in the last layer
|
||||
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.
|
||||
# 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._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._switching_layers = True
|
||||
|
||||
@ -193,8 +203,8 @@ class SimulationPass(RenderPass):
|
||||
current_layer_batch.addItem(node.getWorldTransformation(), layer_data)
|
||||
current_layer_batch.render(self._scene.getActiveCamera())
|
||||
|
||||
self._old_current_layer = self._layer_view._current_layer_num
|
||||
self._old_current_path = self._layer_view._current_path_num
|
||||
self._old_current_layer = self._layer_view.getCurrentLayer()
|
||||
self._old_current_path = self._layer_view.getCurrentPath()
|
||||
|
||||
# Create a new batch that is not range-limited
|
||||
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.
|
||||
self._switching_layers = True
|
||||
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.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import sys
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
@ -58,6 +57,7 @@ class SimulationView(CuraView):
|
||||
LAYER_VIEW_TYPE_LINE_TYPE = 1
|
||||
LAYER_VIEW_TYPE_FEEDRATE = 2
|
||||
LAYER_VIEW_TYPE_THICKNESS = 3
|
||||
SIMULATION_FACTOR = 3
|
||||
|
||||
_no_layers_warning_preference = "view/no_layers_warning"
|
||||
|
||||
@ -74,21 +74,20 @@ class SimulationView(CuraView):
|
||||
self._old_max_layers = 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.currentLayerNumChanged.connect(self._onCurrentLayerNumChanged)
|
||||
|
||||
self._current_feedrates = {}
|
||||
self._lengths_of_polyline ={}
|
||||
self._busy = False
|
||||
self._simulation_running = False
|
||||
|
||||
self._ghost_shader = None # type: Optional["ShaderProgram"]
|
||||
self._layer_pass = None # type: Optional[SimulationPass]
|
||||
self._composite_pass = None # type: Optional[CompositePass]
|
||||
self._old_layer_bindings = None # type: Optional[List[str]]
|
||||
self._simulationview_composite_shader = None # type: Optional["ShaderProgram"]
|
||||
self._old_composite_shader = None # type: Optional["ShaderProgram"]
|
||||
self._ghost_shader: Optional["ShaderProgram"] = None
|
||||
self._layer_pass: Optional[SimulationPass] = None
|
||||
self._composite_pass: Optional[CompositePass] = None
|
||||
self._old_layer_bindings: Optional[List[str]] = None
|
||||
self._simulationview_composite_shader: Optional["ShaderProgram"] = None
|
||||
self._old_composite_shader: Optional["ShaderProgram"] = None
|
||||
|
||||
self._max_feedrate = sys.float_info.min
|
||||
self._min_feedrate = sys.float_info.max
|
||||
@ -98,14 +97,15 @@ class SimulationView(CuraView):
|
||||
self._min_line_width = sys.float_info.max
|
||||
self._min_flow_rate = sys.float_info.max
|
||||
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._resetSettings()
|
||||
self._legend_items = None
|
||||
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/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._compatibility_mode = self._evaluateCompatibilityMode()
|
||||
|
||||
self._slice_first_warning_message = Message(catalog.i18nc("@info:status",
|
||||
"Nothing is shown because you need to slice first."),
|
||||
title = catalog.i18nc("@info:title", "No layers to show"),
|
||||
option_text = catalog.i18nc("@info:option_text",
|
||||
"Do not show this message again"),
|
||||
option_state = False,
|
||||
message_type = Message.MessageType.WARNING)
|
||||
self._slice_first_warning_message = Message(catalog.i18nc("@info:status", "Nothing is shown because you need to slice first."),
|
||||
title=catalog.i18nc("@info:title", "No layers to show"),
|
||||
option_text=catalog.i18nc("@info:option_text",
|
||||
"Do not show this message again"),
|
||||
option_state=False,
|
||||
message_type=Message.MessageType.WARNING)
|
||||
self._slice_first_warning_message.optionToggled.connect(self._onDontAskMeAgain)
|
||||
CuraApplication.getInstance().getPreferences().addPreference(self._no_layers_warning_preference, True)
|
||||
|
||||
@ -189,9 +188,85 @@ class SimulationView(CuraView):
|
||||
def getMaxLayers(self) -> int:
|
||||
return self._max_layers
|
||||
|
||||
def getCurrentPath(self) -> int:
|
||||
def getCurrentPath(self) -> float:
|
||||
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:
|
||||
return self._minimum_path_num
|
||||
|
||||
@ -279,7 +354,7 @@ class SimulationView(CuraView):
|
||||
self._startUpdateTopLayers()
|
||||
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.
|
||||
|
||||
@ -289,6 +364,9 @@ class SimulationView(CuraView):
|
||||
if self._current_path_num != value:
|
||||
self._current_path_num = min(max(value, 0), self._max_paths)
|
||||
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.currentPathNumChanged.emit()
|
||||
@ -402,15 +480,6 @@ class SimulationView(CuraView):
|
||||
def getMaxFeedrate(self) -> float:
|
||||
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:
|
||||
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.
|
||||
@ -503,6 +572,7 @@ class SimulationView(CuraView):
|
||||
self._max_thickness = sys.float_info.min
|
||||
self._min_flow_rate = sys.float_info.max
|
||||
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.
|
||||
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]
|
||||
if visible_indices.size == 0: # No items to take maximum or minimum of.
|
||||
continue
|
||||
self._lengths_of_polyline[layer_index] = polyline.lineLengths
|
||||
visible_feedrates = numpy.take(polyline.lineFeedrates, visible_indices)
|
||||
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_with_extrusion = numpy.take(polyline.lineWidths, visible_indicies_with_extrusion)
|
||||
visible_thicknesses = numpy.take(polyline.lineThicknesses, visible_indices)
|
||||
|
@ -127,6 +127,7 @@ Item
|
||||
function resumeSimulation()
|
||||
{
|
||||
UM.SimulationView.setSimulationRunning(true)
|
||||
UM.SimulationView.setCurrentPath(UM.SimulationView.currentPath)
|
||||
simulationTimer.start()
|
||||
layerSlider.manuallyChanged = false
|
||||
pathSlider.manuallyChanged = false
|
||||
@ -136,54 +137,19 @@ Item
|
||||
Timer
|
||||
{
|
||||
id: simulationTimer
|
||||
interval: UM.SimulationView.simulationTime
|
||||
interval: 1000 / 15
|
||||
running: false
|
||||
repeat: true
|
||||
onTriggered:
|
||||
{
|
||||
var currentPath = UM.SimulationView.currentPath
|
||||
var numPaths = UM.SimulationView.numPaths
|
||||
var currentLayer = UM.SimulationView.currentLayer
|
||||
var numLayers = UM.SimulationView.numLayers
|
||||
|
||||
// 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)
|
||||
}
|
||||
// divide by 1000 to account for ms to s conversion
|
||||
const advance_time = simulationTimer.interval / 1000.0;
|
||||
if (!UM.SimulationView.advanceTime(advance_time)) {
|
||||
playButton.pauseSimulation();
|
||||
}
|
||||
// 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.
|
||||
isSimulationPlaying = true
|
||||
isSimulationPlaying = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,11 +11,6 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
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:
|
||||
super().__init__(parent)
|
||||
self._simulation_view = simulation_view
|
||||
@ -55,17 +50,13 @@ class SimulationViewProxy(QObject):
|
||||
def numPaths(self):
|
||||
return self._simulation_view.getMaxPaths()
|
||||
|
||||
@pyqtProperty(int, notify=currentPathChanged)
|
||||
@pyqtProperty(float, notify=currentPathChanged)
|
||||
def currentPath(self):
|
||||
return self._simulation_view.getCurrentPath()
|
||||
|
||||
@pyqtProperty(int, notify=currentPathChanged)
|
||||
def simulationTime(self):
|
||||
# Extracts the currents paths simulation time (in seconds) for the current path from the dict of simulation time of the current layer.
|
||||
# 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))
|
||||
@pyqtSlot(float, result=bool)
|
||||
def advanceTime(self, duration: float) -> bool:
|
||||
return self._simulation_view.advanceTime(duration)
|
||||
|
||||
@pyqtProperty(int, notify=currentPathChanged)
|
||||
def minimumPath(self):
|
||||
@ -91,8 +82,8 @@ class SimulationViewProxy(QObject):
|
||||
def setMinimumLayer(self, layer_num):
|
||||
self._simulation_view.setMinimumLayer(layer_num)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setCurrentPath(self, path_num):
|
||||
@pyqtSlot(float)
|
||||
def setCurrentPath(self, path_num: float):
|
||||
self._simulation_view.setPath(path_num)
|
||||
|
||||
@pyqtSlot(int)
|
||||
@ -228,4 +219,3 @@ class SimulationViewProxy(QObject):
|
||||
self._simulation_view.activityChanged.disconnect(self._onActivityChanged)
|
||||
self._simulation_view.globalStackChanged.disconnect(self._onGlobalStackChanged)
|
||||
self._simulation_view.preferencesChanged.disconnect(self._onPreferencesChanged)
|
||||
|
||||
|
@ -331,7 +331,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
||||
return False
|
||||
|
||||
[printer, *_] = self._printers
|
||||
return printer.pinterType in ("ultimaker_methodx", "ultimaker_methodxl")
|
||||
return printer.name in ("ultimaker_methodx", "ultimaker_methodxl")
|
||||
|
||||
@pyqtProperty(bool, notify=_cloudClusterPrintersChanged)
|
||||
def supportsPrintJobActions(self) -> bool:
|
||||
|
@ -1,41 +1,47 @@
|
||||
### Direct requirements for Uranium and libCharon ###
|
||||
PyQt6-sip==13.4.1 \
|
||||
--hash=sha256:0df998f2b6ceeacfd10de773441572e215be0c9cae566cc7dd36e231bf714a12 \
|
||||
--hash=sha256:224575e84805c4317bacd5d1b8e93e0ad5c48685dadbbe1e902d4ebe16f22828 \
|
||||
--hash=sha256:36ae29cdc223cacc1257d0f5075cf81474550c6d26b728f922487a2aa935f130 \
|
||||
--hash=sha256:3a674c591d4274d4ea8127205290e927a7dab0eb87a0038d4f4ea1d430782649 \
|
||||
--hash=sha256:3ef9392e4ae29d393b79237d85840cdc6b8831f36eed5d56c7d9b329b380cc8d \
|
||||
--hash=sha256:43935873d60f57719632840d517afee04ef8f30e92cfe0dadc7e6326691920fc \
|
||||
--hash=sha256:5731f22618435654352ef07684549a17be82b75254227fc80b4b5b0b59fc6656 \
|
||||
--hash=sha256:5bc4beb6fb1de4c9ba8beee7b1a4a813fa888c3b095206dafcd25d7e6e4ed2a7 \
|
||||
--hash=sha256:5c36ab984402e96792eebf4b031abfaa589aa20af3190a79c54502c16964d97e \
|
||||
--hash=sha256:a2a0461992c6657f343308b150c4d6b57e9e7a0e5c2f79538434e7fb869ea827 \
|
||||
--hash=sha256:a81490ee84d7a41a126b116081bd97d758f41bf706aee0a8cec24d6e4c660184 \
|
||||
--hash=sha256:e00e287ea05bbc293fc6e2198301962af9b7b622bd2daf4288f925a88ae35dc9 \
|
||||
--hash=sha256:e670a7b2fb7e32204ce67d274017bfff3e21139d217d60cebbfcb75b019c91ee \
|
||||
--hash=sha256:ee06f255787a0b4957f357f93b78d2a11ca3761916833e3afa83f1381d4d1a46 \
|
||||
--hash=sha256:fbee0d554e0e98f56dbf6d94b00a28cc32425938ad7ae98fd91f8822c5b24d45 \
|
||||
--hash=sha256:fcc6d78314783f4a193f02353f431b7ea4d357f47c3c7a7d0740e723f69c64dc
|
||||
PyQt6==6.4.2 \
|
||||
--hash=sha256:18d1daf98d9236d55102cdadafd1056f5802f3c9288fcf7238569937b71a89f0 \
|
||||
--hash=sha256:25bd399b4a95dce65d5f937c1aa85d3c7e14a21745ae2a4ca14c0116cd104290 \
|
||||
--hash=sha256:740244f608fe15ee1d89695c43f31a14caeca41c4f02ac36c86dfba4a5d5813d \
|
||||
--hash=sha256:c128bc0f17833e324593e3db83e99470d451a197dd17ff0333927b946c935bd9
|
||||
PyQt6-Qt6==6.4.2 \
|
||||
--hash=sha256:9f07c3c100cb46cca4074965e7494d4df4f0fc016497d5303c1fe135822876e1 \
|
||||
--hash=sha256:a29b8c858babd523e80c8db5f8fd19792641588ec04eab49af18b7a4423eb99f \
|
||||
--hash=sha256:c0e91d0275d428496cacff717a9b719c52bfa52b21f124d638b79cc2217bc81e \
|
||||
--hash=sha256:d19c4e72615762cd6f0b043f23fa5f0b02656091427ce6de1efccd58e10e6a53
|
||||
PyQt6-NetworkAuth==6.4.0 \
|
||||
--hash=sha256:ab6178b3b2902ae9939a148555cfcee8c7803d6b0d5924cd1bd8f3407b8b9210 \
|
||||
--hash=sha256:c16ec80232d88024b60d04386a23cc93067e5644a65f47f26ffb13d84dcd4a6d \
|
||||
--hash=sha256:c302cd0d838c7229eda5e26e0b1b3d3ec4f8720f8d9379472bce5a89ff0735c2 \
|
||||
--hash=sha256:d948fc0cf43b64afbda2acb5bf2392f785a1e7a2950d79ea850c1a3f4ae12f1a
|
||||
PyQt6-NetworkAuth-Qt6==6.4.2 \
|
||||
--hash=sha256:179094bcb4d4d056316c22d3d067cd94d4591da23f804461bfb025ccfa29b2b4 \
|
||||
--hash=sha256:1de6abbb5fa6585b97ae49d3f64b0dfad40bd56b1a31744d9775ff26247241c8 \
|
||||
--hash=sha256:79ec4b0fc9450bbedbff03541b93b10d1c7e761cd2cc16ce70d2b09dcdf8c720 \
|
||||
--hash=sha256:d96d557fe61edb9b68d189f270f0393d6579c8d308e6b0d41bc0699371d7cb4e
|
||||
PyQt6-sip==13.6.0 \
|
||||
--hash=sha256:0dfd22cfedd87e96f9d51e0778ca2ba3dc0be83e424e9e0f98f6994d8d9c90f0 \
|
||||
--hash=sha256:13885361ca2cb2f5085d50359ba61b3fabd41b139fb58f37332acbe631ef2357 \
|
||||
--hash=sha256:24441032a29791e82beb7dfd76878339058def0e97fdb7c1cea517f3a0e6e96b \
|
||||
--hash=sha256:2486e1588071943d4f6657ba09096dc9fffd2322ad2c30041e78ea3f037b5778 \
|
||||
--hash=sha256:3075d8b325382750829e6cde6971c943352309d35768a4d4da0587459606d562 \
|
||||
--hash=sha256:33ea771fe777eb0d1a2c3ef35bcc3f7a286eb3ff09cd5b2fdd3d87d1f392d7e8 \
|
||||
--hash=sha256:39854dba35f8e5a4288da26ecb5f40b4c5ec1932efffb3f49d5ea435a7f37fb3 \
|
||||
--hash=sha256:3bf03e130fbfd75c9c06e687b86ba375410c7a9e835e4e03285889e61dd4b0c4 \
|
||||
--hash=sha256:43fb8551796030aae3d66d6e35e277494071ec6172cd182c9569ab7db268a2f5 \
|
||||
--hash=sha256:58f68a48400e0b3d1ccb18090090299bad26e3aed7ccb7057c65887b79b8aeea \
|
||||
--hash=sha256:5b9c6b6f9cfccb48cbb78a59603145a698fb4ffd176764d7083e5bf47631d8df \
|
||||
--hash=sha256:747f6ca44af81777a2c696bd501bc4815a53ec6fc94d4e25830e10bc1391f8ab \
|
||||
--hash=sha256:86a7b67c64436e32bffa9c28c9f21bf14a9faa54991520b12c3f6f435f24df7f \
|
||||
--hash=sha256:8c282062125eea5baf830c6998587d98c50be7c3a817a057fb95fef647184012 \
|
||||
--hash=sha256:8f9df9f7ccd8a9f0f1d36948c686f03ce1a1281543a3e636b7b7d5e086e1a436 \
|
||||
--hash=sha256:98bf954103b087162fa63b3a78f30b0b63da22fd6450b610ec1b851dbb798228 \
|
||||
--hash=sha256:9adf672f9114687533a74d5c2d4c03a9a929ad5ad9c3e88098a7da1a440ab916 \
|
||||
--hash=sha256:a6ce80bc24618d8a41be8ca51ad9f10e8bc4296dd90ab2809573df30a23ae0e5 \
|
||||
--hash=sha256:d6b5f699aaed0ac1fcd23e8fbca70d8a77965831b7c1ce474b81b1678817a49d \
|
||||
--hash=sha256:fa759b6339ff7e25f9afe2a6b651b775f0a36bcb3f5fa85e81a90d3b033c83f4 \
|
||||
--hash=sha256:fa7b10af7488efc5e53b41dd42c0f421bde6c2865a107af7ae259aff9d841da9
|
||||
PyQt6==6.6.0 \
|
||||
--hash=sha256:33655db05ac2de699320f035250c21434c77144a6a2943aca3f4c579dabc3f7b \
|
||||
--hash=sha256:3ef68830a9b32050c30f7962c56a5927802c9193b68eaf405faecb8ce9ae10a8 \
|
||||
--hash=sha256:d41512d66044c2df9c5f515a56a922170d68a37b3406ffddc8b4adc57181b576 \
|
||||
--hash=sha256:fc7185d65755f26d7a6842492ec5398c92544dc4eafbbcbef1b1922aca585c96
|
||||
PyQt6-Qt6==6.6.0 \
|
||||
--hash=sha256:1b079a33088d32ff47872cdb37fd15aa42101f0be46c3340244483849b781438 \
|
||||
--hash=sha256:8cb30d64a4d32465ea1686bc827cbe452225fb387c4873356b0fa7b9ae63534f \
|
||||
--hash=sha256:a151f34712cd645111e89cb30b02e5fb69c9dcc3603ab3c03a561e874bd7cbcf \
|
||||
--hash=sha256:e5483ae04bf107411c7469f1be9f9e2eb9840303e788b3ac524fe30af90d45f4
|
||||
PyQt6-NetworkAuth==6.6.0 \
|
||||
--hash=sha256:7b90b81792fe53105287c8cbb5e4b22bc44a482268ffb7d3e33f852807f86182 \
|
||||
--hash=sha256:c7e2335159aa795e2fe6fb069ccce6308672ab80f26c50fab57caf957371cbb5 \
|
||||
--hash=sha256:cdfc0bfaea16a9e09f075bdafefb996aa9fdec392052ba4fb3cbac233c1958fb \
|
||||
--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; \
|
||||
--hash=sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716
|
||||
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":
|
||||
{
|
||||
"label": "Command Line Settings",
|
||||
|
@ -354,11 +354,12 @@
|
||||
"print_sequence": { "enabled": false },
|
||||
"raft_base_line_spacing": { "value": "2*raft_base_line_width" },
|
||||
"raft_base_line_width": { "value": 1.4 },
|
||||
"raft_base_speed": { "value": 5 },
|
||||
"raft_base_speed": { "value": 10 },
|
||||
"raft_base_thickness": { "value": 0.8 },
|
||||
"raft_interface_extruder_nr": { "value": "raft_surface_extruder_nr" },
|
||||
"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_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" },
|
||||
|
@ -11,7 +11,7 @@ Cura.ExpandablePopup
|
||||
{
|
||||
id: machineSelector
|
||||
|
||||
property Cura.MachineManager machineManager
|
||||
property var machineManager: Cura.MachineManager
|
||||
property bool isNetworkPrinter: machineManager.activeMachineHasNetworkConnection
|
||||
property bool isConnectedCloudPrinter: machineManager.activeMachineHasCloudConnection
|
||||
property bool isCloudRegistered: machineManager.activeMachineHasCloudRegistration
|
||||
@ -107,6 +107,7 @@ Cura.ExpandablePopup
|
||||
{
|
||||
return UM.Theme.getIcon("Printer", "medium")
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return ""
|
||||
|
Loading…
x
Reference in New Issue
Block a user