Merge branch '5.7'

# Conflicts:
#	conandata.yml
This commit is contained in:
c.lamboo 2024-03-11 11:32:09 +01:00
commit b615177739
5 changed files with 46 additions and 25 deletions

View File

@ -28,6 +28,7 @@ class MaterialOutputModel(QObject):
"abs-wss1" :{"name" :"absr_175" ,"guid": "88c8919c-6a09-471a-b7b6-e801263d862d"}, "abs-wss1" :{"name" :"absr_175" ,"guid": "88c8919c-6a09-471a-b7b6-e801263d862d"},
"asa" :{"name" :"asa_175" ,"guid": "416eead4-0d8e-4f0b-8bfc-a91a519befa5"}, "asa" :{"name" :"asa_175" ,"guid": "416eead4-0d8e-4f0b-8bfc-a91a519befa5"},
"nylon-cf" :{"name" :"cffpa_175" ,"guid": "85bbae0e-938d-46fb-989f-c9b3689dc4f0"}, "nylon-cf" :{"name" :"cffpa_175" ,"guid": "85bbae0e-938d-46fb-989f-c9b3689dc4f0"},
"nylon12-cf": {"name": "nylon12-cf_175", "guid": "3c6f2877-71cc-4760-84e6-4b89ab243e3b"},
"nylon" :{"name" :"nylon_175" ,"guid": "283d439a-3490-4481-920c-c51d8cdecf9c"}, "nylon" :{"name" :"nylon_175" ,"guid": "283d439a-3490-4481-920c-c51d8cdecf9c"},
"pc" :{"name" :"pc_175" ,"guid": "62414577-94d1-490d-b1e4-7ef3ec40db02"}, "pc" :{"name" :"pc_175" ,"guid": "62414577-94d1-490d-b1e4-7ef3ec40db02"},
"petg" :{"name" :"petg_175" ,"guid": "69386c85-5b6c-421a-bec5-aeb1fb33f060"}, "petg" :{"name" :"petg_175" ,"guid": "69386c85-5b6c-421a-bec5-aeb1fb33f060"},

View File

