Merge branch 'master' of github.com:Ultimaker/Cura into feature_multi_materialsnozzles

This commit is contained in:
Jaime van Kessel 2016-06-22 16:32:37 +02:00
commit cbbb204718
13 changed files with 324 additions and 311 deletions

View file

@ -36,6 +36,7 @@ class BuildVolume(SceneNode):
self._disallowed_area_mesh = None self._disallowed_area_mesh = None
self.setCalculateBoundingBox(False) self.setCalculateBoundingBox(False)
self._volume_aabb = None
self._active_container_stack = None self._active_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) 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(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) 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 = MeshBuilder()
mb.addQuad( mb.addQuad(
@ -108,10 +109,10 @@ class BuildVolume(SceneNode):
Vector(max_w, min_h - 0.2, max_d), Vector(max_w, min_h - 0.2, max_d),
Vector(min_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): for n in range(0, 6):
v = self._grid_mesh.getVertex(n) v = mb.getVertex(n)
self._grid_mesh.setVertexUVCoordinates(n, v[0], v[2]) mb.setVertexUVCoordinates(n, v[0], v[2])
self._grid_mesh = mb.build()
disallowed_area_height = 0.1 disallowed_area_height = 0.1
disallowed_area_size = 0 disallowed_area_size = 0
@ -136,11 +137,11 @@ class BuildVolume(SceneNode):
size = 0 size = 0
disallowed_area_size = max(size, disallowed_area_size) disallowed_area_size = max(size, disallowed_area_size)
self._disallowed_area_mesh = mb.getData() self._disallowed_area_mesh = mb.build()
else: else:
self._disallowed_area_mesh = None 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 skirt_size = 0.0
@ -158,6 +159,9 @@ class BuildVolume(SceneNode):
Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds
def getBoundingBox(self):
return self._volume_aabb
def _onGlobalContainerStackChanged(self): def _onGlobalContainerStackChanged(self):
if self._active_container_stack: if self._active_container_stack:
self._active_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged) self._active_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)

View file

@ -1,116 +1,217 @@
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from UM.Application import Application 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. ## 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. # 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): class ConvexHullDecorator(SceneNodeDecorator):
def __init__(self): def __init__(self):
super().__init__() 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_node = None
self._convex_hull_job = None self._init2DConvexHullCache()
# Keep track of the previous parent so we can clear its convex hull when the object is reparented
self._parent_node = None
self._global_stack = None self._global_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
Application.getInstance().getController().toolOperationStarted.connect(self._onChanged)
Application.getInstance().getController().toolOperationStopped.connect(self._onChanged)
self._onGlobalStackChanged() self._onGlobalStackChanged()
#Application.getInstance().getMachineManager().activeProfileChanged.connect(self._onActiveProfileChanged)
#Application.getInstance().getMachineManager().activeMachineInstanceChanged.connect(self._onActiveMachineInstanceChanged)
#self._onActiveProfileChanged()
def setNode(self, node): 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) 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. ## Force that a new (empty) object is created upon copy.
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
copy = ConvexHullDecorator() return ConvexHullDecorator()
return copy
## Get the unmodified convex hull of the node ## Get the unmodified 2D projected convex hull of the node
def getConvexHull(self): 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"), numpy.float32)))
return hull
## Get the convex hull of the node with the full head size ## Get the convex hull of the node with the full head size
def getConvexHullHeadFull(self): def getConvexHullHeadFull(self):
if not self._convex_hull_head_full: if self._node is None:
return self.getConvexHull() return None
return self._convex_hull_head_full
return self._compute2DConvexHeadFull()
## Get convex hull of the object + head size ## Get convex hull of the object + head size
# In case of printing all at once this is the same as the convex hull. # 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 # For one at the time this is area with intersection of mirrored head
def getConvexHullHead(self): def getConvexHullHead(self):
if not self._convex_hull_head: if self._node is None:
return self.getConvexHull() return None
return self._convex_hull_head
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 ## Get convex hull of the node
# In case of printing all at once this is the same as the convex hull. # 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. # For one at the time this is the area without the head.
def getConvexHullBoundary(self): def getConvexHullBoundary(self):
if not self._convex_hull_boundary: if self._node is None:
return self.getConvexHull() return None
return self._convex_hull_boundary
def setConvexHullBoundary(self, hull): if self._global_stack:
self._convex_hull_boundary = hull if self._global_stack("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): def recomputeConvexHull(self):
self._convex_hull_head_full = hull 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): convex_hull = self.getConvexHull()
self._convex_hull_head = hull if self._convex_hull_node:
if self._convex_hull_node.getHull() == convex_hull:
def setConvexHull(self, hull): return
self._convex_hull = hull
if not hull and self._convex_hull_node:
self._convex_hull_node.setParent(None) self._convex_hull_node.setParent(None)
self._convex_hull_node = None hull_node = ConvexHullNode.ConvexHullNode(self._node, convex_hull, root)
self._convex_hull_node = hull_node
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 _onSettingValueChanged(self, key, property_name): def _onSettingValueChanged(self, key, property_name):
if key == "print_sequence" and property_name == "value": if key == "print_sequence" and property_name == "value":
self._onChanged() self._onChanged()
def _onChanged(self, *args): def _init2DConvexHullCache(self):
if self._convex_hull_job: # Cache for the group code path in _compute2DConvexHull()
self._convex_hull_job.cancel() self._2d_convex_hull_group_child_polygon = None
self.setConvexHull(None) self._2d_convex_hull_group_result = None
def _onParentChanged(self, node): # Cache for the mesh code path in _compute2DConvexHull()
# Force updating the convex hull of the parent group if the object is in a group self._2d_convex_hull_mesh = None
if self._parent_node and self._parent_node.callDecoration("isGroup"): self._2d_convex_hull_mesh_world_transform = None
self._parent_node.callDecoration("setConvexHull", None) self._2d_convex_hull_mesh_result = None
self._parent_node = self.getNode().getParent()
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"), 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): def _onGlobalStackChanged(self):
if self._global_stack: if self._global_stack:
@ -124,3 +225,11 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._global_stack.containersChanged.connect(self._onChanged) self._global_stack.containersChanged.connect(self._onChanged)
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())

