Finally, use the new convex hull code to compute the object 'shadow' and exclusion zones.

Contributes to CURA-1504
This commit is contained in:
Simon Edwards 2016-05-23 13:47:21 +02:00
parent 0b858f3878
commit d7127b800c
5 changed files with 222 additions and 79 deletions

View File

@ -1,28 +1,20 @@
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from UM.Application import Application
from UM.Math.Polygon import Polygon
from UM.Logger import Logger
from . import ConvexHullNode
import numpy
## The convex hull decorator is a scene node decorator that adds the convex hull functionality to a scene node.
# If a scene node has a convex hull decorator, it will have a shadow in which other objects can not be printed.
class ConvexHullDecorator(SceneNodeDecorator):
def __init__(self):
def __init__(self,):
super().__init__()
self._convex_hull = None
# In case of printing all at once this is the same as the convex hull.
# For one at the time this is the area without the head.
self._convex_hull_boundary = None
# In case of printing all at once this is the same as the convex hull.
# For one at the time this is area with intersection of mirrored head
self._convex_hull_head = None
# In case of printing all at once this is the same as the convex hull.
# For one at the time this is area with intersection of full head
self._convex_hull_head_full = None
self._convex_hull_node = None
self._convex_hull_job = None
self._init2DConvexHullCache()
self._profile = None
Application.getInstance().getMachineManager().activeProfileChanged.connect(self._onActiveProfileChanged)
@ -31,59 +23,56 @@ class ConvexHullDecorator(SceneNodeDecorator):
## Force that a new (empty) object is created upon copy.
def __deepcopy__(self, memo):
copy = ConvexHullDecorator()
return copy
return ConvexHullDecorator()
## Get the unmodified convex hull of the node
## Get the unmodified 2D projected convex hull of the node
def getConvexHull(self):
return self._convex_hull
hull = self._compute2DConvexHull()
profile = Application.getInstance().getMachineManager().getWorkingProfile()
if profile:
if profile.getSettingValue("print_sequence") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
hull = hull.getMinkowskiHull(Polygon(numpy.array(profile.getSettingValue("machine_head_polygon"), numpy.float32)))
return hull
## Get the convex hull of the node with the full head size
def getConvexHullHeadFull(self):
if not self._convex_hull_head_full:
return self.getConvexHull()
return self._convex_hull_head_full
return self._compute2DConvexHeadFull()
## Get convex hull of the object + head size
# In case of printing all at once this is the same as the convex hull.
# For one at the time this is area with intersection of mirrored head
def getConvexHullHead(self):
if not self._convex_hull_head:
return self.getConvexHull()
return self._convex_hull_head
profile = Application.getInstance().getMachineManager().getWorkingProfile()
if profile:
if profile.getSettingValue("print_sequence") == "one_at_a_time" and not self._node.getParent().callDecoration(
"isGroup"):
return self._compute2DConvexHeadMin()
return None
## Get convex hull of the node
# In case of printing all at once this is the same as the convex hull.
# For one at the time this is the area without the head.
def getConvexHullBoundary(self):
if not self._convex_hull_boundary:
return self.getConvexHull()
return self._convex_hull_boundary
def setConvexHullBoundary(self, hull):
self._convex_hull_boundary = hull
profile = Application.getInstance().getMachineManager().getWorkingProfile()
if profile:
if profile.getSettingValue("print_sequence") == "one_at_a_time" and not self._node.getParent().callDecoration(
"isGroup"):
# Printing one at a time and it's not an object in a group
return self._compute2DConvexHull()
return None
def setConvexHullHeadFull(self, hull):
self._convex_hull_head_full = hull
def recomputeConvexHull(self):
convex_hull = self.getConvexHull()
if self._convex_hull_node:
if self._convex_hull_node.getHull() == convex_hull:
Logger.log('d', 'ConvexHullDecorator not creating a new ConvexHullNode')
return
self._convex_hull_node.setParent(None)
Logger.log('d', 'ConvexHullDecorator creating ConvexHullNode')
hull_node = ConvexHullNode.ConvexHullNode(self._node, convex_hull,
Application.getInstance().getController().getScene().getRoot())
self._convex_hull_node = hull_node
def setConvexHullHead(self, hull):
self._convex_hull_head = hull
def setConvexHull(self, hull):
self._convex_hull = hull
def getConvexHullJob(self):
return self._convex_hull_job
def setConvexHullJob(self, job):
self._convex_hull_job = job
def getConvexHullNode(self):
return self._convex_hull_node
def setConvexHullNode(self, node):
self._convex_hull_node = node
def _onActiveProfileChanged(self):
if self._profile:
self._profile.settingValueChanged.disconnect(self._onSettingValueChanged)
@ -94,18 +83,118 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._profile.settingValueChanged.connect(self._onSettingValueChanged)
def _onActiveMachineInstanceChanged(self):
if self._convex_hull_job:
self._convex_hull_job.cancel()
self.setConvexHull(None)
if self._convex_hull_node:
self._convex_hull_node.setParent(None)
self._convex_hull_node = None
def _onSettingValueChanged(self, setting):
if setting == "print_sequence":
if self._convex_hull_job:
self._convex_hull_job.cancel()
self.setConvexHull(None)
if self._convex_hull_node:
self._convex_hull_node.setParent(None)
self._convex_hull_node = None
self.recomputeConvexHull()
def _init2DConvexHullCache(self):
# Cache for the group code path in _compute2DConvexHull()
self._2d_convex_hull_group_child_polygon = None
self._2d_convex_hull_group_result = None
# Cache for the mesh code path in _compute2DConvexHull()
self._2d_convex_hull_mesh = None
self._2d_convex_hull_mesh_world_transform = None
self._2d_convex_hull_mesh_result = None
def _compute2DConvexHull(self):
if self._node.callDecoration("isGroup"):
points = numpy.zeros((0, 2), dtype=numpy.int32)
for child in self._node.getChildren():
child_hull = child.callDecoration("_compute2DConvexHull")
if child_hull:
points = numpy.append(points, child_hull.getPoints(), axis = 0)
if points.size < 3:
return None
child_polygon = Polygon(points)
# Check the cache
if child_polygon == self._2d_convex_hull_group_child_polygon:
# Logger.log('d', 'Cache hit in _compute2DConvexHull group path')
return self._2d_convex_hull_group_result
# First, calculate the normal convex hull around the points
convex_hull = child_polygon.getConvexHull()
# Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull.
# This is done because of rounding errors.
rounded_hull = self._roundHull(convex_hull)
# Store the result in the cache
self._2d_convex_hull_group_child_polygon = child_polygon
self._2d_convex_hull_group_result = rounded_hull
return rounded_hull
else:
if not self._node.getMeshData():
return None
mesh = self._node.getMeshData()
world_transform = self._node.getWorldTransformation()
# Check the cache
if mesh is self._2d_convex_hull_mesh and world_transform == self._2d_convex_hull_mesh_world_transform:
# Logger.log('d', 'Cache hit in _compute2DConvexHull mesh path')
return self._2d_convex_hull_mesh_result
vertex_data = mesh.getConvexHullTransformedVertices(world_transform)
# Don't use data below 0.
# TODO; We need a better check for this as this gives poor results for meshes with long edges.
vertex_data = vertex_data[vertex_data[:,1] >= 0]
# Round the vertex data to 1/10th of a mm, then remove all duplicate vertices
# This is done to greatly speed up further convex hull calculations as the convex hull
# becomes much less complex when dealing with highly detailed models.
vertex_data = numpy.round(vertex_data, 1)
vertex_data = vertex_data[:, [0, 2]] # Drop the Y components to project to 2D.
# Grab the set of unique points.
#
# This basically finds the unique rows in the array by treating them as opaque groups of bytes
# which are as long as the 2 float64s in each row, and giving this view to numpy.unique() to munch.
# See http://stackoverflow.com/questions/16970982/find-unique-rows-in-numpy-array
vertex_byte_view = numpy.ascontiguousarray(vertex_data).view(
numpy.dtype((numpy.void, vertex_data.dtype.itemsize * vertex_data.shape[1])))
_, idx = numpy.unique(vertex_byte_view, return_index=True)
vertex_data = vertex_data[idx] # Select the unique rows by index.
hull = Polygon(vertex_data)
# First, calculate the normal convex hull around the points
convex_hull = hull.getConvexHull()
# Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull.
# This is done because of rounding errors.
rounded_hull = convex_hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32)))
# Store the result in the cache
self._2d_convex_hull_mesh = mesh
self._2d_convex_hull_mesh_world_transform = world_transform
self._2d_convex_hull_mesh_result = rounded_hull
return rounded_hull
def _getHeadAndFans(self):
profile = Application.getInstance().getMachineManager().getWorkingProfile()
return Polygon(numpy.array(profile.getSettingValue("machine_head_with_fans_polygon"), numpy.float32))
def _compute2DConvexHeadFull(self):
return self._compute2DConvexHull().getMinkowskiHull(self._getHeadAndFans())
def _compute2DConvexHeadMin(self):
headAndFans = self._getHeadAndFans()
mirrored = headAndFans.mirror([0, 0], [0, 1]).mirror([0, 0], [1, 0]) # Mirror horizontally & vertically.
head_and_fans = self._getHeadAndFans().intersectionConvexHulls(mirrored)
# Min head hull is used for the push free
min_head_hull = self._compute2DConvexHull().getMinkowskiHull(head_and_fans)
return min_head_hull
def _roundHull(self, convex_hull):
return convex_hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32)))

