PlatformPhysics.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. # Copyright (c) 2015 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from PyQt5.QtCore import QTimer
  4. from UM.Scene.SceneNode import SceneNode
  5. from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
  6. from UM.Math.Vector import Vector
  7. from UM.Math.AxisAlignedBox import AxisAlignedBox
  8. from UM.Application import Application
  9. from UM.Scene.Selection import Selection
  10. from UM.Preferences import Preferences
  11. from cura.ConvexHullDecorator import ConvexHullDecorator
  12. from . import PlatformPhysicsOperation
  13. from . import ZOffsetDecorator
  14. import copy
  15. class PlatformPhysics:
  16. def __init__(self, controller, volume):
  17. super().__init__()
  18. self._controller = controller
  19. self._controller.getScene().sceneChanged.connect(self._onSceneChanged)
  20. self._controller.toolOperationStarted.connect(self._onToolOperationStarted)
  21. self._controller.toolOperationStopped.connect(self._onToolOperationStopped)
  22. self._build_volume = volume
  23. self._enabled = True
  24. self._change_timer = QTimer()
  25. self._change_timer.setInterval(100)
  26. self._change_timer.setSingleShot(True)
  27. self._change_timer.timeout.connect(self._onChangeTimerFinished)
  28. Preferences.getInstance().addPreference("physics/automatic_push_free", True)
  29. def _onSceneChanged(self, source):
  30. self._change_timer.start()
  31. def _onChangeTimerFinished(self):
  32. if not self._enabled:
  33. return
  34. root = self._controller.getScene().getRoot()
  35. for node in BreadthFirstIterator(root):
  36. if node is root or type(node) is not SceneNode or node.getBoundingBox() is None:
  37. continue
  38. bbox = node.getBoundingBox()
  39. # Ignore intersections with the bottom
  40. build_volume_bounding_box = self._build_volume.getBoundingBox().set(bottom=-9001)
  41. node._outside_buildarea = False
  42. # Mark the node as outside the build volume if the bounding box test fails.
  43. if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
  44. node._outside_buildarea = True
  45. # Move it downwards if bottom is above platform
  46. move_vector = Vector()
  47. if not (node.getParent() and node.getParent().callDecoration("isGroup")): #If an object is grouped, don't move it down
  48. z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
  49. if bbox.bottom > 0:
  50. move_vector = move_vector.set(y=-bbox.bottom + z_offset)
  51. elif bbox.bottom < z_offset:
  52. move_vector = move_vector.set(y=(-bbox.bottom) - z_offset)
  53. #if not Float.fuzzyCompare(bbox.bottom, 0.0):
  54. # pass#move_vector.setY(-bbox.bottom)
  55. # If there is no convex hull for the node, start calculating it and continue.
  56. if not node.getDecorator(ConvexHullDecorator):
  57. node.addDecorator(ConvexHullDecorator())
  58. node.callDecoration("recomputeConvexHull")
  59. if Preferences.getInstance().getValue("physics/automatic_push_free"):
  60. # Check for collisions between convex hulls
  61. for other_node in BreadthFirstIterator(root):
  62. # Ignore root, ourselves and anything that is not a normal SceneNode.
  63. if other_node is root or type(other_node) is not SceneNode or other_node is node:
  64. continue
  65. # Ignore colissions of a group with it's own children
  66. if other_node in node.getAllChildren() or node in other_node.getAllChildren():
  67. continue
  68. # Ignore colissions within a group
  69. if other_node.getParent().callDecoration("isGroup") is not None or node.getParent().callDecoration("isGroup") is not None:
  70. continue
  71. #if node.getParent().callDecoration("isGroup") is other_node.getParent().callDecoration("isGroup"):
  72. # continue
  73. # Ignore nodes that do not have the right properties set.
  74. if not other_node.callDecoration("getConvexHull") or not other_node.getBoundingBox():
  75. continue
  76. # Check to see if the bounding boxes intersect. If not, we can ignore the node as there is no way the hull intersects.
  77. #if node.getBoundingBox().intersectsBox(other_node.getBoundingBox()) == AxisAlignedBox.IntersectionResult.NoIntersection:
  78. # continue
  79. # Get the overlap distance for both convex hulls. If this returns None, there is no intersection.
  80. try:
  81. head_hull = node.callDecoration("getConvexHullHead")
  82. if head_hull:
  83. overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHull"))
  84. if not overlap:
  85. other_head_hull = other_node.callDecoration("getConvexHullHead")
  86. if other_head_hull:
  87. overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_head_hull)
  88. else:
  89. overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_node.callDecoration("getConvexHull"))
  90. except:
  91. overlap = None #It can sometimes occur that the calculated convex hull has no size, in which case there is no overlap.
  92. if overlap is None:
  93. continue
  94. move_vector = move_vector.set(x=overlap[0] * 1.1, z=overlap[1] * 1.1)
  95. convex_hull = node.callDecoration("getConvexHull")
  96. if convex_hull:
  97. if not convex_hull.isValid():
  98. return
  99. # Check for collisions between disallowed areas and the object
  100. for area in self._build_volume.getDisallowedAreas():
  101. overlap = convex_hull.intersectsPolygon(area)
  102. if overlap is None:
  103. continue
  104. node._outside_buildarea = True
  105. if not Vector.Null.equals(move_vector, epsilon=1e-5):
  106. op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
  107. op.push()
  108. def _onToolOperationStarted(self, tool):
  109. self._enabled = False
  110. def _onToolOperationStopped(self, tool):
  111. if tool.getPluginId() == "TranslateTool":
  112. for node in Selection.getAllSelectedObjects():
  113. if node.getBoundingBox().bottom < 0:
  114. if not node.getDecorator(ZOffsetDecorator.ZOffsetDecorator):
  115. node.addDecorator(ZOffsetDecorator.ZOffsetDecorator())
  116. node.callDecoration("setZOffset", node.getBoundingBox().bottom)
  117. else:
  118. if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator):
  119. node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
  120. self._enabled = True
  121. self._onChangeTimerFinished()