View file

@ -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)

View file

@ -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 from UM.View.GL.OpenGL import OpenGL
class ConvexHullNode(SceneNode): class ConvexHullNode(SceneNode):
## Convex hull node is a special type of scene node that is used to display a 2D area, to indicate the ## 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 # 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" # The node this mesh is "watching"
self._node = node self._node = node
self._node.transformationChanged.connect(self._onNodePositionChanged)
self._node.parentChanged.connect(self._onNodeParentChanged)
self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged) self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged)
self._onNodeDecoratorsChanged(self._node) self._onNodeDecoratorsChanged(self._node)
self._convex_hull_head_mesh = None self._convex_hull_head_mesh = None
self._hull = hull self._hull = hull
hull_mesh = self.createHullMesh(self._hull.getPoints()) if self._hull:
if hull_mesh: hull_mesh = self.createHullMesh(self._hull.getPoints())
self.setMeshData(hull_mesh) if hull_mesh:
self.setMeshData(hull_mesh)
convex_hull_head = self._node.callDecoration("getConvexHullHead") convex_hull_head = self._node.callDecoration("getConvexHullHead")
if convex_hull_head: if convex_hull_head:
self._convex_hull_head_mesh = self.createHullMesh(convex_hull_head.getPoints()) self._convex_hull_head_mesh = self.createHullMesh(convex_hull_head.getPoints())
def getHull(self):
return self._hull
## Actually create the mesh from the hullpoints ## Actually create the mesh from the hullpoints
# /param hull_points list of xy values # /param hull_points list of xy values
# /return meshData # /return meshData
@ -62,7 +63,7 @@ class ConvexHullNode(SceneNode):
mesh_builder.addFace(point_first, point_previous, point_new, color = self._color) mesh_builder.addFace(point_first, point_previous, point_new, color = self._color)
point_previous = point_new # Prepare point_previous for the next triangle. point_previous = point_new # Prepare point_previous for the next triangle.
return mesh_builder.getData() return mesh_builder.build()
def getWatchedNode(self): def getWatchedNode(self):
return self._node return self._node
@ -73,24 +74,13 @@ class ConvexHullNode(SceneNode):
self._shader.setUniformValue("u_color", self._color) self._shader.setUniformValue("u_color", self._color)
if self.getParent(): if self.getParent():
renderer.queueNode(self, transparent = True, shader = self._shader, backface_cull = True, sort = -8) if self.getMeshData():
if self._convex_hull_head_mesh: renderer.queueNode(self, transparent = True, shader = self._shader, backface_cull = True, sort = -8)
renderer.queueNode(self, shader = self._shader, transparent = True, mesh = self._convex_hull_head_mesh, 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 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): def _onNodeDecoratorsChanged(self, node):
self._color = Color(35, 35, 35, 0.5) self._color = Color(35, 35, 35, 0.5)