@ -21,23 +21,31 @@ from UM.Scene.SceneNode import SceneNode
from UM.Qt.QtRenderer import QtRenderer from UM.Qt.QtRenderer import QtRenderer
class Snapshot: class Snapshot:
DEFAULT_WIDTH_HEIGHT = 300
MAX_RENDER_DISTANCE = 10000
BOUND_BOX_FACTOR = 1.75
CAMERA_FOVY = 30
ATTEMPTS_FOR_SNAPSHOT = 10
@staticmethod @staticmethod
def getImageBoundaries(image: QImage): def getNonZeroPixels(image: QImage):
# Look at the resulting image to get a good crop.
# Get the pixels as byte array
pixel_array = image.bits().asarray(image.sizeInBytes()) pixel_array = image.bits().asarray(image.sizeInBytes())
width, height = image.width(), image.height() width, height = image.width(), image.height()
# Convert to numpy array, assume it's 32 bit (it should always be)
pixels = numpy.frombuffer(pixel_array, dtype=numpy.uint8).reshape([height, width, 4]) pixels = numpy.frombuffer(pixel_array, dtype=numpy.uint8).reshape([height, width, 4])
# Find indices of non zero pixels # Find indices of non zero pixels
nonzero_pixels = numpy.nonzero(pixels) return numpy.nonzero(pixels)
@staticmethod
def getImageBoundaries(image: QImage):
nonzero_pixels = Snapshot.getNonZeroPixels(image)
min_y, min_x, min_a_ = numpy.amin(nonzero_pixels, axis=1) # type: ignore min_y, min_x, min_a_ = numpy.amin(nonzero_pixels, axis=1) # type: ignore
max_y, max_x, max_a_ = numpy.amax(nonzero_pixels, axis=1) # type: ignore max_y, max_x, max_a_ = numpy.amax(nonzero_pixels, axis=1) # type: ignore
return min_x, max_x, min_y, max_y return min_x, max_x, min_y, max_y
@staticmethod @staticmethod
def isometricSnapshot(width: int = 300, height: int = 300, *, node: Optional[SceneNode] = None) -> Optional[QImage]: def isometricSnapshot(width: int = DEFAULT_WIDTH_HEIGHT, height: int = DEFAULT_WIDTH_HEIGHT, *, node: Optional[SceneNode] = None) -> Optional[QImage]:
""" """
Create an isometric snapshot of the scene. Create an isometric snapshot of the scene.
@ -92,8 +100,8 @@ class Snapshot:
camera_width / 2, camera_width / 2,
-camera_height / 2, -camera_height / 2,
camera_height / 2, camera_height / 2,
-10000, -Snapshot.MAX_RENDER_DISTANCE,
10000 Snapshot.MAX_RENDER_DISTANCE
) )
camera.setPerspective(False) camera.setPerspective(False)
camera.setProjectionMatrix(ortho_matrix) camera.setProjectionMatrix(ortho_matrix)
@ -112,14 +120,17 @@ class Snapshot:
return render_pass.getOutput() return render_pass.getOutput()
@staticmethod
def isNodeRenderable(node):
return not getattr(node, "_outside_buildarea", False) and node.callDecoration(
"isSliceable") and node.getMeshData() and node.isVisible() and not node.callDecoration(
"isNonThumbnailVisibleMesh")
@staticmethod @staticmethod
def nodeBounds(root_node: SceneNode) -> Optional[AxisAlignedBox]: def nodeBounds(root_node: SceneNode) -> Optional[AxisAlignedBox]:
axis_aligned_box = None axis_aligned_box = None
for node in DepthFirstIterator(root_node): for node in DepthFirstIterator(root_node):
if not getattr(node, "_outside_buildarea", False): if Snapshot.isNodeRenderable(node):
if node.callDecoration(
"isSliceable") and node.getMeshData() and node.isVisible() and not node.callDecoration(
"isNonThumbnailVisibleMesh"):
if axis_aligned_box is None: if axis_aligned_box is None:
axis_aligned_box = node.getBoundingBox() axis_aligned_box = node.getBoundingBox()
else: else:
@ -127,7 +138,7 @@ class Snapshot:
return axis_aligned_box return axis_aligned_box
@staticmethod @staticmethod
def snapshot(width = 300, height = 300): def snapshot(width = DEFAULT_WIDTH_HEIGHT, height = DEFAULT_WIDTH_HEIGHT, number_of_attempts = ATTEMPTS_FOR_SNAPSHOT):
"""Return a QImage of the scene """Return a QImage of the scene
Uses PreviewPass that leaves out some elements Aspect ratio assumes a square Uses PreviewPass that leaves out some elements Aspect ratio assumes a square
@ -163,13 +174,13 @@ class Snapshot:
looking_from_offset = Vector(-1, 1, 2) looking_from_offset = Vector(-1, 1, 2)
if size > 0: if size > 0:
# determine the watch distance depending on the size # determine the watch distance depending on the size
looking_from_offset = looking_from_offset * size * 1.75 looking_from_offset = looking_from_offset * size * Snapshot.BOUND_BOX_FACTOR
camera.setPosition(look_at + looking_from_offset) camera.setPosition(look_at + looking_from_offset)
camera.lookAt(look_at) camera.lookAt(look_at)
satisfied = False satisfied = False
size = None size = None
fovy = 30 fovy = Snapshot.CAMERA_FOVY
while not satisfied: while not satisfied:
if size is not None: if size is not None:
@ -184,9 +195,14 @@ class Snapshot:
pixel_output = preview_pass.getOutput() pixel_output = preview_pass.getOutput()
try: try:
min_x, max_x, min_y, max_y = Snapshot.getImageBoundaries(pixel_output) min_x, max_x, min_y, max_y = Snapshot.getImageBoundaries(pixel_output)
except (ValueError, AttributeError): except (ValueError, AttributeError) as e:
Logger.logException("w", "Failed to crop the snapshot!") if number_of_attempts == 0:
Logger.warning( f"Failed to crop the snapshot even after {Snapshot.ATTEMPTS_FOR_SNAPSHOT} attempts!")
return None return None
else:
number_of_attempts = number_of_attempts - 1
Logger.info("Trying to get the snapshot again.")
return Snapshot.snapshot(width, height, number_of_attempts)
size = max((max_x - min_x) / render_width, (max_y - min_y) / render_height) size = max((max_x - min_x) / render_width, (max_y - min_y) / render_height)
if size > 0.5 or satisfied: if size > 0.5 or satisfied:

View File

