ConvexHullJob.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. # Copyright (c) 2015 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from UM.Job import Job
  4. from UM.Application import Application
  5. from UM.Math.Polygon import Polygon
  6. import numpy
  7. import copy
  8. from . import ConvexHullNode
  9. class ConvexHullJob(Job):
  10. def __init__(self, node):
  11. super().__init__()
  12. self._node = node
  13. def run(self):
  14. if not self._node:
  15. return
  16. ## If the scene node is a group, use the hull of the children to calculate its hull.
  17. if self._node.callDecoration("isGroup"):
  18. hull = Polygon(numpy.zeros((0, 2), dtype=numpy.int32))
  19. for child in self._node.getChildren():
  20. child_hull = child.callDecoration("getConvexHull")
  21. if child_hull:
  22. hull.setPoints(numpy.append(hull.getPoints(), child_hull.getPoints(), axis = 0))
  23. if hull.getPoints().size < 3:
  24. self._node.callDecoration("setConvexHull", None)
  25. self._node.callDecoration("setConvexHullJob", None)
  26. return
  27. Job.yieldThread()
  28. else:
  29. if not self._node.getMeshData():
  30. return
  31. mesh = self._node.getMeshData()
  32. vertex_data = mesh.getTransformed(self._node.getWorldTransformation()).getVertices()
  33. # Don't use data below 0. TODO; We need a better check for this as this gives poor results for meshes with long edges.
  34. vertex_data = vertex_data[vertex_data[:,1] >= 0]
  35. # Round the vertex data to 1/10th of a mm, then remove all duplicate vertices
  36. # This is done to greatly speed up further convex hull calculations as the convex hull
  37. # becomes much less complex when dealing with highly detailed models.
  38. vertex_data = numpy.round(vertex_data, 1)
  39. vertex_data = vertex_data[:, [0, 2]] # Drop the Y components to project to 2D.
  40. # Grab the set of unique points.
  41. #
  42. # This basically finds the unique rows in the array by treating them as opaque groups of bytes
  43. # which are as long as the 2 float64s in each row, and giving this view to numpy.unique() to munch.
  44. # See http://stackoverflow.com/questions/16970982/find-unique-rows-in-numpy-array
  45. vertex_byte_view = numpy.ascontiguousarray(vertex_data).view(numpy.dtype((numpy.void, vertex_data.dtype.itemsize * vertex_data.shape[1])))
  46. _, idx = numpy.unique(vertex_byte_view, return_index=True)
  47. vertex_data = vertex_data[idx] # Select the unique rows by index.
  48. hull = Polygon(vertex_data)
  49. # First, calculate the normal convex hull around the points
  50. hull = hull.getConvexHull()
  51. # Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull.
  52. # This is done because of rounding errors.
  53. hull = hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32)))
  54. profile = Application.getInstance().getMachineManager().getWorkingProfile()
  55. if profile:
  56. if profile.getSettingValue("print_sequence") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
  57. # Printing one at a time and it's not an object in a group
  58. self._node.callDecoration("setConvexHullBoundary", copy.deepcopy(hull))
  59. head_and_fans = Polygon(numpy.array(profile.getSettingValue("machine_head_with_fans_polygon"), numpy.float32))
  60. # Full head hull is used to actually check the order.
  61. full_head_hull = hull.getMinkowskiHull(head_and_fans)
  62. self._node.callDecoration("setConvexHullHeadFull", full_head_hull)
  63. mirrored = copy.deepcopy(head_and_fans)
  64. mirrored.mirror([0, 0], [0, 1]) #Mirror horizontally.
  65. mirrored.mirror([0, 0], [1, 0]) #Mirror vertically.
  66. head_and_fans = head_and_fans.intersectionConvexHulls(mirrored)
  67. # Add extra margin depending on adhesion type
  68. adhesion_type = profile.getSettingValue("adhesion_type")
  69. extra_margin = 0
  70. machine_head_coords = numpy.array(
  71. profile.getSettingValue("machine_head_with_fans_polygon"),
  72. numpy.float32)
  73. head_y_size = abs(machine_head_coords).min() # safe margin to take off in all directions
  74. if adhesion_type == "raft":
  75. extra_margin = max(0, profile.getSettingValue("raft_margin")-head_y_size)
  76. elif adhesion_type == "brim":
  77. extra_margin = max(0, profile.getSettingValue("brim_width")-head_y_size)
  78. elif adhesion_type == "skirt":
  79. extra_margin = max(
  80. 0, profile.getSettingValue("skirt_gap") +
  81. profile.getSettingValue("skirt_line_count") * profile.getSettingValue("skirt_line_width") -
  82. head_y_size)
  83. # adjust head_and_fans with extra margin
  84. if extra_margin > 0:
  85. # In Cura 2.2+, there is a function to create this circle-like polygon.
  86. extra_margin_polygon = Polygon(numpy.array([
  87. [-extra_margin, 0],
  88. [-extra_margin * 0.707, extra_margin * 0.707],
  89. [0, extra_margin],
  90. [extra_margin * 0.707, extra_margin * 0.707],
  91. [extra_margin, 0],
  92. [extra_margin * 0.707, -extra_margin * 0.707],
  93. [0, -extra_margin],
  94. [-extra_margin * 0.707, -extra_margin * 0.707]
  95. ], numpy.float32))
  96. head_and_fans = head_and_fans.getMinkowskiHull(extra_margin_polygon)
  97. # Min head hull is used for the push free
  98. min_head_hull = hull.getMinkowskiHull(head_and_fans)
  99. self._node.callDecoration("setConvexHullHead", min_head_hull)
  100. hull = hull.getMinkowskiHull(Polygon(numpy.array(profile.getSettingValue("machine_head_polygon"),numpy.float32)))
  101. else:
  102. self._node.callDecoration("setConvexHullHead", None)
  103. if self._node.getParent() is None: #Node was already deleted before job is done.
  104. self._node.callDecoration("setConvexHullNode",None)
  105. self._node.callDecoration("setConvexHull", None)
  106. self._node.callDecoration("setConvexHullJob", None)
  107. return
  108. hull_node = ConvexHullNode.ConvexHullNode(self._node, hull, Application.getInstance().getController().getScene().getRoot())
  109. self._node.callDecoration("setConvexHullNode", hull_node)
  110. self._node.callDecoration("setConvexHull", hull)
  111. self._node.callDecoration("setConvexHullJob", None)
  112. if self._node.getParent() and self._node.getParent().callDecoration("isGroup"):
  113. job = self._node.getParent().callDecoration("getConvexHullJob")
  114. if job:
  115. job.cancel()
  116. self._node.getParent().callDecoration("setConvexHull", None)
  117. hull_node = self._node.getParent().callDecoration("getConvexHullNode")
  118. if hull_node:
  119. hull_node.setParent(None)