View file

@ -49,7 +49,6 @@ from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENU
from PyQt5.QtGui import QColor, QIcon from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
import ast #For literal eval of extruder setting types.
import platform import platform
import sys import sys
import os.path import os.path
@ -122,7 +121,8 @@ class CuraApplication(QtApplication):
self._i18n_catalog = None self._i18n_catalog = None
self._previous_active_tool = None self._previous_active_tool = None
self._platform_activity = False self._platform_activity = False
self._scene_bounding_box = AxisAlignedBox() self._scene_bounding_box = AxisAlignedBox.Null
self._job_name = None self._job_name = None
self._center_after_select = False self._center_after_select = False
self._camera_animation = None self._camera_animation = None
@ -362,7 +362,8 @@ class CuraApplication(QtApplication):
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface...")) 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", qmlRegisterSingletonType(MachineManagerModel.MachineManagerModel, "Cura", 1, 0, "MachineManager",
MachineManagerModel.createMachineManagerModel) MachineManagerModel.createMachineManagerModel)
@ -468,12 +469,14 @@ class CuraApplication(QtApplication):
count += 1 count += 1
if not scene_bounding_box: if not scene_bounding_box:
scene_bounding_box = copy.deepcopy(node.getBoundingBox()) scene_bounding_box = node.getBoundingBox()
else: 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: if not scene_bounding_box:
scene_bounding_box = AxisAlignedBox() scene_bounding_box = AxisAlignedBox.Null
if repr(self._scene_bounding_box) != repr(scene_bounding_box): if repr(self._scene_bounding_box) != repr(scene_bounding_box):
self._scene_bounding_box = scene_bounding_box self._scene_bounding_box = scene_bounding_box
@ -738,7 +741,6 @@ class CuraApplication(QtApplication):
# Add all individual nodes to the selection # Add all individual nodes to the selection
Selection.add(child) Selection.add(child)
child.callDecoration("setConvexHull", None)
op.push() op.push()
# Note: The group removes itself from the scene once all its children have left it, # Note: The group removes itself from the scene once all its children have left it,

View file

@ -96,4 +96,4 @@ class Layer:
builder.addQuad(point1, point2, point3, point4, color = poly_color) builder.addQuad(point1, point2, point3, point4, color = poly_color)
return builder.getData() return builder.build()

View file

@ -1,66 +1,25 @@
# Copyright (c) 2015 Ultimaker B.V. # Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher. # 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 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): class LayerData(MeshData):
def __init__(self): def __init__(self, vertices = None, normals = None, indices = None, colors = None, uvs = None, file_name = None,
super().__init__() center_position = None, layers=None, element_counts=None):
self._layers = {} super().__init__(vertices=vertices, normals=normals, indices=indices, colors=colors, uvs=uvs,
self._element_counts = {} file_name=file_name, center_position=center_position)
self._layers = layers
def addLayer(self, layer): self._element_counts = element_counts
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): def getLayer(self, layer):
if layer in self._layers: if layer in self._layers:
return self._layers[layer] return self._layers[layer]
else:
return None
def getLayers(self): def getLayers(self):
return self._layers return self._layers
def getElementCounts(self): def getElementCounts(self):
return self._element_counts 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
View 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)

View file

