123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- 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):
- super().__init__()
- self._convex_hull_node = None
- self._init2DConvexHullCache()
- self._profile = None
- Application.getInstance().getMachineManager().activeProfileChanged.connect(self._onActiveProfileChanged)
- Application.getInstance().getMachineManager().activeMachineInstanceChanged.connect(self._onActiveMachineInstanceChanged)
- self._onActiveProfileChanged()
- ## Force that a new (empty) object is created upon copy.
- def __deepcopy__(self, memo):
- return ConvexHullDecorator()
- ## Get the unmodified 2D projected convex hull of the node
- def getConvexHull(self):
- 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):
- 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):
- 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):
- 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 recomputeConvexHull(self):
- convex_hull = self.getConvexHull()
- if self._convex_hull_node:
- if self._convex_hull_node.getHull() == convex_hull:
- return
- self._convex_hull_node.setParent(None)
- hull_node = ConvexHullNode.ConvexHullNode(self._node, convex_hull,
- Application.getInstance().getController().getScene().getRoot())
- self._convex_hull_node = hull_node
- def _onActiveProfileChanged(self):
- if self._profile:
- self._profile.settingValueChanged.disconnect(self._onSettingValueChanged)
- self._profile = Application.getInstance().getMachineManager().getWorkingProfile()
- if self._profile:
- self._profile.settingValueChanged.connect(self._onSettingValueChanged)
- def _onActiveMachineInstanceChanged(self):
- if self._convex_hull_node:
- self._convex_hull_node.setParent(None)
- self._convex_hull_node = None
- def _onSettingValueChanged(self, setting):
- if setting == "print_sequence":
- 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)))
|