PlatformPhysics.py 6.7 KB

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