@ -2,6 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import json import json
import re import re
import threading
from typing import Optional, cast, List, Dict, Pattern, Set from typing import Optional, cast, List, Dict, Pattern, Set
@ -65,6 +66,7 @@ class ThreeMFWriter(MeshWriter):
self._unit_matrix_string = ThreeMFWriter._convertMatrixToString(Matrix()) self._unit_matrix_string = ThreeMFWriter._convertMatrixToString(Matrix())
self._archive: Optional[zipfile.ZipFile] = None self._archive: Optional[zipfile.ZipFile] = None
self._store_archive = False self._store_archive = False
self._lock = threading.Lock()
@staticmethod @staticmethod
def _convertMatrixToString(matrix): def _convertMatrixToString(matrix):
@ -423,6 +425,7 @@ class ThreeMFWriter(MeshWriter):
@call_on_qt_thread # must be called from the main thread because of OpenGL @call_on_qt_thread # must be called from the main thread because of OpenGL
def _createSnapshot(self): def _createSnapshot(self):
Logger.log("d", "Creating thumbnail image...") Logger.log("d", "Creating thumbnail image...")
self._lock.acquire()
if not CuraApplication.getInstance().isVisible: if not CuraApplication.getInstance().isVisible:
Logger.log("w", "Can't create snapshot when renderer not initialized.") Logger.log("w", "Can't create snapshot when renderer not initialized.")
return None return None
@ -431,6 +434,7 @@ class ThreeMFWriter(MeshWriter):
except: except:
Logger.logException("w", "Failed to create snapshot image") Logger.logException("w", "Failed to create snapshot image")
return None return None
finally: self._lock.release()
return snapshot return snapshot

View File

@ -46,7 +46,7 @@ UM.Dialog
UM.Label UM.Label
{ {
id: descriptionLabel id: descriptionLabel
text: catalog.i18nc("@action:description", "When exporting a Universal Cura Project, all the models present on the build plate will be included with their current position, orientation and scale. You can also select which per-extruder or per-model settings should be included to ensure a proper printing of the batch, even on different printers.") text: catalog.i18nc("@action:description", "Universal Cura Project files can be printed on different 3D printers while retaining positional data and selected settings. When exported, all models present on the build plate will be included along with their current position, orientation, and scale. You can also select which per-extruder or per-model settings should be included to ensure proper printing.")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
wrapMode: Text.Wrap wrapMode: Text.Wrap
Layout.maximumWidth: headerColumn.width Layout.maximumWidth: headerColumn.width

View File

@ -6815,7 +6815,7 @@
"prime_tower_mode": "prime_tower_mode":
{ {
"label": "Prime Tower Type", "label": "Prime Tower Type",
"description": "<html>How to generate the prime tower:<ul><li><b>Normal:</b> create a bucket in which secondary materials are primed</li><li><b>Interleaved:</b> create a prime tower as sparse as possible. This will save time and filament, but is only possible if the used materials adhere to each other.</li></ul></html>", "description": "<html>How to generate the prime tower:<ul><li><b>Normal:</b> create a bucket in which secondary materials are primed</li><li><b>Interleaved:</b> create a prime tower as sparse as possible. This will save time and filament, but is only possible if the used materials adhere to each other</li></ul></html>",
"type": "enum", "type": "enum",
"value": "'interleaved' if (all(material_type_var == extruderValues('material_type')[0] for material_type_var in extruderValues('material_type')) and all(material_brand_var == extruderValues('material_brand')[0] for material_brand_var in extruderValues('material_brand'))) else 'normal'", "value": "'interleaved' if (all(material_type_var == extruderValues('material_type')[0] for material_type_var in extruderValues('material_type')) and all(material_brand_var == extruderValues('material_brand')[0] for material_brand_var in extruderValues('material_brand'))) else 'normal'",
"options": "options":
@ -7301,7 +7301,7 @@
"user_defined_print_order_enabled": "user_defined_print_order_enabled":
{ {
"label": "Set Print Sequence Manually", "label": "Set Print Sequence Manually",
"description": "Allows to order the object list to set the print sequence manually. First object from the list will be printed first.", "description": "Allows you to order the object list to manually set the print sequence. First object from the list will be printed first.",
"type": "bool", "type": "bool",
"default_value": false, "default_value": false,
"settable_per_mesh": false, "settable_per_mesh": false,