@ -14,7 +14,6 @@ from UM.Preferences import Preferences
from cura.ConvexHullDecorator import ConvexHullDecorator from cura.ConvexHullDecorator import ConvexHullDecorator
from . import PlatformPhysicsOperation from . import PlatformPhysicsOperation
from . import ConvexHullJob
from . import ZOffsetDecorator from . import ZOffsetDecorator
import copy import copy
@ -27,7 +26,6 @@ class PlatformPhysics:
self._controller.toolOperationStarted.connect(self._onToolOperationStarted) self._controller.toolOperationStarted.connect(self._onToolOperationStarted)
self._controller.toolOperationStopped.connect(self._onToolOperationStopped) self._controller.toolOperationStopped.connect(self._onToolOperationStopped)
self._build_volume = volume self._build_volume = volume
self._enabled = True self._enabled = True
self._change_timer = QTimer() self._change_timer = QTimer()
@ -46,16 +44,13 @@ class PlatformPhysics:
root = self._controller.getScene().getRoot() root = self._controller.getScene().getRoot()
for node in BreadthFirstIterator(root): 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 continue
bbox = node.getBoundingBox() 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()) # Ignore intersections with the bottom
build_volume_bounding_box.setBottom(-9001) # Ignore intersections with the bottom build_volume_bounding_box = self._build_volume.getBoundingBox().set(bottom=-9001)
node._outside_buildarea = False node._outside_buildarea = False
# Mark the node as outside the build volume if the bounding box test fails. # 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 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 z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
if bbox.bottom > 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: 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): #if not Float.fuzzyCompare(bbox.bottom, 0.0):
# pass#move_vector.setY(-bbox.bottom) # 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 there is no convex hull for the node, start calculating it and continue.
if not node.getDecorator(ConvexHullDecorator): if not node.getDecorator(ConvexHullDecorator):
node.addDecorator(ConvexHullDecorator()) node.addDecorator(ConvexHullDecorator())
node.callDecoration("recomputeConvexHull")
if not node.callDecoration("getConvexHull"):
if not node.callDecoration("getConvexHullJob"): if Preferences.getInstance().getValue("physics/automatic_push_free"):
job = ConvexHullJob.ConvexHullJob(node)
job.start()
node.callDecoration("setConvexHullJob", job)
elif Preferences.getInstance().getValue("physics/automatic_push_free"):
# Check for collisions between convex hulls # Check for collisions between convex hulls
for other_node in BreadthFirstIterator(root): for other_node in BreadthFirstIterator(root):
# Ignore root, ourselves and anything that is not a normal SceneNode. # Ignore root, ourselves and anything that is not a normal SceneNode.
@ -125,8 +115,7 @@ class PlatformPhysics:
if overlap is None: if overlap is None:
continue continue
move_vector.setX(overlap[0] * 1.1) move_vector = move_vector.set(x=overlap[0] * 1.1, z=overlap[1] * 1.1)
move_vector.setZ(overlap[1] * 1.1)
convex_hull = node.callDecoration("getConvexHull") convex_hull = node.callDecoration("getConvexHull")
if convex_hull: if convex_hull:
if not convex_hull.isValid(): if not convex_hull.isValid():
@ -139,7 +128,7 @@ class PlatformPhysics:
node._outside_buildarea = True 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 = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
op.push() op.push()

View file

@ -28,4 +28,4 @@ class PlatformPhysicsOperation(Operation):
return group return group
def __repr__(self): def __repr__(self):
return "PlatformPhysicsOperation(t = {0})".format(self._position) return "PlatformPhysicsOperation(new_position = {0})".format(self._new_position)

View file

@ -12,7 +12,7 @@ from UM.i18n import i18nCatalog
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from cura import LayerData from cura import LayerDataBuilder
from cura import LayerDataDecorator from cura import LayerDataDecorator
import numpy import numpy
@ -63,7 +63,7 @@ class ProcessSlicedLayersJob(Job):
return return
mesh = MeshData() mesh = MeshData()
layer_data = LayerData.LayerData() layer_data = LayerDataBuilder.LayerDataBuilder()
layer_count = len(self._layers) layer_count = len(self._layers)
# Find the minimum layer number # Find the minimum layer number
@ -115,7 +115,7 @@ class ProcessSlicedLayersJob(Job):
self._progress.setProgress(progress) self._progress.setProgress(progress)
# We are done processing all the layers we got from the engine, now create a mesh out of the data # We are done processing all the layers we got from the engine, now create a mesh out of the data
layer_data.build() layer_mesh = layer_data.build()
if self._abort_requested: if self._abort_requested:
if self._progress: if self._progress:
@ -124,7 +124,7 @@ class ProcessSlicedLayersJob(Job):
# Add LayerDataDecorator to scene node to indicate that the node has layer data # Add LayerDataDecorator to scene node to indicate that the node has layer data
decorator = LayerDataDecorator.LayerDataDecorator() decorator = LayerDataDecorator.LayerDataDecorator()
decorator.setLayerData(layer_data) decorator.setLayerData(layer_mesh)
new_node.addDecorator(decorator) new_node.addDecorator(decorator)
new_node.setMeshData(mesh) new_node.setMeshData(mesh)

View file

