PlatformPhysics.py 7.7 KB

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