123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- # Copyright (c) 2015 Ultimaker B.V.
- # Cura is released under the terms of the AGPLv3 or higher.
- from PyQt5.QtCore import QTimer
- from UM.Scene.SceneNode import SceneNode
- from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
- from UM.Operations.TranslateOperation import TranslateOperation
- from UM.Math.Float import Float
- from UM.Math.Vector import Vector
- from UM.Math.AxisAlignedBox import AxisAlignedBox
- from UM.Application import Application
- from UM.Scene.Selection import Selection
- from UM.Preferences import Preferences
- from cura.ConvexHullDecorator import ConvexHullDecorator
- from . import PlatformPhysicsOperation
- from . import ConvexHullJob
- from . import ZOffsetDecorator
- import time
- import threading
- import copy
- class PlatformPhysics:
- def __init__(self, controller, volume):
- super().__init__()
- self._controller = controller
- self._controller.getScene().sceneChanged.connect(self._onSceneChanged)
- self._controller.toolOperationStarted.connect(self._onToolOperationStarted)
- self._controller.toolOperationStopped.connect(self._onToolOperationStopped)
- self._build_volume = volume
- self._enabled = True
- self._change_timer = QTimer()
- self._change_timer.setInterval(100)
- self._change_timer.setSingleShot(True)
- self._change_timer.timeout.connect(self._onChangeTimerFinished)
- Preferences.getInstance().addPreference("physics/automatic_push_free", True)
- def _onSceneChanged(self, source):
- self._change_timer.start()
- def _onChangeTimerFinished(self):
- if not self._enabled:
- return
- root = self._controller.getScene().getRoot()
- for node in BreadthFirstIterator(root):
- if node is root or type(node) is not SceneNode:
- continue
- bbox = node.getBoundingBox()
- if not bbox or not bbox.isValid():
- self._change_timer.start()
- continue
- build_volume_bounding_box = copy.deepcopy(self._build_volume.getBoundingBox())
- build_volume_bounding_box.setBottom(-9001) # Ignore intersections with the bottom
- node._outside_buildarea = False
- # Mark the node as outside the build volume if the bounding box test fails.
- if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
- node._outside_buildarea = True
- # Move it downwards if bottom is above platform
- move_vector = Vector()
- if not (node.getParent() and node.getParent().callDecoration("isGroup")): #If an object is grouped, don't move it down
- z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
- if bbox.bottom > 0:
- move_vector.setY(-bbox.bottom + z_offset)
- elif bbox.bottom < z_offset:
- move_vector.setY((-bbox.bottom) - z_offset)
- #if not Float.fuzzyCompare(bbox.bottom, 0.0):
- # pass#move_vector.setY(-bbox.bottom)
- # If there is no convex hull for the node, start calculating it and continue.
- if not node.getDecorator(ConvexHullDecorator):
- node.addDecorator(ConvexHullDecorator())
-
- if not node.callDecoration("getConvexHull"):
- if not node.callDecoration("getConvexHullJob"):
- job = ConvexHullJob.ConvexHullJob(node)
- job.start()
- node.callDecoration("setConvexHullJob", job)
-
- elif Preferences.getInstance().getValue("physics/automatic_push_free"):
- # Check for collisions between convex hulls
- for other_node in BreadthFirstIterator(root):
- # Ignore root, ourselves and anything that is not a normal SceneNode.
- if other_node is root or type(other_node) is not SceneNode or other_node is node:
- continue
-
- # Ignore colissions of a group with it's own children
- if other_node in node.getAllChildren() or node in other_node.getAllChildren():
- continue
-
- # Ignore colissions within a group
- if other_node.getParent().callDecoration("isGroup") is not None or node.getParent().callDecoration("isGroup") is not None:
- continue
- #if node.getParent().callDecoration("isGroup") is other_node.getParent().callDecoration("isGroup"):
- # continue
-
- # Ignore nodes that do not have the right properties set.
- if not other_node.callDecoration("getConvexHull") or not other_node.getBoundingBox():
- continue
- # Check to see if the bounding boxes intersect. If not, we can ignore the node as there is no way the hull intersects.
- #if node.getBoundingBox().intersectsBox(other_node.getBoundingBox()) == AxisAlignedBox.IntersectionResult.NoIntersection:
- # continue
- # Get the overlap distance for both convex hulls. If this returns None, there is no intersection.
- try:
- head_hull = node.callDecoration("getConvexHullHead")
- if head_hull:
- overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHullHead"))
- if not overlap:
- other_head_hull = other_node.callDecoration("getConvexHullHead")
- if other_head_hull:
- overlap = node.callDecoration("getConvexHullHead").intersectsPolygon(other_head_hull)
- else:
- overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_node.callDecoration("getConvexHull"))
- except:
- overlap = None #It can sometimes occur that the caclulated convex hull has no size, in which case there is no overlap.
- if overlap is None:
- continue
- move_vector.setX(overlap[0] * 1.1)
- move_vector.setZ(overlap[1] * 1.1)
- convex_hull = node.callDecoration("getConvexHull")
- if convex_hull:
- if not convex_hull.isValid():
- return
- # Check for collisions between disallowed areas and the object
- for area in self._build_volume.getDisallowedAreas():
- overlap = convex_hull.intersectsPolygon(area)
- if overlap is None:
- continue
- node._outside_buildarea = True
- if move_vector != Vector():
- op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
- op.push()
- def _onToolOperationStarted(self, tool):
- self._enabled = False
- def _onToolOperationStopped(self, tool):
- if tool.getPluginId() == "TranslateTool":
- for node in Selection.getAllSelectedObjects():
- if node.getBoundingBox().bottom < 0:
- if not node.getDecorator(ZOffsetDecorator.ZOffsetDecorator):
- node.addDecorator(ZOffsetDecorator.ZOffsetDecorator())
- node.callDecoration("setZOffset", node.getBoundingBox().bottom)
- else:
- if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator):
- node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
- self._enabled = True
- self._onChangeTimerFinished()
|