@ -7,7 +7,7 @@ from PyQt5.QtGui import QImage, qRed, qGreen, qBlue
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from UM.Mesh.MeshReader import MeshReader from UM.Mesh.MeshReader import MeshReader
from UM.Mesh.MeshData import MeshData from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from UM.Job import Job from UM.Job import Job
@ -48,13 +48,9 @@ class ImageReader(MeshReader):
return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.image_color_invert) return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.image_color_invert)
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert): def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert):
mesh = None # TODO: @UnusedVariable
scene_node = None # TODO: @UnusedVariable
scene_node = SceneNode() scene_node = SceneNode()
mesh = MeshData() mesh = MeshBuilder()
scene_node.setMeshData(mesh)
img = QImage(file_name) img = QImage(file_name)
@ -76,9 +72,9 @@ class ImageReader(MeshReader):
scale_vector = Vector(xz_size, peak_height, xz_size) scale_vector = Vector(xz_size, peak_height, xz_size)
if width > height: if width > height:
scale_vector.setZ(scale_vector.z * aspect) scale_vector = scale_vector.set(z=scale_vector.z * aspect)
elif height > width: elif height > width:
scale_vector.setX(scale_vector.x / aspect) scale_vector = scale_vector.set(x=scale_vector.x / aspect)
if width > max_size or height > max_size: if width > max_size or height > max_size:
scale_factor = max_size / width scale_factor = max_size / width
@ -173,8 +169,8 @@ class ImageReader(MeshReader):
geo_height = height_minus_one * texel_height geo_height = height_minus_one * texel_height
# bottom # bottom
mesh.addFace(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height) mesh.addFaceByPoints(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height)
mesh.addFace(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0) mesh.addFaceByPoints(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0)
# north and south walls # north and south walls
for n in range(0, width_minus_one): for n in range(0, width_minus_one):
@ -187,11 +183,11 @@ class ImageReader(MeshReader):
hs0 = height_data[height_minus_one, n] hs0 = height_data[height_minus_one, n]
hs1 = height_data[height_minus_one, n + 1] hs1 = height_data[height_minus_one, n + 1]
mesh.addFace(x, 0, 0, nx, 0, 0, nx, hn1, 0) mesh.addFaceByPoints(x, 0, 0, nx, 0, 0, nx, hn1, 0)
mesh.addFace(nx, hn1, 0, x, hn0, 0, x, 0, 0) mesh.addFaceByPoints(nx, hn1, 0, x, hn0, 0, x, 0, 0)
mesh.addFace(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height) mesh.addFaceByPoints(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height)
mesh.addFace(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height) mesh.addFaceByPoints(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height)
# west and east walls # west and east walls
for n in range(0, height_minus_one): for n in range(0, height_minus_one):
@ -204,12 +200,14 @@ class ImageReader(MeshReader):
he0 = height_data[n, width_minus_one] he0 = height_data[n, width_minus_one]
he1 = height_data[n + 1, width_minus_one] he1 = height_data[n + 1, width_minus_one]
mesh.addFace(0, 0, y, 0, 0, ny, 0, hw1, ny) mesh.addFaceByPoints(0, 0, y, 0, 0, ny, 0, hw1, ny)
mesh.addFace(0, hw1, ny, 0, hw0, y, 0, 0, y) mesh.addFaceByPoints(0, hw1, ny, 0, hw0, y, 0, 0, y)
mesh.addFace(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny) mesh.addFaceByPoints(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny)
mesh.addFace(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y) mesh.addFaceByPoints(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y)
mesh.calculateNormals(fast=True) mesh.calculateNormals(fast=True)
scene_node.setMeshData(mesh.build())
return scene_node return scene_node

View file

@ -8,7 +8,7 @@ from UM.Event import Event, KeyEvent
from UM.Signal import Signal from UM.Signal import Signal
from UM.Scene.Selection import Selection from UM.Scene.Selection import Selection
from UM.Math.Color import Color from UM.Math.Color import Color
from UM.Mesh.MeshData import MeshData from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Job import Job from UM.Job import Job
from UM.Preferences import Preferences from UM.Preferences import Preferences
@ -240,7 +240,7 @@ class _CreateTopLayersJob(Job):
if self._cancel or not layer_data: if self._cancel or not layer_data:
return return
layer_mesh = MeshData() layer_mesh = MeshBuilder()
for i in range(self._solid_layers): for i in range(self._solid_layers):
layer_number = self._layer_number - i layer_number = self._layer_number - i
if layer_number < 0: if layer_number < 0:
@ -275,7 +275,7 @@ class _CreateTopLayersJob(Job):
if not jump_mesh or jump_mesh.getVertices() is None: if not jump_mesh or jump_mesh.getVertices() is None:
jump_mesh = None jump_mesh = None
self.setResult({ "layers": layer_mesh, "jumps": jump_mesh }) self.setResult({ "layers": layer_mesh.build(), "jumps": jump_mesh })
def cancel(self): def cancel(self):
self._cancel = True self._cancel = True