PlatformPhysics.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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.Operations.ScaleToBoundsOperation import ScaleToBoundsOperation
  8. from UM.Math.Float import Float
  9. from UM.Math.Vector import Vector
  10. from UM.Math.AxisAlignedBox import AxisAlignedBox
  11. from UM.Application import Application
  12. from UM.Scene.Selection import Selection
  13. from UM.Preferences import Preferences
  14. from cura.ConvexHullDecorator import ConvexHullDecorator
  15. from . import PlatformPhysicsOperation
  16. from . import ConvexHullJob
  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. # Mark the node as outside the build volume if the bounding box test fails.
  50. if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
  51. node._outside_buildarea = True
  52. else:
  53. node._outside_buildarea = False
  54. # Move it downwards if bottom is above platform
  55. move_vector = Vector()
  56. if bbox.bottom > 0:
  57. move_vector.setY(-bbox.bottom)
  58. #if not Float.fuzzyCompare(bbox.bottom, 0.0):
  59. # pass#move_vector.setY(-bbox.bottom)
  60. # If there is no convex hull for the node, start calculating it and continue.
  61. if not node.getDecorator(ConvexHullDecorator):
  62. node.addDecorator(ConvexHullDecorator())
  63. if not node.callDecoration("getConvexHull"):
  64. if not node.callDecoration("getConvexHullJob"):
  65. job = ConvexHullJob.ConvexHullJob(node)
  66. job.start()
  67. node.callDecoration("setConvexHullJob", job)
  68. elif Selection.isSelected(node):
  69. pass
  70. elif Preferences.getInstance().getValue("physics/automatic_push_free"):
  71. # Check for collisions between convex hulls
  72. for other_node in BreadthFirstIterator(root):
  73. # Ignore root, ourselves and anything that is not a normal SceneNode.
  74. if other_node is root or type(other_node) is not SceneNode or other_node is node:
  75. continue
  76. # Ignore colissions of a group with it's own children
  77. if other_node in node.getAllChildren() or node in other_node.getAllChildren():
  78. continue
  79. # Ignore colissions within a group
  80. if other_node.getParent().callDecoration("isGroup") is not None:
  81. if node.getParent().callDecoration("isGroup") is other_node.getParent().callDecoration("isGroup"):
  82. continue
  83. # Ignore nodes that do not have the right properties set.
  84. if not other_node.callDecoration("getConvexHull") or not other_node.getBoundingBox():
  85. continue
  86. # Check to see if the bounding boxes intersect. If not, we can ignore the node as there is no way the hull intersects.
  87. if node.getBoundingBox().intersectsBox(other_node.getBoundingBox()) == AxisAlignedBox.IntersectionResult.NoIntersection:
  88. continue
  89. # Get the overlap distance for both convex hulls. If this returns None, there is no intersection.
  90. overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_node.callDecoration("getConvexHull"))
  91. if overlap is None:
  92. continue
  93. move_vector.setX(overlap[0] * 1.1)
  94. move_vector.setZ(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 move_vector != Vector():
  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. self._enabled = True
  112. self._onChangeTimerFinished()