mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-10-10 15:27:53 -06:00
Merge branch 'master' into feature_print_monitoring
# Conflicts: # cura/CuraApplication.py
This commit is contained in:
commit
6d9eb4028e
66 changed files with 25728 additions and 1470 deletions
|
@ -36,6 +36,7 @@ class BuildVolume(SceneNode):
|
|||
self._disallowed_area_mesh = None
|
||||
|
||||
self.setCalculateBoundingBox(False)
|
||||
self._volume_aabb = None
|
||||
|
||||
self._active_container_stack = None
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged)
|
||||
|
@ -99,7 +100,7 @@ class BuildVolume(SceneNode):
|
|||
mb.addLine(Vector(min_w, max_h, min_d), Vector(min_w, max_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(max_w, max_h, min_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor)
|
||||
|
||||
self.setMeshData(mb.getData())
|
||||
self.setMeshData(mb.build())
|
||||
|
||||
mb = MeshBuilder()
|
||||
mb.addQuad(
|
||||
|
@ -108,10 +109,10 @@ class BuildVolume(SceneNode):
|
|||
Vector(max_w, min_h - 0.2, max_d),
|
||||
Vector(min_w, min_h - 0.2, max_d)
|
||||
)
|
||||
self._grid_mesh = mb.getData()
|
||||
for n in range(0, 6):
|
||||
v = self._grid_mesh.getVertex(n)
|
||||
self._grid_mesh.setVertexUVCoordinates(n, v[0], v[2])
|
||||
v = mb.getVertex(n)
|
||||
mb.setVertexUVCoordinates(n, v[0], v[2])
|
||||
self._grid_mesh = mb.build()
|
||||
|
||||
disallowed_area_height = 0.1
|
||||
disallowed_area_size = 0
|
||||
|
@ -136,11 +137,11 @@ class BuildVolume(SceneNode):
|
|||
size = 0
|
||||
disallowed_area_size = max(size, disallowed_area_size)
|
||||
|
||||
self._disallowed_area_mesh = mb.getData()
|
||||
self._disallowed_area_mesh = mb.build()
|
||||
else:
|
||||
self._disallowed_area_mesh = None
|
||||
|
||||
self._aabb = AxisAlignedBox(minimum = Vector(min_w, min_h - 1.0, min_d), maximum = Vector(max_w, max_h, max_d))
|
||||
self._volume_aabb = AxisAlignedBox(minimum = Vector(min_w, min_h - 1.0, min_d), maximum = Vector(max_w, max_h, max_d))
|
||||
|
||||
skirt_size = 0.0
|
||||
|
||||
|
@ -158,6 +159,9 @@ class BuildVolume(SceneNode):
|
|||
|
||||
Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds
|
||||
|
||||
def getBoundingBox(self):
|
||||
return self._volume_aabb
|
||||
|
||||
def _onGlobalContainerStackChanged(self):
|
||||
if self._active_container_stack:
|
||||
self._active_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
||||
|
|
|
@ -1,116 +1,217 @@
|
|||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||
from UM.Application import Application
|
||||
|
||||
from UM.Math.Polygon import Polygon
|
||||
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 = 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
|
||||
|
||||
# Keep track of the previous parent so we can clear its convex hull when the object is reparented
|
||||
self._parent_node = None
|
||||
self._init2DConvexHullCache()
|
||||
|
||||
self._global_stack = None
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
||||
Application.getInstance().getController().toolOperationStarted.connect(self._onChanged)
|
||||
Application.getInstance().getController().toolOperationStopped.connect(self._onChanged)
|
||||
|
||||
self._onGlobalStackChanged()
|
||||
#Application.getInstance().getMachineManager().activeProfileChanged.connect(self._onActiveProfileChanged)
|
||||
#Application.getInstance().getMachineManager().activeMachineInstanceChanged.connect(self._onActiveMachineInstanceChanged)
|
||||
#self._onActiveProfileChanged()
|
||||
|
||||
def setNode(self, node):
|
||||
previous_node = self._node
|
||||
if previous_node is not None and node is not previous_node:
|
||||
previous_node.transformationChanged.connect(self._onChanged)
|
||||
previous_node.parentChanged.connect(self._onChanged)
|
||||
|
||||
super().setNode(node)
|
||||
self._parent_node = node.getParent()
|
||||
node.parentChanged.connect(self._onParentChanged)
|
||||
|
||||
self._node.transformationChanged.connect(self._onChanged)
|
||||
self._node.parentChanged.connect(self._onChanged)
|
||||
|
||||
self._onChanged()
|
||||
|
||||
## 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
|
||||
if self._node is None:
|
||||
return None
|
||||
|
||||
hull = self._compute2DConvexHull()
|
||||
if self._global_stack and self._node:
|
||||
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
|
||||
hull = hull.getMinkowskiHull(Polygon(numpy.array(self._global_stack.getProperty("machine_head_polygon", "value"), 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
|
||||
if self._node is None:
|
||||
return None
|
||||
|
||||
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
|
||||
if self._node is None:
|
||||
return None
|
||||
|
||||
if self._global_stack:
|
||||
if self._global_stack.getProperty("print_sequence", "value") == "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
|
||||
if self._node is None:
|
||||
return None
|
||||
|
||||
def setConvexHullBoundary(self, hull):
|
||||
self._convex_hull_boundary = hull
|
||||
if self._global_stack:
|
||||
if self._global_stack.getProperty("print_sequence", "value") == "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):
|
||||
controller = Application.getInstance().getController()
|
||||
root = controller.getScene().getRoot()
|
||||
if self._node is None or controller.isToolOperationActive() or not self.__isDescendant(root, self._node):
|
||||
if self._convex_hull_node:
|
||||
self._convex_hull_node.setParent(None)
|
||||
self._convex_hull_node = None
|
||||
return
|
||||
|
||||
def setConvexHullHead(self, hull):
|
||||
self._convex_hull_head = hull
|
||||
|
||||
def setConvexHull(self, hull):
|
||||
self._convex_hull = hull
|
||||
if not hull and self._convex_hull_node:
|
||||
convex_hull = self.getConvexHull()
|
||||
if self._convex_hull_node:
|
||||
if self._convex_hull_node.getHull() == convex_hull:
|
||||
return
|
||||
self._convex_hull_node.setParent(None)
|
||||
self._convex_hull_node = None
|
||||
|
||||
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
|
||||
hull_node = ConvexHullNode.ConvexHullNode(self._node, convex_hull, root)
|
||||
self._convex_hull_node = hull_node
|
||||
|
||||
def _onSettingValueChanged(self, key, property_name):
|
||||
if key == "print_sequence" and property_name == "value":
|
||||
self._onChanged()
|
||||
|
||||
def _onChanged(self, *args):
|
||||
if self._convex_hull_job:
|
||||
self._convex_hull_job.cancel()
|
||||
self.setConvexHull(None)
|
||||
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
|
||||
|
||||
def _onParentChanged(self, node):
|
||||
# Force updating the convex hull of the parent group if the object is in a group
|
||||
if self._parent_node and self._parent_node.callDecoration("isGroup"):
|
||||
self._parent_node.callDecoration("setConvexHull", None)
|
||||
self._parent_node = self.getNode().getParent()
|
||||
# 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:
|
||||
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:
|
||||
rounded_hull = None
|
||||
if self._node.getMeshData():
|
||||
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:
|
||||
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]
|
||||
|
||||
if len(vertex_data) >= 4:
|
||||
# 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)
|
||||
|
||||
if len(vertex_data) >= 4:
|
||||
# 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):
|
||||
return Polygon(numpy.array(self._global_stack.getProperty("machine_head_with_fans_polygon", "value"), 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)))
|
||||
|
||||
def _onChanged(self, *args):
|
||||
self.recomputeConvexHull()
|
||||
|
||||
def _onGlobalStackChanged(self):
|
||||
if self._global_stack:
|
||||
|
@ -124,3 +225,11 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
self._global_stack.containersChanged.connect(self._onChanged)
|
||||
|
||||
self._onChanged()
|
||||
|
||||
## Returns true if node is a descendent or the same as the root node.
|
||||
def __isDescendant(self, root, node):
|
||||
if node is None:
|
||||
return False
|
||||
if root is node:
|
||||
return True
|
||||
return self.__isDescendant(root, node.getParent())
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.Job import Job
|
||||
from UM.Application import Application
|
||||
from UM.Math.Polygon import Polygon
|
||||
|
||||
import numpy
|
||||
import copy
|
||||
from . import ConvexHullNode
|
||||
|
||||
## Job to async calculate the convex hull of a node.
|
||||
class ConvexHullJob(Job):
|
||||
def __init__(self, node):
|
||||
super().__init__()
|
||||
|
||||
self._node = node
|
||||
|
||||
def run(self):
|
||||
if not self._node:
|
||||
return
|
||||
## 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))
|
||||
for child in self._node.getChildren():
|
||||
child_hull = child.callDecoration("getConvexHull")
|
||||
if child_hull:
|
||||
hull.setPoints(numpy.append(hull.getPoints(), child_hull.getPoints(), axis = 0))
|
||||
|
||||
if hull.getPoints().size < 3:
|
||||
self._node.callDecoration("setConvexHull", None)
|
||||
self._node.callDecoration("setConvexHullJob", None)
|
||||
return
|
||||
|
||||
Job.yieldThread()
|
||||
|
||||
else:
|
||||
if not self._node.getMeshData():
|
||||
return
|
||||
mesh = self._node.getMeshData()
|
||||
vertex_data = mesh.getTransformed(self._node.getWorldTransformation()).getVertices()
|
||||
# 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
|
||||
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.
|
||||
hull = hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32)))
|
||||
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_stack:
|
||||
if global_stack.getProperty("print_sequence", "value")== "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
|
||||
self._node.callDecoration("setConvexHullBoundary", copy.deepcopy(hull))
|
||||
head_and_fans = Polygon(numpy.array(global_stack.getProperty("machine_head_with_fans_polygon", "value"), numpy.float32))
|
||||
|
||||
# Full head hull is used to actually check the order.
|
||||
full_head_hull = hull.getMinkowskiHull(head_and_fans)
|
||||
self._node.callDecoration("setConvexHullHeadFull", full_head_hull)
|
||||
mirrored = copy.deepcopy(head_and_fans)
|
||||
mirrored.mirror([0, 0], [0, 1]) #Mirror horizontally.
|
||||
mirrored.mirror([0, 0], [1, 0]) #Mirror vertically.
|
||||
head_and_fans = head_and_fans.intersectionConvexHulls(mirrored)
|
||||
|
||||
# Min head hull is used for the push free
|
||||
min_head_hull = hull.getMinkowskiHull(head_and_fans)
|
||||
self._node.callDecoration("setConvexHullHead", min_head_hull)
|
||||
hull = hull.getMinkowskiHull(Polygon(numpy.array(global_stack.getProperty("machine_head_polygon","value"),numpy.float32)))
|
||||
else:
|
||||
self._node.callDecoration("setConvexHullHead", None)
|
||||
if self._node.getParent() is None: # Node was already deleted before job is done.
|
||||
self._node.callDecoration("setConvexHullNode",None)
|
||||
self._node.callDecoration("setConvexHull", None)
|
||||
self._node.callDecoration("setConvexHullJob", None)
|
||||
return
|
||||
|
||||
hull_node = ConvexHullNode.ConvexHullNode(self._node, hull, Application.getInstance().getController().getScene().getRoot())
|
||||
self._node.callDecoration("setConvexHullNode", hull_node)
|
||||
self._node.callDecoration("setConvexHull", hull)
|
||||
self._node.callDecoration("setConvexHullJob", None)
|
||||
|
||||
if self._node.getParent() and self._node.getParent().callDecoration("isGroup"):
|
||||
job = self._node.getParent().callDecoration("getConvexHullJob")
|
||||
if job:
|
||||
job.cancel()
|
||||
self._node.getParent().callDecoration("setConvexHull", None)
|
||||
hull_node = self._node.getParent().callDecoration("getConvexHullNode")
|
||||
if hull_node:
|
||||
hull_node.setParent(None)
|
|
@ -9,7 +9,6 @@ from UM.Mesh.MeshBuilder import MeshBuilder # To create a mesh to display the c
|
|||
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
||||
|
||||
class ConvexHullNode(SceneNode):
|
||||
## Convex hull node is a special type of scene node that is used to display a 2D area, to indicate the
|
||||
# location an object uses on the buildplate. This area (or area's in case of one at a time printing) is
|
||||
|
@ -31,21 +30,23 @@ class ConvexHullNode(SceneNode):
|
|||
|
||||
# The node this mesh is "watching"
|
||||
self._node = node
|
||||
self._node.transformationChanged.connect(self._onNodePositionChanged)
|
||||
self._node.parentChanged.connect(self._onNodeParentChanged)
|
||||
self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged)
|
||||
self._onNodeDecoratorsChanged(self._node)
|
||||
|
||||
self._convex_hull_head_mesh = None
|
||||
self._hull = hull
|
||||
|
||||
hull_mesh = self.createHullMesh(self._hull.getPoints())
|
||||
if hull_mesh:
|
||||
self.setMeshData(hull_mesh)
|
||||
if self._hull:
|
||||
hull_mesh = self.createHullMesh(self._hull.getPoints())
|
||||
if hull_mesh:
|
||||
self.setMeshData(hull_mesh)
|
||||
convex_hull_head = self._node.callDecoration("getConvexHullHead")
|
||||
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 +63,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
|
||||
|
@ -73,24 +74,13 @@ class ConvexHullNode(SceneNode):
|
|||
self._shader.setUniformValue("u_color", self._color)
|
||||
|
||||
if self.getParent():
|
||||
renderer.queueNode(self, transparent = True, shader = self._shader, backface_cull = True, sort = -8)
|
||||
if self._convex_hull_head_mesh:
|
||||
renderer.queueNode(self, shader = self._shader, transparent = True, mesh = self._convex_hull_head_mesh, backface_cull = True, sort = -8)
|
||||
if self.getMeshData():
|
||||
renderer.queueNode(self, transparent = True, shader = self._shader, backface_cull = True, sort = -8)
|
||||
if self._convex_hull_head_mesh:
|
||||
renderer.queueNode(self, shader = self._shader, transparent = True, mesh = self._convex_hull_head_mesh, backface_cull = True, sort = -8)
|
||||
|
||||
return True
|
||||
|
||||
def _onNodePositionChanged(self, node):
|
||||
if node.callDecoration("getConvexHull"):
|
||||
node.callDecoration("setConvexHull", None)
|
||||
node.callDecoration("setConvexHullNode", None)
|
||||
self.setParent(None) # Garbage collection should delete this node after a while.
|
||||
|
||||
def _onNodeParentChanged(self, node):
|
||||
if node.getParent():
|
||||
self.setParent(self._original_parent)
|
||||
else:
|
||||
self.setParent(None)
|
||||
|
||||
def _onNodeDecoratorsChanged(self, node):
|
||||
self._color = Color(35, 35, 35, 0.5)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
from UM.Qt.QtApplication import QtApplication
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.Camera import Camera
|
||||
from UM.Scene.Platform import Platform
|
||||
from UM.Scene.Platform import Platform as Scene_Platform
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Math.Quaternion import Quaternion
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
|
@ -14,6 +14,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
|||
from UM.Mesh.ReadMeshJob import ReadMeshJob
|
||||
from UM.Logger import Logger
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Platform import Platform
|
||||
from UM.JobQueue import JobQueue
|
||||
from UM.SaveFile import SaveFile
|
||||
from UM.Scene.Selection import Selection
|
||||
|
@ -44,12 +45,12 @@ from . import CuraSplashScreen
|
|||
from . import MachineManagerModel
|
||||
from . import ContainerSettingsModel
|
||||
from . import CameraImageProvider
|
||||
from . import MachineActionManager
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
|
||||
from PyQt5.QtGui import QColor, QIcon
|
||||
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
|
||||
|
||||
import ast #For literal eval of extruder setting types.
|
||||
import platform
|
||||
import sys
|
||||
import os.path
|
||||
|
@ -59,7 +60,7 @@ import urllib
|
|||
numpy.seterr(all="ignore")
|
||||
|
||||
#WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612
|
||||
if platform.system() == "Linux": # Needed for platform.linux_distribution, which is not available on Windows and OSX
|
||||
if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX
|
||||
# For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
|
||||
if platform.linux_distribution()[0] in ("Ubuntu", ): # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
|
||||
import ctypes
|
||||
|
@ -100,6 +101,8 @@ class CuraApplication(QtApplication):
|
|||
SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True)
|
||||
SettingDefinition.addSettingType("extruder", int, str, UM.Settings.Validator)
|
||||
|
||||
self._machine_action_manager = MachineActionManager.MachineActionManager()
|
||||
|
||||
super().__init__(name = "cura", version = CuraVersion, buildtype = CuraBuildType)
|
||||
|
||||
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
|
||||
|
@ -122,7 +125,8 @@ class CuraApplication(QtApplication):
|
|||
self._i18n_catalog = None
|
||||
self._previous_active_tool = None
|
||||
self._platform_activity = False
|
||||
self._scene_bounding_box = AxisAlignedBox()
|
||||
self._scene_bounding_box = AxisAlignedBox.Null
|
||||
|
||||
self._job_name = None
|
||||
self._center_after_select = False
|
||||
self._camera_animation = None
|
||||
|
@ -344,7 +348,7 @@ class CuraApplication(QtApplication):
|
|||
Selection.selectionChanged.connect(self.onSelectionChanged)
|
||||
|
||||
root = controller.getScene().getRoot()
|
||||
self._platform = Platform(root)
|
||||
self._platform = Scene_Platform(root)
|
||||
|
||||
self._volume = BuildVolume.BuildVolume(root)
|
||||
|
||||
|
@ -365,10 +369,12 @@ class CuraApplication(QtApplication):
|
|||
|
||||
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))
|
||||
|
||||
ExtruderManager.ExtruderManager.getInstance() #Initialise extruder so as to listen to global container stack changes before the first global container stack is set.
|
||||
# Initialise extruder so as to listen to global container stack changes before the first global container stack is set.
|
||||
ExtruderManager.ExtruderManager.getInstance()
|
||||
qmlRegisterSingletonType(MachineManagerModel.MachineManagerModel, "Cura", 1, 0, "MachineManager",
|
||||
MachineManagerModel.createMachineManagerModel)
|
||||
|
||||
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
||||
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
|
||||
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
|
||||
self.initializeEngine()
|
||||
|
@ -385,6 +391,12 @@ class CuraApplication(QtApplication):
|
|||
|
||||
self.exec_()
|
||||
|
||||
## Get the machine action manager
|
||||
# We ignore any *args given to this, as we also register the machine manager as qml singleton.
|
||||
# It wants to give this function an engine and script engine, but we don't care about that.
|
||||
def getMachineActionManager(self, *args):
|
||||
return self._machine_action_manager
|
||||
|
||||
## Handle Qt events
|
||||
def event(self, event):
|
||||
if event.type() == QEvent.FileOpen:
|
||||
|
@ -471,12 +483,14 @@ class CuraApplication(QtApplication):
|
|||
|
||||
count += 1
|
||||
if not scene_bounding_box:
|
||||
scene_bounding_box = copy.deepcopy(node.getBoundingBox())
|
||||
scene_bounding_box = node.getBoundingBox()
|
||||
else:
|
||||
scene_bounding_box += node.getBoundingBox()
|
||||
other_bb = node.getBoundingBox()
|
||||
if other_bb is not None:
|
||||
scene_bounding_box = scene_bounding_box + node.getBoundingBox()
|
||||
|
||||
if not scene_bounding_box:
|
||||
scene_bounding_box = AxisAlignedBox()
|
||||
scene_bounding_box = AxisAlignedBox.Null
|
||||
|
||||
if repr(self._scene_bounding_box) != repr(scene_bounding_box):
|
||||
self._scene_bounding_box = scene_bounding_box
|
||||
|
@ -741,7 +755,6 @@ class CuraApplication(QtApplication):
|
|||
|
||||
# Add all individual nodes to the selection
|
||||
Selection.add(child)
|
||||
child.callDecoration("setConvexHull", None)
|
||||
|
||||
op.push()
|
||||
# Note: The group removes itself from the scene once all its children have left it,
|
||||
|
|
|
@ -110,14 +110,14 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
# \param description
|
||||
# \return The plugin object matching the given extension and description.
|
||||
def _findProfileWriter(self, extension, description):
|
||||
pr = PluginRegistry.getInstance()
|
||||
plugin_registry = PluginRegistry.getInstance()
|
||||
for plugin_id, meta_data in self._getIOPlugins("profile_writer"):
|
||||
for supported_type in meta_data["profile_writer"]: # All file types this plugin can supposedly write.
|
||||
supported_extension = supported_type.get("extension", None)
|
||||
if supported_extension == extension: # This plugin supports a file type with the same extension.
|
||||
supported_description = supported_type.get("description", None)
|
||||
if supported_description == description: # The description is also identical. Assume it's the same file type.
|
||||
return pr.getPluginObject(plugin_id)
|
||||
return plugin_registry.getPluginObject(plugin_id)
|
||||
return None
|
||||
|
||||
## Imports a profile from a file
|
||||
|
@ -129,9 +129,9 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
if not file_name:
|
||||
return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path")}
|
||||
|
||||
pr = PluginRegistry.getInstance()
|
||||
plugin_registry = PluginRegistry.getInstance()
|
||||
for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
|
||||
profile_reader = pr.getPluginObject(plugin_id)
|
||||
profile_reader = plugin_registry.getPluginObject(plugin_id)
|
||||
try:
|
||||
profile = profile_reader.read(file_name) #Try to open the file with the profile reader.
|
||||
except Exception as e:
|
||||
|
@ -144,6 +144,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
new_name = self.createUniqueName("quality", "", os.path.splitext(os.path.basename(file_name))[0],
|
||||
catalog.i18nc("@label", "Custom profile"))
|
||||
profile.setName(new_name)
|
||||
profile._id = new_name
|
||||
|
||||
if self._machineHasOwnQualities():
|
||||
profile.setDefinition(self._activeDefinition())
|
||||
|
@ -161,12 +162,12 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
## Gets a list of profile writer plugins
|
||||
# \return List of tuples of (plugin_id, meta_data).
|
||||
def _getIOPlugins(self, io_type):
|
||||
pr = PluginRegistry.getInstance()
|
||||
active_plugin_ids = pr.getActivePlugins()
|
||||
plugin_registry = PluginRegistry.getInstance()
|
||||
active_plugin_ids = plugin_registry.getActivePlugins()
|
||||
|
||||
result = []
|
||||
for plugin_id in active_plugin_ids:
|
||||
meta_data = pr.getMetaData(plugin_id)
|
||||
meta_data = plugin_registry.getMetaData(plugin_id)
|
||||
if io_type in meta_data:
|
||||
result.append( (plugin_id, meta_data) )
|
||||
return result
|
||||
|
|
|
@ -111,7 +111,7 @@ class ExtruderManager(QObject):
|
|||
## Creates a container stack for an extruder train.
|
||||
#
|
||||
# The container stack has an extruder definition at the bottom, which is
|
||||
# linked to a machine definition. Then it has a nozzle profile, a material
|
||||
# linked to a machine definition. Then it has a variant profile, a material
|
||||
# profile, a quality profile and a user profile, in that order.
|
||||
#
|
||||
# The resulting container stack is added to the registry.
|
||||
|
@ -136,31 +136,31 @@ class ExtruderManager(QObject):
|
|||
container_stack.addMetaDataEntry("position", position)
|
||||
container_stack.addContainer(extruder_definition)
|
||||
|
||||
#Find the nozzle to use for this extruder.
|
||||
nozzle = container_registry.getEmptyInstanceContainer()
|
||||
if machine_definition.getMetaDataEntry("has_nozzles", default = "False") == "True":
|
||||
#First add any nozzle. Later, overwrite with preference if the preference is valid.
|
||||
nozzles = container_registry.findInstanceContainers(machine = machine_id, type = "nozzle")
|
||||
if len(nozzles) >= 1:
|
||||
nozzle = nozzles[0]
|
||||
preferred_nozzle_id = machine_definition.getMetaDataEntry("preferred_nozzle")
|
||||
if preferred_nozzle_id:
|
||||
preferred_nozzles = container_registry.findInstanceContainers(id = preferred_nozzle_id, type = "nozzle")
|
||||
if len(preferred_nozzles) >= 1:
|
||||
nozzle = preferred_nozzles[0]
|
||||
#Find the variant to use for this extruder.
|
||||
variant = container_registry.getEmptyInstanceContainer()
|
||||
if machine_definition.getMetaDataEntry("has_variants"):
|
||||
#First add any variant. Later, overwrite with preference if the preference is valid.
|
||||
variants = container_registry.findInstanceContainers(definition = machine_id, type = "variant")
|
||||
if len(variants) >= 1:
|
||||
variant = variants[0]
|
||||
preferred_variant_id = machine_definition.getMetaDataEntry("preferred_variant")
|
||||
if preferred_variant_id:
|
||||
preferred_variants = container_registry.findInstanceContainers(id = preferred_variant_id, type = "variant")
|
||||
if len(preferred_variants) >= 1:
|
||||
variant = preferred_variants[0]
|
||||
else:
|
||||
UM.Logger.log("w", "The preferred nozzle \"%s\" of machine %s doesn't exist or is not a nozzle profile.", preferred_nozzle_id, machine_id)
|
||||
#And leave it at the default nozzle.
|
||||
container_stack.addContainer(nozzle)
|
||||
UM.Logger.log("w", "The preferred variant \"%s\" of machine %s doesn't exist or is not a variant profile.", preferred_variant_id, machine_id)
|
||||
#And leave it at the default variant.
|
||||
container_stack.addContainer(variant)
|
||||
|
||||
#Find a material to use for this nozzle.
|
||||
#Find a material to use for this variant.
|
||||
material = container_registry.getEmptyInstanceContainer()
|
||||
if machine_definition.getMetaDataEntry("has_materials", default = "False") == "True":
|
||||
if machine_definition.getMetaDataEntry("has_materials"):
|
||||
#First add any material. Later, overwrite with preference if the preference is valid.
|
||||
if machine_definition.getMetaDataEntry("has_nozzle_materials", default = "False") == "True":
|
||||
materials = container_registry.findInstanceContainers(type = "material", machine = machine_id, nozzle = nozzle.getId())
|
||||
if machine_definition.getMetaDataEntry("has_variant_materials", default = "False") == "True":
|
||||
materials = container_registry.findInstanceContainers(type = "material", definition = machine_id, variant = variant.getId())
|
||||
else:
|
||||
materials = container_registry.findInstanceContainers(type = "material", machine = machine_id)
|
||||
materials = container_registry.findInstanceContainers(type = "material", definition = machine_id)
|
||||
if len(materials) >= 1:
|
||||
material = materials[0]
|
||||
preferred_material_id = machine_definition.getMetaDataEntry("preferred_material")
|
||||
|
@ -175,14 +175,14 @@ class ExtruderManager(QObject):
|
|||
|
||||
#Find a quality to use for this extruder.
|
||||
quality = container_registry.getEmptyInstanceContainer()
|
||||
|
||||
|
||||
#First add any quality. Later, overwrite with preference if the preference is valid.
|
||||
qualities = container_registry.findInstanceContainers(type = "quality")
|
||||
if len(qualities) >= 1:
|
||||
quality = qualities[0]
|
||||
preferred_quality_id = machine_definition.getMetaDataEntry("preferred_quality")
|
||||
if preferred_quality_id:
|
||||
preferred_quality = container_registry.findInstanceContainers(id = preferred_quality_id.lower(), type = "quality")
|
||||
preferred_quality = container_registry.findInstanceContainers(id = preferred_quality_id, type = "quality")
|
||||
if len(preferred_quality) >= 1:
|
||||
quality = preferred_quality[0]
|
||||
else:
|
||||
|
|
|
@ -46,12 +46,17 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
|
||||
self._add_global = False
|
||||
|
||||
self._active_extruder_stack = None
|
||||
|
||||
#Listen to changes.
|
||||
manager = cura.ExtruderManager.ExtruderManager.getInstance()
|
||||
manager.extrudersChanged.connect(self._updateExtruders) #When the list of extruders changes in general.
|
||||
UM.Application.globalContainerStackChanged.connect(self._updateExtruders) #When the current machine changes.
|
||||
UM.Application.getInstance().globalContainerStackChanged.connect(self._updateExtruders) #When the current machine changes.
|
||||
self._updateExtruders()
|
||||
|
||||
manager.activeExtruderChanged.connect(self._onActiveExtruderChanged)
|
||||
self._onActiveExtruderChanged()
|
||||
|
||||
def setAddGlobal(self, add):
|
||||
if add != self._add_global:
|
||||
self._add_global = add
|
||||
|
@ -63,6 +68,26 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
def addGlobal(self):
|
||||
return self._add_global
|
||||
|
||||
def _onActiveExtruderChanged(self):
|
||||
manager = cura.ExtruderManager.ExtruderManager.getInstance()
|
||||
active_extruder_stack = manager.getActiveExtruderStack()
|
||||
if self._active_extruder_stack != active_extruder_stack:
|
||||
if self._active_extruder_stack:
|
||||
self._active_extruder_stack.containersChanged.disconnect(self._onExtruderStackContainersChanged)
|
||||
|
||||
if active_extruder_stack:
|
||||
# Update the model when the material container is changed
|
||||
active_extruder_stack.containersChanged.connect(self._onExtruderStackContainersChanged)
|
||||
self._active_extruder_stack = active_extruder_stack
|
||||
|
||||
|
||||
def _onExtruderStackContainersChanged(self, container):
|
||||
# The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name
|
||||
if container.getMetaDataEntry("type") == "material":
|
||||
self._updateExtruders()
|
||||
|
||||
modelChanged = pyqtSignal()
|
||||
|
||||
## Update the list of extruders.
|
||||
#
|
||||
# This should be called whenever the list of extruders changes.
|
||||
|
@ -85,7 +110,10 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
self.appendItem(item)
|
||||
|
||||
for extruder in manager.getMachineExtruders(global_container_stack.getBottom().getId()):
|
||||
extruder_name = extruder.getName()
|
||||
material = extruder.findContainer({ "type": "material" })
|
||||
if material:
|
||||
extruder_name = "%s (%s)" % (material.getName(), extruder_name)
|
||||
position = extruder.getBottom().getMetaDataEntry("position", default = "0") #Position in the definition.
|
||||
try:
|
||||
position = int(position)
|
||||
|
@ -95,10 +123,11 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
colour = material.getMetaDataEntry("color_code", default = default_colour) if material else default_colour
|
||||
item = { #Construct an item with only the relevant information.
|
||||
"id": extruder.getId(),
|
||||
"name": extruder.getName(),
|
||||
"name": extruder_name,
|
||||
"colour": colour,
|
||||
"index": position
|
||||
}
|
||||
self.appendItem(item)
|
||||
|
||||
self.sort(lambda item: item["index"])
|
||||
self.modelChanged.emit()
|
||||
|
|
|
@ -96,4 +96,4 @@ class Layer:
|
|||
|
||||
builder.addQuad(point1, point2, point3, point4, color = poly_color)
|
||||
|
||||
return builder.getData()
|
||||
return builder.build()
|
||||
|
|
|
@ -1,66 +1,25 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
from .Layer import Layer
|
||||
from .LayerPolygon import LayerPolygon
|
||||
from UM.Mesh.MeshData import MeshData
|
||||
|
||||
import numpy
|
||||
|
||||
|
||||
## Class to holds the layer mesh and information about the layers.
|
||||
# Immutable, use LayerDataBuilder to create one of these.
|
||||
class LayerData(MeshData):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._layers = {}
|
||||
self._element_counts = {}
|
||||
|
||||
def addLayer(self, layer):
|
||||
if layer not in self._layers:
|
||||
self._layers[layer] = Layer(layer)
|
||||
|
||||
def addPolygon(self, layer, polygon_type, data, line_width):
|
||||
if layer not in self._layers:
|
||||
self.addLayer(layer)
|
||||
|
||||
p = LayerPolygon(self, polygon_type, data, line_width)
|
||||
self._layers[layer].polygons.append(p)
|
||||
def __init__(self, vertices = None, normals = None, indices = None, colors = None, uvs = None, file_name = None,
|
||||
center_position = None, layers=None, element_counts=None):
|
||||
super().__init__(vertices=vertices, normals=normals, indices=indices, colors=colors, uvs=uvs,
|
||||
file_name=file_name, center_position=center_position)
|
||||
self._layers = layers
|
||||
self._element_counts = element_counts
|
||||
|
||||
def getLayer(self, layer):
|
||||
if layer in self._layers:
|
||||
return self._layers[layer]
|
||||
else:
|
||||
return None
|
||||
|
||||
def getLayers(self):
|
||||
return self._layers
|
||||
|
||||
def getElementCounts(self):
|
||||
return self._element_counts
|
||||
|
||||
def setLayerHeight(self, layer, height):
|
||||
if layer not in self._layers:
|
||||
self.addLayer(layer)
|
||||
|
||||
self._layers[layer].setHeight(height)
|
||||
|
||||
def setLayerThickness(self, layer, thickness):
|
||||
if layer not in self._layers:
|
||||
self.addLayer(layer)
|
||||
|
||||
self._layers[layer].setThickness(thickness)
|
||||
|
||||
def build(self):
|
||||
vertex_count = 0
|
||||
for layer, data in self._layers.items():
|
||||
vertex_count += data.vertexCount()
|
||||
|
||||
vertices = numpy.empty((vertex_count, 3), numpy.float32)
|
||||
colors = numpy.empty((vertex_count, 4), numpy.float32)
|
||||
indices = numpy.empty((vertex_count, 2), numpy.int32)
|
||||
|
||||
offset = 0
|
||||
for layer, data in self._layers.items():
|
||||
offset = data.build(offset, vertices, colors, indices)
|
||||
self._element_counts[layer] = data.elementCount
|
||||
|
||||
self.clear()
|
||||
self.addVertices(vertices)
|
||||
self.addColors(colors)
|
||||
self.addIndices(indices.flatten())
|
||||
|
|
72
cura/LayerDataBuilder.py
Normal file
72
cura/LayerDataBuilder.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from .Layer import Layer
|
||||
from .LayerPolygon import LayerPolygon
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||
from .LayerData import LayerData
|
||||
|
||||
import numpy
|
||||
|
||||
## Builder class for constructing a LayerData object
|
||||
class LayerDataBuilder(MeshBuilder):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._layers = {}
|
||||
self._element_counts = {}
|
||||
|
||||
def addLayer(self, layer):
|
||||
if layer not in self._layers:
|
||||
self._layers[layer] = Layer(layer)
|
||||
|
||||
def addPolygon(self, layer, polygon_type, data, line_width):
|
||||
if layer not in self._layers:
|
||||
self.addLayer(layer)
|
||||
|
||||
p = LayerPolygon(self, polygon_type, data, line_width)
|
||||
self._layers[layer].polygons.append(p)
|
||||
|
||||
def getLayer(self, layer):
|
||||
if layer in self._layers:
|
||||
return self._layers[layer]
|
||||
|
||||
def getLayers(self):
|
||||
return self._layers
|
||||
|
||||
def getElementCounts(self):
|
||||
return self._element_counts
|
||||
|
||||
def setLayerHeight(self, layer, height):
|
||||
if layer not in self._layers:
|
||||
self.addLayer(layer)
|
||||
|
||||
self._layers[layer].setHeight(height)
|
||||
|
||||
def setLayerThickness(self, layer, thickness):
|
||||
if layer not in self._layers:
|
||||
self.addLayer(layer)
|
||||
|
||||
self._layers[layer].setThickness(thickness)
|
||||
|
||||
def build(self):
|
||||
vertex_count = 0
|
||||
for layer, data in self._layers.items():
|
||||
vertex_count += data.vertexCount()
|
||||
|
||||
vertices = numpy.empty((vertex_count, 3), numpy.float32)
|
||||
colors = numpy.empty((vertex_count, 4), numpy.float32)
|
||||
indices = numpy.empty((vertex_count, 2), numpy.int32)
|
||||
|
||||
offset = 0
|
||||
for layer, data in self._layers.items():
|
||||
offset = data.build(offset, vertices, colors, indices)
|
||||
self._element_counts[layer] = data.elementCount
|
||||
|
||||
self.addVertices(vertices)
|
||||
self.addColors(colors)
|
||||
self.addIndices(indices.flatten())
|
||||
|
||||
return LayerData(vertices=self.getVertices(), normals=self.getNormals(), indices=self.getIndices(),
|
||||
colors=self.getColors(), uvs=self.getUVCoordinates(), file_name=self.getFileName(),
|
||||
center_position=self.getCenterPosition(), layers=self._layers,
|
||||
element_counts=self._element_counts)
|
78
cura/MachineAction.py
Normal file
78
cura/MachineAction.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal, QUrl
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
|
||||
from UM.PluginObject import PluginObject
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
||||
from UM.Application import Application
|
||||
|
||||
import os
|
||||
|
||||
|
||||
class MachineAction(QObject, PluginObject):
|
||||
def __init__(self, key, label = ""):
|
||||
super().__init__()
|
||||
self._key = key
|
||||
self._label = label
|
||||
self._qml_url = ""
|
||||
|
||||
self._component = None
|
||||
self._context = None
|
||||
self._view = None
|
||||
self._finished = False
|
||||
|
||||
labelChanged = pyqtSignal()
|
||||
onFinished = pyqtSignal()
|
||||
|
||||
def getKey(self):
|
||||
return self._key
|
||||
|
||||
@pyqtProperty(str, notify = labelChanged)
|
||||
def label(self):
|
||||
return self._label
|
||||
|
||||
def setLabel(self, label):
|
||||
if self._label != label:
|
||||
self._label = label
|
||||
self.labelChanged.emit()
|
||||
|
||||
## Reset the action to it's default state.
|
||||
# This should not be re-implemented by child classes, instead re-implement _reset.
|
||||
# /sa _reset
|
||||
@pyqtSlot()
|
||||
def reset(self):
|
||||
self._finished = False
|
||||
self._reset()
|
||||
|
||||
## Protected implementation of reset.
|
||||
# /sa reset()
|
||||
def _reset(self):
|
||||
pass
|
||||
|
||||
@pyqtSlot()
|
||||
def setFinished(self):
|
||||
self._finished = True
|
||||
self._reset()
|
||||
self.onFinished.emit()
|
||||
|
||||
@pyqtProperty(bool, notify = onFinished)
|
||||
def finished(self):
|
||||
return self._finished
|
||||
|
||||
def _createViewFromQML(self):
|
||||
path = QUrl.fromLocalFile(
|
||||
os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url))
|
||||
self._component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
self._context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._context.setContextProperty("manager", self)
|
||||
self._view = self._component.create(self._context)
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def displayItem(self):
|
||||
if not self._component:
|
||||
self._createViewFromQML()
|
||||
|
||||
return self._view
|
143
cura/MachineActionManager.py
Normal file
143
cura/MachineActionManager.py
Normal file
|
@ -0,0 +1,143 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
from UM.Logger import Logger
|
||||
from UM.PluginRegistry import PluginRegistry # So MachineAction can be added as plugin type
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
|
||||
## Raised when trying to add an unknown machine action as a required action
|
||||
class UnknownMachineActionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
## Raised when trying to add a machine action that does not have an unique key.
|
||||
class NotUniqueMachineActionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MachineActionManager(QObject):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._machine_actions = {} # Dict of all known machine actions
|
||||
self._required_actions = {} # Dict of all required actions by definition ID
|
||||
self._supported_actions = {} # Dict of all supported actions by definition ID
|
||||
self._first_start_actions = {} # Dict of all actions that need to be done when first added by definition ID
|
||||
|
||||
# Add machine_action as plugin type
|
||||
PluginRegistry.addType("machine_action", self.addMachineAction)
|
||||
|
||||
# Ensure that all containers that were registered before creation of this registry are also handled.
|
||||
# This should not have any effect, but it makes it safer if we ever refactor the order of things.
|
||||
for container in ContainerRegistry.getInstance().findDefinitionContainers():
|
||||
self._onContainerAdded(container)
|
||||
|
||||
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
|
||||
|
||||
def _onContainerAdded(self, container):
|
||||
## Ensure that the actions are added to this manager
|
||||
if isinstance(container, DefinitionContainer):
|
||||
supported_actions = container.getMetaDataEntry("supported_actions", [])
|
||||
for action in supported_actions:
|
||||
self.addSupportedAction(container.getId(), action)
|
||||
|
||||
required_actions = container.getMetaDataEntry("required_actions", [])
|
||||
for action in required_actions:
|
||||
self.addRequiredAction(container.getId(), action)
|
||||
|
||||
first_start_actions = container.getMetaDataEntry("first_start_actions", [])
|
||||
for action in first_start_actions:
|
||||
self.addFirstStartAction(container.getId(), action)
|
||||
|
||||
## Add a required action to a machine
|
||||
# Raises an exception when the action is not recognised.
|
||||
def addRequiredAction(self, definition_id, action_key):
|
||||
if action_key in self._machine_actions:
|
||||
if definition_id in self._required_actions:
|
||||
self._required_actions[definition_id] |= {self._machine_actions[action_key]}
|
||||
else:
|
||||
self._required_actions[definition_id] = {self._machine_actions[action_key]}
|
||||
else:
|
||||
raise UnknownMachineActionError("Action %s, which is required for %s is not known." % (action_key, definition_id))
|
||||
|
||||
## Add a supported action to a machine.
|
||||
def addSupportedAction(self, definition_id, action_key):
|
||||
if action_key in self._machine_actions:
|
||||
if definition_id in self._supported_actions:
|
||||
self._supported_actions[definition_id] |= {self._machine_actions[action_key]}
|
||||
else:
|
||||
self._supported_actions[definition_id] = {self._machine_actions[action_key]}
|
||||
else:
|
||||
Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id)
|
||||
|
||||
## Add an action to the first start list of a machine.
|
||||
def addFirstStartAction(self, definition_id, action_key, index = None):
|
||||
if action_key in self._machine_actions:
|
||||
if definition_id in self._first_start_actions:
|
||||
if index is not None:
|
||||
self._first_start_actions[definition_id].insert(index, self._machine_actions[action_key])
|
||||
else:
|
||||
self._first_start_actions[definition_id].append(self._machine_actions[action_key])
|
||||
else:
|
||||
self._first_start_actions[definition_id] = [self._machine_actions[action_key]]
|
||||
else:
|
||||
Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id)
|
||||
|
||||
## Add a (unique) MachineAction
|
||||
# if the Key of the action is not unique, an exception is raised.
|
||||
def addMachineAction(self, action):
|
||||
if action.getKey() not in self._machine_actions:
|
||||
self._machine_actions[action.getKey()] = action
|
||||
else:
|
||||
raise NotUniqueMachineActionError("MachineAction with key %s was already added. Actions must have unique keys.", action.getKey())
|
||||
|
||||
## Get all actions supported by given machine
|
||||
# \param definition_id The ID of the definition you want the supported actions of
|
||||
# \returns set of supported actions.
|
||||
@pyqtSlot(str, result = "QVariantList")
|
||||
def getSupportedActions(self, definition_id):
|
||||
if definition_id in self._supported_actions:
|
||||
return list(self._supported_actions[definition_id])
|
||||
else:
|
||||
return set()
|
||||
|
||||
## Get all actions required by given machine
|
||||
# \param definition_id The ID of the definition you want the required actions of
|
||||
# \returns set of required actions.
|
||||
def getRequiredActions(self, definition_id):
|
||||
if definition_id in self._required_actions:
|
||||
return self._required_actions[definition_id]
|
||||
else:
|
||||
return set()
|
||||
|
||||
## Get all actions that need to be performed upon first start of a given machine.
|
||||
# Note that contrary to required / supported actions a list is returned (as it could be required to run the same
|
||||
# action multiple times).
|
||||
# \param definition_id The ID of the definition that you want to get the "on added" actions for.
|
||||
# \returns List of actions.
|
||||
@pyqtSlot(str, result="QVariantList")
|
||||
def getFirstStartActions(self, definition_id):
|
||||
if definition_id in self._first_start_actions:
|
||||
return self._first_start_actions[definition_id]
|
||||
else:
|
||||
return []
|
||||
|
||||
## Remove Machine action from manager
|
||||
# \param action to remove
|
||||
def removeMachineAction(self, action):
|
||||
try:
|
||||
del self._machine_actions[action.getKey()]
|
||||
except KeyError:
|
||||
Logger.log("w", "Trying to remove MachineAction (%s) that was already removed", action.getKey())
|
||||
|
||||
## Get MachineAction by key
|
||||
# \param key String of key to select
|
||||
# \return Machine action if found, None otherwise
|
||||
def getMachineAction(self, key):
|
||||
if key in self._machine_actions:
|
||||
return self._machine_actions[key]
|
||||
else:
|
||||
return None
|
|
@ -74,15 +74,6 @@ class MachineManagerModel(QObject):
|
|||
def _onOutputDevicesChanged(self):
|
||||
self.outputDevicesChanged.emit()
|
||||
|
||||
@pyqtProperty("QVariantMap", notify = globalContainerChanged)
|
||||
def extrudersIds(self):
|
||||
## Find all extruders that reference the new stack
|
||||
extruders = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(**{"machine": self._global_container_stack.getId()})
|
||||
result = {}
|
||||
for extruder in extruders:
|
||||
result[extruder.getMetaDataEntry("position")] = extruder.getId()
|
||||
return result
|
||||
|
||||
def _onGlobalPropertyChanged(self, key, property_name):
|
||||
if property_name == "value":
|
||||
self.globalValueChanged.emit()
|
||||
|
@ -385,11 +376,16 @@ class MachineManagerModel(QObject):
|
|||
return
|
||||
|
||||
old_material = self._active_container_stack.findContainer({"type":"material"})
|
||||
old_quality = self._active_container_stack.findContainer({"type": "quality"})
|
||||
if old_material:
|
||||
material_index = self._active_container_stack.getContainerIndex(old_material)
|
||||
self._active_container_stack.replaceContainer(material_index, containers[0])
|
||||
|
||||
self.setActiveQuality(self._updateQualityContainer(self._active_container_stack.getBottom(), containers[0]).id)
|
||||
preferred_quality_name = None
|
||||
if old_quality:
|
||||
preferred_quality_name = old_quality.getName()
|
||||
|
||||
self.setActiveQuality(self._updateQualityContainer(self._global_container_stack.getBottom(), containers[0], preferred_quality_name).id)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActiveVariant(self, variant_id):
|
||||
|
@ -398,11 +394,16 @@ class MachineManagerModel(QObject):
|
|||
return
|
||||
|
||||
old_variant = self._active_container_stack.findContainer({"type": "variant"})
|
||||
old_material = self._active_container_stack.findContainer({"type": "material"})
|
||||
if old_variant:
|
||||
variant_index = self._active_container_stack.getContainerIndex(old_variant)
|
||||
self._active_container_stack.replaceContainer(variant_index, containers[0])
|
||||
|
||||
self.setActiveMaterial(self._updateMaterialContainer(self._active_container_stack.getBottom(), containers[0]).id)
|
||||
preferred_material = None
|
||||
if old_material:
|
||||
preferred_material = old_material.getId()
|
||||
|
||||
self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), containers[0], preferred_material).id)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActiveQuality(self, quality_id):
|
||||
|
@ -512,7 +513,7 @@ class MachineManagerModel(QObject):
|
|||
|
||||
return self._empty_variant_container
|
||||
|
||||
def _updateMaterialContainer(self, definition, variant_container = None):
|
||||
def _updateMaterialContainer(self, definition, variant_container = None, preferred_material = None):
|
||||
if not definition.getMetaDataEntry("has_materials"):
|
||||
return self._empty_material_container
|
||||
|
||||
|
@ -526,7 +527,8 @@ class MachineManagerModel(QObject):
|
|||
else:
|
||||
search_criteria["definition"] = "fdmprinter"
|
||||
|
||||
preferred_material = definition.getMetaDataEntry("preferred_material")
|
||||
if not preferred_material:
|
||||
preferred_material = definition.getMetaDataEntry("preferred_material")
|
||||
if preferred_material:
|
||||
search_criteria["id"] = preferred_material
|
||||
|
||||
|
@ -536,7 +538,7 @@ class MachineManagerModel(QObject):
|
|||
|
||||
return self._empty_material_container
|
||||
|
||||
def _updateQualityContainer(self, definition, material_container = None):
|
||||
def _updateQualityContainer(self, definition, material_container = None, preferred_quality_name = None):
|
||||
search_criteria = { "type": "quality" }
|
||||
|
||||
if definition.getMetaDataEntry("has_machine_quality"):
|
||||
|
@ -547,9 +549,12 @@ class MachineManagerModel(QObject):
|
|||
else:
|
||||
search_criteria["definition"] = "fdmprinter"
|
||||
|
||||
preferred_quality = definition.getMetaDataEntry("preferred_quality")
|
||||
if preferred_quality:
|
||||
search_criteria["id"] = preferred_quality
|
||||
if preferred_quality_name:
|
||||
search_criteria["name"] = preferred_quality_name
|
||||
else:
|
||||
preferred_quality = definition.getMetaDataEntry("preferred_quality")
|
||||
if preferred_quality:
|
||||
search_criteria["id"] = preferred_quality
|
||||
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
|
|
|
@ -14,7 +14,6 @@ from UM.Preferences import Preferences
|
|||
from cura.ConvexHullDecorator import ConvexHullDecorator
|
||||
|
||||
from . import PlatformPhysicsOperation
|
||||
from . import ConvexHullJob
|
||||
from . import ZOffsetDecorator
|
||||
|
||||
import copy
|
||||
|
@ -27,7 +26,6 @@ class PlatformPhysics:
|
|||
self._controller.toolOperationStarted.connect(self._onToolOperationStarted)
|
||||
self._controller.toolOperationStopped.connect(self._onToolOperationStopped)
|
||||
self._build_volume = volume
|
||||
|
||||
self._enabled = True
|
||||
|
||||
self._change_timer = QTimer()
|
||||
|
@ -46,16 +44,13 @@ 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()
|
||||
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
|
||||
# Ignore intersections with the bottom
|
||||
build_volume_bounding_box = self._build_volume.getBoundingBox().set(bottom=-9001)
|
||||
node._outside_buildarea = False
|
||||
|
||||
# Mark the node as outside the build volume if the bounding box test fails.
|
||||
|
@ -67,9 +62,9 @@ class PlatformPhysics:
|
|||
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)
|
||||
move_vector = move_vector.set(y=-bbox.bottom + z_offset)
|
||||
elif bbox.bottom < z_offset:
|
||||
move_vector.setY((-bbox.bottom) - z_offset)
|
||||
move_vector = move_vector.set(y=(-bbox.bottom) - z_offset)
|
||||
|
||||
#if not Float.fuzzyCompare(bbox.bottom, 0.0):
|
||||
# pass#move_vector.setY(-bbox.bottom)
|
||||
|
@ -77,14 +72,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.
|
||||
|
@ -125,8 +115,7 @@ class PlatformPhysics:
|
|||
|
||||
if overlap is None:
|
||||
continue
|
||||
move_vector.setX(overlap[0] * 1.1)
|
||||
move_vector.setZ(overlap[1] * 1.1)
|
||||
move_vector = move_vector.set(x=overlap[0] * 1.1, z=overlap[1] * 1.1)
|
||||
convex_hull = node.callDecoration("getConvexHull")
|
||||
if convex_hull:
|
||||
if not convex_hull.isValid():
|
||||
|
@ -139,7 +128,7 @@ class PlatformPhysics:
|
|||
|
||||
node._outside_buildarea = True
|
||||
|
||||
if move_vector != Vector():
|
||||
if not Vector.Null.equals(move_vector, epsilon=1e-5):
|
||||
op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
|
||||
op.push()
|
||||
|
||||
|
|
|
@ -28,4 +28,4 @@ class PlatformPhysicsOperation(Operation):
|
|||
return group
|
||||
|
||||
def __repr__(self):
|
||||
return "PlatformPhysicsOperation(t = {0})".format(self._position)
|
||||
return "PlatformPhysicsOperation(new_position = {0})".format(self._new_position)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue