Snapshot.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import numpy
  4. from PyQt5 import QtCore
  5. from PyQt5.QtGui import QImage
  6. from cura.PreviewPass import PreviewPass
  7. from cura.Scene import ConvexHullNode
  8. from UM.Application import Application
  9. from UM.Math.AxisAlignedBox import AxisAlignedBox
  10. from UM.Math.Matrix import Matrix
  11. from UM.Math.Vector import Vector
  12. from UM.Mesh.MeshData import transformVertices
  13. from UM.Scene.Camera import Camera
  14. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  15. class Snapshot:
  16. @staticmethod
  17. def getImageBoundaries(image: QImage):
  18. # Look at the resulting image to get a good crop.
  19. # Get the pixels as byte array
  20. pixel_array = image.bits().asarray(image.byteCount())
  21. width, height = image.width(), image.height()
  22. # Convert to numpy array, assume it's 32 bit (it should always be)
  23. pixels = numpy.frombuffer(pixel_array, dtype=numpy.uint8).reshape([height, width, 4])
  24. # Find indices of non zero pixels
  25. nonzero_pixels = numpy.nonzero(pixels)
  26. min_y, min_x, min_a_ = numpy.amin(nonzero_pixels, axis=1)
  27. max_y, max_x, max_a_ = numpy.amax(nonzero_pixels, axis=1)
  28. return min_x, max_x, min_y, max_y
  29. ## Return a QImage of the scene
  30. # Uses PreviewPass that leaves out some elements
  31. # Aspect ratio assumes a square
  32. @staticmethod
  33. def snapshot(width = 300, height = 300):
  34. scene = Application.getInstance().getController().getScene()
  35. active_camera = scene.getActiveCamera()
  36. render_width, render_height = active_camera.getWindowSize()
  37. render_width = int(render_width)
  38. render_height = int(render_height)
  39. preview_pass = PreviewPass(render_width, render_height)
  40. root = scene.getRoot()
  41. camera = Camera("snapshot", root)
  42. # determine zoom and look at
  43. bbox = None
  44. for node in DepthFirstIterator(root):
  45. if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
  46. if bbox is None:
  47. bbox = node.getBoundingBox()
  48. else:
  49. bbox = bbox + node.getBoundingBox()
  50. # If there is no bounding box, it means that there is no model in the buildplate
  51. if bbox is None:
  52. return None
  53. look_at = bbox.center
  54. # guessed size so the objects are hopefully big
  55. size = max(bbox.width, bbox.height, bbox.depth * 0.5)
  56. # Looking from this direction (x, y, z) in OGL coordinates
  57. looking_from_offset = Vector(-1, 1, 2)
  58. if size > 0:
  59. # determine the watch distance depending on the size
  60. looking_from_offset = looking_from_offset * size * 1.3
  61. camera.setPosition(look_at + looking_from_offset)
  62. camera.lookAt(look_at)
  63. satisfied = False
  64. size = None
  65. fovy = 30
  66. while not satisfied:
  67. if size is not None:
  68. satisfied = True # always be satisfied after second try
  69. projection_matrix = Matrix()
  70. # Somehow the aspect ratio is also influenced in reverse by the screen width/height
  71. # So you have to set it to render_width/render_height to get 1
  72. projection_matrix.setPerspective(fovy, render_width / render_height, 1, 500)
  73. camera.setProjectionMatrix(projection_matrix)
  74. preview_pass.setCamera(camera)
  75. preview_pass.render()
  76. pixel_output = preview_pass.getOutput()
  77. min_x, max_x, min_y, max_y = Snapshot.getImageBoundaries(pixel_output)
  78. size = max((max_x - min_x) / render_width, (max_y - min_y) / render_height)
  79. if size > 0.5 or satisfied:
  80. satisfied = True
  81. else:
  82. # make it big and allow for some empty space around
  83. fovy *= 0.5 # strangely enough this messes up the aspect ratio: fovy *= size * 1.1
  84. # make it a square
  85. if max_x - min_x >= max_y - min_y:
  86. # make y bigger
  87. min_y, max_y = int((max_y + min_y) / 2 - (max_x - min_x) / 2), int((max_y + min_y) / 2 + (max_x - min_x) / 2)
  88. else:
  89. # make x bigger
  90. min_x, max_x = int((max_x + min_x) / 2 - (max_y - min_y) / 2), int((max_x + min_x) / 2 + (max_y - min_y) / 2)
  91. cropped_image = pixel_output.copy(min_x, min_y, max_x - min_x, max_y - min_y)
  92. # Scale it to the correct size
  93. scaled_image = cropped_image.scaled(
  94. width, height,
  95. aspectRatioMode = QtCore.Qt.IgnoreAspectRatio,
  96. transformMode = QtCore.Qt.SmoothTransformation)
  97. return scaled_image