ConvexHullDecorator.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
  2. from UM.Application import Application
  3. from UM.Math.Polygon import Polygon
  4. from UM.Logger import Logger
  5. from . import ConvexHullNode
  6. import numpy
  7. ## The convex hull decorator is a scene node decorator that adds the convex hull functionality to a scene node.
  8. # If a scene node has a convex hull decorator, it will have a shadow in which other objects can not be printed.
  9. class ConvexHullDecorator(SceneNodeDecorator):
  10. def __init__(self):
  11. super().__init__()
  12. self._convex_hull_node = None
  13. self._init2DConvexHullCache()
  14. self._profile = None
  15. Application.getInstance().getMachineManager().activeProfileChanged.connect(self._onActiveProfileChanged)
  16. Application.getInstance().getMachineManager().activeMachineInstanceChanged.connect(self._onActiveMachineInstanceChanged)
  17. self._onActiveProfileChanged()
  18. ## Force that a new (empty) object is created upon copy.
  19. def __deepcopy__(self, memo):
  20. return ConvexHullDecorator()
  21. ## Get the unmodified 2D projected convex hull of the node
  22. def getConvexHull(self):
  23. hull = self._compute2DConvexHull()
  24. profile = Application.getInstance().getMachineManager().getWorkingProfile()
  25. if profile:
  26. if profile.getSettingValue("print_sequence") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
  27. hull = hull.getMinkowskiHull(Polygon(numpy.array(profile.getSettingValue("machine_head_polygon"), numpy.float32)))
  28. return hull
  29. ## Get the convex hull of the node with the full head size
  30. def getConvexHullHeadFull(self):
  31. return self._compute2DConvexHeadFull()
  32. ## Get convex hull of the object + head size
  33. # In case of printing all at once this is the same as the convex hull.
  34. # For one at the time this is area with intersection of mirrored head
  35. def getConvexHullHead(self):
  36. profile = Application.getInstance().getMachineManager().getWorkingProfile()
  37. if profile:
  38. if profile.getSettingValue("print_sequence") == "one_at_a_time" and not self._node.getParent().callDecoration(
  39. "isGroup"):
  40. return self._compute2DConvexHeadMin()
  41. return None
  42. ## Get convex hull of the node
  43. # In case of printing all at once this is the same as the convex hull.
  44. # For one at the time this is the area without the head.
  45. def getConvexHullBoundary(self):
  46. profile = Application.getInstance().getMachineManager().getWorkingProfile()
  47. if profile:
  48. if profile.getSettingValue("print_sequence") == "one_at_a_time" and not self._node.getParent().callDecoration(
  49. "isGroup"):
  50. # Printing one at a time and it's not an object in a group
  51. return self._compute2DConvexHull()
  52. return None
  53. def recomputeConvexHull(self):
  54. convex_hull = self.getConvexHull()
  55. if self._convex_hull_node:
  56. if self._convex_hull_node.getHull() == convex_hull:
  57. return
  58. self._convex_hull_node.setParent(None)
  59. hull_node = ConvexHullNode.ConvexHullNode(self._node, convex_hull,
  60. Application.getInstance().getController().getScene().getRoot())
  61. self._convex_hull_node = hull_node
  62. def _onActiveProfileChanged(self):
  63. if self._profile:
  64. self._profile.settingValueChanged.disconnect(self._onSettingValueChanged)
  65. self._profile = Application.getInstance().getMachineManager().getWorkingProfile()
  66. if self._profile:
  67. self._profile.settingValueChanged.connect(self._onSettingValueChanged)
  68. def _onActiveMachineInstanceChanged(self):
  69. if self._convex_hull_node:
  70. self._convex_hull_node.setParent(None)
  71. self._convex_hull_node = None
  72. def _onSettingValueChanged(self, setting):
  73. if setting == "print_sequence":
  74. self.recomputeConvexHull()
  75. def _init2DConvexHullCache(self):
  76. # Cache for the group code path in _compute2DConvexHull()
  77. self._2d_convex_hull_group_child_polygon = None
  78. self._2d_convex_hull_group_result = None
  79. # Cache for the mesh code path in _compute2DConvexHull()
  80. self._2d_convex_hull_mesh = None
  81. self._2d_convex_hull_mesh_world_transform = None
  82. self._2d_convex_hull_mesh_result = None
  83. def _compute2DConvexHull(self):
  84. if self._node.callDecoration("isGroup"):
  85. points = numpy.zeros((0, 2), dtype=numpy.int32)
  86. for child in self._node.getChildren():
  87. child_hull = child.callDecoration("_compute2DConvexHull")
  88. if child_hull:
  89. points = numpy.append(points, child_hull.getPoints(), axis = 0)
  90. if points.size < 3:
  91. return None
  92. child_polygon = Polygon(points)
  93. # Check the cache
  94. if child_polygon == self._2d_convex_hull_group_child_polygon:
  95. # Logger.log('d', 'Cache hit in _compute2DConvexHull group path')
  96. return self._2d_convex_hull_group_result
  97. # First, calculate the normal convex hull around the points
  98. convex_hull = child_polygon.getConvexHull()
  99. # Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull.
  100. # This is done because of rounding errors.
  101. rounded_hull = self._roundHull(convex_hull)
  102. # Store the result in the cache
  103. self._2d_convex_hull_group_child_polygon = child_polygon
  104. self._2d_convex_hull_group_result = rounded_hull
  105. return rounded_hull
  106. else:
  107. if not self._node.getMeshData():
  108. return None
  109. mesh = self._node.getMeshData()
  110. world_transform = self._node.getWorldTransformation()
  111. # Check the cache
  112. if mesh is self._2d_convex_hull_mesh and world_transform == self._2d_convex_hull_mesh_world_transform:
  113. # Logger.log('d', 'Cache hit in _compute2DConvexHull mesh path')
  114. return self._2d_convex_hull_mesh_result
  115. vertex_data = mesh.getConvexHullTransformedVertices(world_transform)
  116. # Don't use data below 0.
  117. # TODO; We need a better check for this as this gives poor results for meshes with long edges.
  118. vertex_data = vertex_data[vertex_data[:,1] >= 0]
  119. # Round the vertex data to 1/10th of a mm, then remove all duplicate vertices
  120. # This is done to greatly speed up further convex hull calculations as the convex hull
  121. # becomes much less complex when dealing with highly detailed models.
  122. vertex_data = numpy.round(vertex_data, 1)
  123. vertex_data = vertex_data[:, [0, 2]] # Drop the Y components to project to 2D.
  124. # Grab the set of unique points.
  125. #
  126. # This basically finds the unique rows in the array by treating them as opaque groups of bytes
  127. # which are as long as the 2 float64s in each row, and giving this view to numpy.unique() to munch.
  128. # See http://stackoverflow.com/questions/16970982/find-unique-rows-in-numpy-array
  129. vertex_byte_view = numpy.ascontiguousarray(vertex_data).view(
  130. numpy.dtype((numpy.void, vertex_data.dtype.itemsize * vertex_data.shape[1])))
  131. _, idx = numpy.unique(vertex_byte_view, return_index=True)
  132. vertex_data = vertex_data[idx] # Select the unique rows by index.
  133. hull = Polygon(vertex_data)
  134. # First, calculate the normal convex hull around the points
  135. convex_hull = hull.getConvexHull()
  136. # Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull.
  137. # This is done because of rounding errors.
  138. rounded_hull = convex_hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32)))
  139. # Store the result in the cache
  140. self._2d_convex_hull_mesh = mesh
  141. self._2d_convex_hull_mesh_world_transform = world_transform
  142. self._2d_convex_hull_mesh_result = rounded_hull
  143. return rounded_hull
  144. def _getHeadAndFans(self):
  145. profile = Application.getInstance().getMachineManager().getWorkingProfile()
  146. return Polygon(numpy.array(profile.getSettingValue("machine_head_with_fans_polygon"), numpy.float32))
  147. def _compute2DConvexHeadFull(self):
  148. return self._compute2DConvexHull().getMinkowskiHull(self._getHeadAndFans())
  149. def _compute2DConvexHeadMin(self):
  150. headAndFans = self._getHeadAndFans()
  151. mirrored = headAndFans.mirror([0, 0], [0, 1]).mirror([0, 0], [1, 0]) # Mirror horizontally & vertically.
  152. head_and_fans = self._getHeadAndFans().intersectionConvexHulls(mirrored)
  153. # Min head hull is used for the push free
  154. min_head_hull = self._compute2DConvexHull().getMinkowskiHull(head_and_fans)
  155. return min_head_hull
  156. def _roundHull(self, convex_hull):
  157. return convex_hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32)))