View File

@ -4,6 +4,7 @@
from UM.Job import Job
from UM.Application import Application
from UM.Math.Polygon import Polygon
from UM.Logger import Logger
import numpy
import copy
@ -19,6 +20,11 @@ class ConvexHullJob(Job):
def run(self):
if not self._node:
return
#################################################################
# Node Convex Hull
#################################################################
## If the scene node is a group, use the hull of the children to calculate its hull.
if self._node.callDecoration("isGroup"):
hull = Polygon(numpy.zeros((0, 2), dtype=numpy.int32))
@ -47,10 +53,20 @@ class ConvexHullJob(Job):
# This is done to greatly speed up further convex hull calculations as the convex hull
# becomes much less complex when dealing with highly detailed models.
vertex_data = numpy.round(vertex_data, 1)
duplicates = (vertex_data[:,0] == vertex_data[:,1]) | (vertex_data[:,1] == vertex_data[:,2]) | (vertex_data[:,0] == vertex_data[:,2])
vertex_data = numpy.delete(vertex_data, numpy.where(duplicates), axis = 0)
hull = Polygon(vertex_data[:, [0, 2]])
vertex_data = vertex_data[:, [0, 2]] # Drop the Y components to project to 2D.
# Grab the set of unique points.
#
# This basically finds the unique rows in the array by treating them as opaque groups of bytes
# which are as long as the 2 float64s in each row, and giving this view to numpy.unique() to munch.
# See http://stackoverflow.com/questions/16970982/find-unique-rows-in-numpy-array
vertex_byte_view = numpy.ascontiguousarray(vertex_data).view(
numpy.dtype((numpy.void, vertex_data.dtype.itemsize * vertex_data.shape[1])))
_, idx = numpy.unique(vertex_byte_view, return_index=True)
vertex_data = vertex_data[idx] # Select the unique rows by index.
hull = Polygon(vertex_data)
# First, calculate the normal convex hull around the points
hull = hull.getConvexHull()
@ -59,6 +75,16 @@ class ConvexHullJob(Job):
# This is done because of rounding errors.
hull = hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32)))
#################################################################
# Print Head Exclusion Zone
#################################################################
#
# TODO
# ConvexHullDecorator should use a memoization strategy in its getters.
# Make MeshData immutable
profile = Application.getInstance().getMachineManager().getWorkingProfile()
if profile:
if profile.getSettingValue("print_sequence") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
@ -99,3 +125,33 @@ class ConvexHullJob(Job):
hull_node = self._node.getParent().callDecoration("getConvexHullNode")
if hull_node:
hull_node.setParent(None)
try:
Logger.log('d', 'ConvexHullJob getConvexHull:' + dumpPoly(self._node.callDecoration("getConvexHull")))
Logger.log('d', 'ConvexHullJob new getConvexHull:' + dumpPoly(self._node.callDecoration("newGetConvexHull")))
except Exception:
pass
try:
Logger.log('d', 'ConvexHullJob getConvexHullHeadFull:' + dumpPoly(self._node.callDecoration("getConvexHullHeadFull")))
Logger.log('d', 'ConvexHullJob new getConvexHullHeadFull:' + dumpPoly(self._node.callDecoration("newGetConvexHullHeadFull")))
except Exception:
pass
try:
Logger.log('d', 'ConvexHullJob getConvexHullHead:' + dumpPoly(self._node.callDecoration("getConvexHullHead")))
Logger.log('d', 'ConvexHullJob new getConvexHullHead:' + dumpPoly(self._node.callDecoration("newGetConvexHullHead")))
except Exception:
pass
try:
Logger.log('d', 'ConvexHullJob getConvexHullBoundary:' + dumpPoly(self._node.callDecoration("getConvexHullBoundary")))
Logger.log('d', 'ConvexHullJob new getConvexHullBoundary:' + dumpPoly(self._node.callDecoration("newGetConvexHullBoundary")))
except Exception:
pass
def dumpPoly(poly):
if poly is None:
return "None"
else:
return repr(poly.getPoints())

