mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-17 03:37:48 -06:00
Merge branch 'master' of github.com:Ultimaker/Cura into feature_multi_materialsnozzles
This commit is contained in:
commit
cbbb204718
13 changed files with 324 additions and 311 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"), 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("print_sequence") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
|
||||
# Printing one at a time and it's not an object in a group
|
||||
return self._compute2DConvexHull()
|
||||
return None
|
||||
|
||||
def setConvexHullHeadFull(self, hull):
|
||||
self._convex_hull_head_full = hull
|
||||
def recomputeConvexHull(self):
|
||||
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"), 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)
|
||||
|
||||
|
|
|
@ -49,7 +49,6 @@ from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENU
|
|||
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
|
||||
|
@ -122,7 +121,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
|
||||
|
@ -362,7 +362,8 @@ 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)
|
||||
|
||||
|
@ -468,12 +469,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
|
||||
|
@ -738,7 +741,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,
|
||||
|
|
|
@ -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)
|
|
@ -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())
|
||||
node.callDecoration("recomputeConvexHull")
|
||||
|
||||
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"):
|
||||
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)
|
||||
|
|
|
@ -12,7 +12,7 @@ from UM.i18n import i18nCatalog
|
|||
|
||||
from UM.Math.Vector import Vector
|
||||
|
||||
from cura import LayerData
|
||||
from cura import LayerDataBuilder
|
||||
from cura import LayerDataDecorator
|
||||
|
||||
import numpy
|
||||
|
@ -63,7 +63,7 @@ class ProcessSlicedLayersJob(Job):
|
|||
return
|
||||
|
||||
mesh = MeshData()
|
||||
layer_data = LayerData.LayerData()
|
||||
layer_data = LayerDataBuilder.LayerDataBuilder()
|
||||
layer_count = len(self._layers)
|
||||
|
||||
# Find the minimum layer number
|
||||
|
@ -115,7 +115,7 @@ class ProcessSlicedLayersJob(Job):
|
|||
self._progress.setProgress(progress)
|
||||
|
||||
# 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._progress:
|
||||
|
@ -124,7 +124,7 @@ class ProcessSlicedLayersJob(Job):
|
|||
|
||||
# Add LayerDataDecorator to scene node to indicate that the node has layer data
|
||||
decorator = LayerDataDecorator.LayerDataDecorator()
|
||||
decorator.setLayerData(layer_data)
|
||||
decorator.setLayerData(layer_mesh)
|
||||
new_node.addDecorator(decorator)
|
||||
|
||||
new_node.setMeshData(mesh)
|
||||
|
|
|
@ -7,7 +7,7 @@ from PyQt5.QtGui import QImage, qRed, qGreen, qBlue
|
|||
from PyQt5.QtCore import Qt
|
||||
|
||||
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.Math.Vector import Vector
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
mesh = MeshData()
|
||||
scene_node.setMeshData(mesh)
|
||||
mesh = MeshBuilder()
|
||||
|
||||
img = QImage(file_name)
|
||||
|
||||
|
@ -76,9 +72,9 @@ class ImageReader(MeshReader):
|
|||
scale_vector = Vector(xz_size, peak_height, xz_size)
|
||||
|
||||
if width > height:
|
||||
scale_vector.setZ(scale_vector.z * aspect)
|
||||
scale_vector = scale_vector.set(z=scale_vector.z * aspect)
|
||||
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:
|
||||
scale_factor = max_size / width
|
||||
|
@ -173,8 +169,8 @@ class ImageReader(MeshReader):
|
|||
geo_height = height_minus_one * texel_height
|
||||
|
||||
# bottom
|
||||
mesh.addFace(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(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height)
|
||||
mesh.addFaceByPoints(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0)
|
||||
|
||||
# north and south walls
|
||||
for n in range(0, width_minus_one):
|
||||
|
@ -187,11 +183,11 @@ class ImageReader(MeshReader):
|
|||
hs0 = height_data[height_minus_one, n]
|
||||
hs1 = height_data[height_minus_one, n + 1]
|
||||
|
||||
mesh.addFace(x, 0, 0, nx, 0, 0, nx, hn1, 0)
|
||||
mesh.addFace(nx, hn1, 0, x, hn0, 0, x, 0, 0)
|
||||
mesh.addFaceByPoints(x, 0, 0, nx, 0, 0, nx, hn1, 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.addFace(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height)
|
||||
mesh.addFaceByPoints(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height)
|
||||
mesh.addFaceByPoints(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height)
|
||||
|
||||
# west and east walls
|
||||
for n in range(0, height_minus_one):
|
||||
|
@ -204,12 +200,14 @@ class ImageReader(MeshReader):
|
|||
he0 = height_data[n, width_minus_one]
|
||||
he1 = height_data[n + 1, width_minus_one]
|
||||
|
||||
mesh.addFace(0, 0, y, 0, 0, ny, 0, hw1, ny)
|
||||
mesh.addFace(0, hw1, ny, 0, hw0, y, 0, 0, y)
|
||||
mesh.addFaceByPoints(0, 0, y, 0, 0, ny, 0, hw1, ny)
|
||||
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.addFace(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y)
|
||||
mesh.addFaceByPoints(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny)
|
||||
mesh.addFaceByPoints(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y)
|
||||
|
||||
mesh.calculateNormals(fast=True)
|
||||
|
||||
scene_node.setMeshData(mesh.build())
|
||||
|
||||
return scene_node
|
||||
|
|
|
@ -8,7 +8,7 @@ from UM.Event import Event, KeyEvent
|
|||
from UM.Signal import Signal
|
||||
from UM.Scene.Selection import Selection
|
||||
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.Preferences import Preferences
|
||||
|
||||
|
@ -240,7 +240,7 @@ class _CreateTopLayersJob(Job):
|
|||
if self._cancel or not layer_data:
|
||||
return
|
||||
|
||||
layer_mesh = MeshData()
|
||||
layer_mesh = MeshBuilder()
|
||||
for i in range(self._solid_layers):
|
||||
layer_number = self._layer_number - i
|
||||
if layer_number < 0:
|
||||
|
@ -275,7 +275,7 @@ class _CreateTopLayersJob(Job):
|
|||
if not jump_mesh or jump_mesh.getVertices() is None:
|
||||
jump_mesh = None
|
||||
|
||||
self.setResult({ "layers": layer_mesh, "jumps": jump_mesh })
|
||||
self.setResult({ "layers": layer_mesh.build(), "jumps": jump_mesh })
|
||||
|
||||
def cancel(self):
|
||||
self._cancel = True
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue