PlatformPhysics.py 7.5 KB

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