View File

@ -8,7 +8,7 @@ from UM.Math.Vector import Vector
from UM.Mesh.MeshBuilder import MeshBuilder # To create a mesh to display the convex hull with.
from UM.View.GL.OpenGL import OpenGL
from UM.Logger import spy
class ConvexHullNode(SceneNode):
## Convex hull node is a special type of scene node that is used to display a 2D area, to indicate the
@ -46,6 +46,9 @@ class ConvexHullNode(SceneNode):
if convex_hull_head:
self._convex_hull_head_mesh = self.createHullMesh(convex_hull_head.getPoints())
def getHull(self):
return self._hull
## Actually create the mesh from the hullpoints
# /param hull_points list of xy values
# /return meshData
@ -62,7 +65,7 @@ class ConvexHullNode(SceneNode):
mesh_builder.addFace(point_first, point_previous, point_new, color = self._color)
point_previous = point_new # Prepare point_previous for the next triangle.
return mesh_builder.getData()
return mesh_builder.build()
def getWatchedNode(self):
return self._node
@ -80,9 +83,7 @@ class ConvexHullNode(SceneNode):
return True
def _onNodePositionChanged(self, node):
if node.callDecoration("getConvexHull"):
node.callDecoration("setConvexHull", None)
node.callDecoration("setConvexHullNode", None)
if node.callDecoration("getConvexHull"):
self.setParent(None) # Garbage collection should delete this node after a while.
def _onNodeParentChanged(self, node):

View File

@ -278,9 +278,11 @@ class CuraApplication(QtApplication):
count += 1
if not scene_boundingbox:
scene_boundingbox = copy.deepcopy(node.getBoundingBox())
scene_boundingbox = node.getBoundingBox()
else:
scene_boundingbox += node.getBoundingBox()
other_bb = node.getBoundingBox()
if other_bb is not None:
scene_boundingbox = scene_boundingbox + node.getBoundingBox()
if not scene_boundingbox:
scene_boundingbox = AxisAlignedBox.Null

View File

@ -45,7 +45,7 @@ class PlatformPhysics:
root = self._controller.getScene().getRoot()
for node in BreadthFirstIterator(root):
if node is root or type(node) is not SceneNode:
if node is root or type(node) is not SceneNode or node.getBoundingBox() is None:
continue
bbox = node.getBoundingBox()
@ -73,14 +73,9 @@ class PlatformPhysics:
# 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"):
node.callDecoration("recomputeConvexHull")
if 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.