mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-12 12:49:10 +08:00
Merge pull request #18549 from Ultimaker/CURA-11650-thumnails-missing
Cura 11650 thumnails missing
This commit is contained in:
commit
cba5b0dc48
@ -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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user