Merge remote-tracking branch 'origin/5.7' into 5.7

This commit is contained in:
Erwan MATHIEU 2024-03-11 13:25:51 +01:00
commit 7c00d2f0b3
3 changed files with 60 additions and 41 deletions

View file

@ -10,8 +10,8 @@ class MaterialOutputModel(QObject):
def __init__(self, guid: Optional[str], type: str, color: str, brand: str, name: str, parent = None) -> None: def __init__(self, guid: Optional[str], type: str, color: str, brand: str, name: str, parent = None) -> None:
super().__init__(parent) super().__init__(parent)
name, guid = MaterialOutputModel.getMaterialFromDefinition(guid,type, brand, name) name, guid = MaterialOutputModel.getMaterialFromDefinition(guid, type, brand, name)
self._guid =guid self._guid = guid
self._type = type self._type = type
self._color = color self._color = color
self._brand = brand self._brand = brand
@ -24,23 +24,22 @@ class MaterialOutputModel(QObject):
@staticmethod @staticmethod
def getMaterialFromDefinition(guid, type, brand, name): def getMaterialFromDefinition(guid, type, brand, name):
_MATERIAL_MAP = { "abs" :{"name" :"abs_175" ,"guid": "2780b345-577b-4a24-a2c5-12e6aad3e690"}, _MATERIAL_MAP = { "abs" :{"name" :"ABS" ,"guid": "2780b345-577b-4a24-a2c5-12e6aad3e690"},
"abs-wss1" :{"name" :"absr_175" ,"guid": "88c8919c-6a09-471a-b7b6-e801263d862d"}, "abs-cf10": {"name": "ABS-CF", "guid": "495a0ce5-9daf-4a16-b7b2-06856d82394d"},
"asa" :{"name" :"asa_175" ,"guid": "416eead4-0d8e-4f0b-8bfc-a91a519befa5"}, "abs-wss1" :{"name" :"ABS-R" ,"guid": "88c8919c-6a09-471a-b7b6-e801263d862d"},
"nylon-cf" :{"name" :"cffpa_175" ,"guid": "85bbae0e-938d-46fb-989f-c9b3689dc4f0"}, "asa" :{"name" :"ASA" ,"guid": "416eead4-0d8e-4f0b-8bfc-a91a519befa5"},
"nylon12-cf": {"name": "nylon12-cf_175", "guid": "3c6f2877-71cc-4760-84e6-4b89ab243e3b"}, "nylon12-cf":{"name": "Nylon 12 CF" ,"guid": "3c6f2877-71cc-4760-84e6-4b89ab243e3b"},
"nylon" :{"name" :"nylon_175" ,"guid": "283d439a-3490-4481-920c-c51d8cdecf9c"}, "nylon" :{"name" :"Nylon" ,"guid": "283d439a-3490-4481-920c-c51d8cdecf9c"},
"pc" :{"name" :"pc_175" ,"guid": "62414577-94d1-490d-b1e4-7ef3ec40db02"}, "pc" :{"name" :"PC" ,"guid": "62414577-94d1-490d-b1e4-7ef3ec40db02"},
"petg" :{"name" :"petg_175" ,"guid": "69386c85-5b6c-421a-bec5-aeb1fb33f060"}, "petg" :{"name" :"PETG" ,"guid": "69386c85-5b6c-421a-bec5-aeb1fb33f060"},
"pla" :{"name" :"pla_175" ,"guid": "0ff92885-617b-4144-a03c-9989872454bc"}, "pla" :{"name" :"PLA" ,"guid": "0ff92885-617b-4144-a03c-9989872454bc"},
"pva" :{"name" :"pva_175" ,"guid": "a4255da2-cb2a-4042-be49-4a83957a2f9a"}, "pva" :{"name" :"PVA" ,"guid": "a4255da2-cb2a-4042-be49-4a83957a2f9a"},
"wss1" :{"name" :"rapidrinse_175","guid": "a140ef8f-4f26-4e73-abe0-cfc29d6d1024"}, "wss1" :{"name" :"RapidRinse" ,"guid": "a140ef8f-4f26-4e73-abe0-cfc29d6d1024"},
"sr30" :{"name" :"sr30_175" ,"guid": "77873465-83a9-4283-bc44-4e542b8eb3eb"}, "sr30" :{"name" :"SR-30" ,"guid": "77873465-83a9-4283-bc44-4e542b8eb3eb"},
"im-pla" :{"name" :"tough_pla_175" ,"guid": "96fca5d9-0371-4516-9e96-8e8182677f3c"}, "bvoh" :{"name" :"BVOH" ,"guid": "923e604c-8432-4b09-96aa-9bbbd42207f4"},
"bvoh" :{"name" :"bvoh_175" ,"guid": "923e604c-8432-4b09-96aa-9bbbd42207f4"}, "cpe" :{"name" :"CPE" ,"guid": "da1872c1-b991-4795-80ad-bdac0f131726"},
"cpe" :{"name" :"cpe_175" ,"guid": "da1872c1-b991-4795-80ad-bdac0f131726"}, "hips" :{"name" :"HIPS" ,"guid": "a468d86a-220c-47eb-99a5-bbb47e514eb0"},
"hips" :{"name" :"hips_175" ,"guid": "a468d86a-220c-47eb-99a5-bbb47e514eb0"}, "tpu" :{"name" :"TPU 95A" ,"guid": "19baa6a9-94ff-478b-b4a1-8157b74358d2"}
"tpu" :{"name" :"tpu_175" ,"guid": "19baa6a9-94ff-478b-b4a1-8157b74358d2"}
} }

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