mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 22:47:29 -06:00
Added first arrange function and smart placement after loading. CURA-3239
This commit is contained in:
parent
d8c20b9d6c
commit
bf08d30e7d
3 changed files with 133 additions and 30 deletions
|
@ -34,6 +34,7 @@ from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
||||||
from UM.Operations.GroupedOperation import GroupedOperation
|
from UM.Operations.GroupedOperation import GroupedOperation
|
||||||
from UM.Operations.SetTransformOperation import SetTransformOperation
|
from UM.Operations.SetTransformOperation import SetTransformOperation
|
||||||
from cura.Arrange import Arrange, ShapeArray
|
from cura.Arrange import Arrange, ShapeArray
|
||||||
|
from cura.ConvexHullDecorator import ConvexHullDecorator
|
||||||
from cura.SetParentOperation import SetParentOperation
|
from cura.SetParentOperation import SetParentOperation
|
||||||
from cura.SliceableObjectDecorator import SliceableObjectDecorator
|
from cura.SliceableObjectDecorator import SliceableObjectDecorator
|
||||||
from cura.BlockSlicingDecorator import BlockSlicingDecorator
|
from cura.BlockSlicingDecorator import BlockSlicingDecorator
|
||||||
|
@ -845,36 +846,31 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
op.push()
|
op.push()
|
||||||
|
|
||||||
## Create a number of copies of existing object.
|
## Testing, prepare arranger for use
|
||||||
# object_id
|
def _prepareArranger(self, fixed_nodes = None):
|
||||||
# count: number of copies
|
arranger = Arrange(215, 215, 107, 107) # TODO: fill in dimensions
|
||||||
# min_offset: minimum offset to other objects.
|
|
||||||
@pyqtSlot("quint64", int)
|
|
||||||
def multiplyObject(self, object_id, count, min_offset = 5):
|
|
||||||
node = self.getController().getScene().findObject(object_id)
|
|
||||||
|
|
||||||
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
|
|
||||||
node = Selection.getSelectedObject(0)
|
|
||||||
|
|
||||||
arranger = Arrange(215, 215, 107, 107)
|
|
||||||
arranger.centerFirst()
|
arranger.centerFirst()
|
||||||
|
|
||||||
# place all objects that are already there
|
if fixed_nodes is None:
|
||||||
root = self.getController().getScene().getRoot()
|
fixed_nodes = []
|
||||||
for node_ in DepthFirstIterator(root):
|
root = self.getController().getScene().getRoot()
|
||||||
# Only count sliceable objects
|
for node_ in DepthFirstIterator(root):
|
||||||
if node_.callDecoration("isSliceable"):
|
# Only count sliceable objects
|
||||||
Logger.log("d", " # Placing [%s]" % str(node_))
|
if node_.callDecoration("isSliceable"):
|
||||||
|
fixed_nodes.append(node_)
|
||||||
|
# place all objects fixed nodes
|
||||||
|
for fixed_node in fixed_nodes:
|
||||||
|
Logger.log("d", " # Placing [%s]" % str(fixed_node))
|
||||||
|
|
||||||
vertices = node_.callDecoration("getConvexHull")
|
vertices = fixed_node.callDecoration("getConvexHull")
|
||||||
points = copy.deepcopy(vertices._points)
|
points = copy.deepcopy(vertices._points)
|
||||||
shape_arr = ShapeArray.from_polygon(points)
|
shape_arr = ShapeArray.from_polygon(points)
|
||||||
arranger.place(0, 0, shape_arr)
|
arranger.place(0, 0, shape_arr)
|
||||||
Logger.log("d", "Current buildplate: \n%s" % str(arranger._occupied[::10, ::10]))
|
Logger.log("d", "Current buildplate: \n%s" % str(arranger._occupied[::10, ::10]))
|
||||||
Logger.log("d", "Current scrores: \n%s" % str(arranger._priority[::20, ::20]))
|
return arranger
|
||||||
|
|
||||||
nodes = []
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _nodeAsShapeArr(cls, node, min_offset):
|
||||||
# hacky way to undo transformation
|
# hacky way to undo transformation
|
||||||
transform = node._transformation
|
transform = node._transformation
|
||||||
transform_x = transform._data[0][3]
|
transform_x = transform._data[0][3]
|
||||||
|
@ -892,8 +888,13 @@ class CuraApplication(QtApplication):
|
||||||
hull_points[:, 1] = numpy.add(hull_points[:, 1], -transform_y)
|
hull_points[:, 1] = numpy.add(hull_points[:, 1], -transform_y)
|
||||||
hull_shape_arr = ShapeArray.from_polygon(hull_points) # x, y
|
hull_shape_arr = ShapeArray.from_polygon(hull_points) # x, y
|
||||||
|
|
||||||
start_prio = 0
|
return offset_shape_arr, hull_shape_arr
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _findNodePlacements(cls, arranger, node, offset_shape_arr, hull_shape_arr, count = 1):
|
||||||
|
# offset_shape_arr, hull_shape_arr, arranger -> nodes, arranger
|
||||||
|
nodes = []
|
||||||
|
start_prio = 0
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
new_node = copy.deepcopy(node)
|
new_node = copy.deepcopy(node)
|
||||||
|
|
||||||
|
@ -911,12 +912,27 @@ class CuraApplication(QtApplication):
|
||||||
Logger.log("d", "Could not find spot!")
|
Logger.log("d", "Could not find spot!")
|
||||||
transformation._data[0][3] = 200
|
transformation._data[0][3] = 200
|
||||||
transformation._data[2][3] = -100 + i * 20
|
transformation._data[2][3] = -100 + i * 20
|
||||||
# TODO: where to place it?
|
|
||||||
|
|
||||||
# new_node.setTransformation(transformation)
|
# new_node.setTransformation(transformation)
|
||||||
nodes.append(new_node)
|
nodes.append(new_node)
|
||||||
|
return nodes
|
||||||
|
|
||||||
if node:
|
## Create a number of copies of existing object.
|
||||||
|
# object_id
|
||||||
|
# count: number of copies
|
||||||
|
# min_offset: minimum offset to other objects.
|
||||||
|
@pyqtSlot("quint64", int)
|
||||||
|
def multiplyObject(self, object_id, count, min_offset = 5):
|
||||||
|
node = self.getController().getScene().findObject(object_id)
|
||||||
|
|
||||||
|
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
|
||||||
|
node = Selection.getSelectedObject(0)
|
||||||
|
|
||||||
|
arranger = self._prepareArranger()
|
||||||
|
offset_shape_arr, hull_shape_arr = self._nodeAsShapeArr(node, min_offset = min_offset)
|
||||||
|
nodes = self._findNodePlacements(arranger, node, offset_shape_arr, hull_shape_arr, count = count)
|
||||||
|
|
||||||
|
if nodes:
|
||||||
current_node = node
|
current_node = node
|
||||||
# Find the topmost group
|
# Find the topmost group
|
||||||
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
|
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
|
||||||
|
@ -927,6 +943,7 @@ class CuraApplication(QtApplication):
|
||||||
op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
|
op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
|
||||||
op.push()
|
op.push()
|
||||||
|
|
||||||
|
|
||||||
## Center object on platform.
|
## Center object on platform.
|
||||||
@pyqtSlot("quint64")
|
@pyqtSlot("quint64")
|
||||||
def centerObject(self, object_id):
|
def centerObject(self, object_id):
|
||||||
|
@ -1043,6 +1060,60 @@ class CuraApplication(QtApplication):
|
||||||
op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1)))
|
op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1)))
|
||||||
op.push()
|
op.push()
|
||||||
|
|
||||||
|
## Testing: arrange selected objects or all objects
|
||||||
|
@pyqtSlot()
|
||||||
|
def arrange(self):
|
||||||
|
min_offset = 5
|
||||||
|
|
||||||
|
if Selection.getAllSelectedObjects():
|
||||||
|
nodes = Selection.getAllSelectedObjects()
|
||||||
|
|
||||||
|
# What nodes are on the build plate and are not being moved
|
||||||
|
fixed_nodes = []
|
||||||
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
|
if type(node) is not SceneNode:
|
||||||
|
continue
|
||||||
|
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
||||||
|
continue # Node that doesnt have a mesh and is not a group.
|
||||||
|
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||||
|
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||||
|
if not node.isSelectable():
|
||||||
|
continue # i.e. node with layer data
|
||||||
|
fixed_nodes.append(node)
|
||||||
|
else:
|
||||||
|
nodes = []
|
||||||
|
fixed_nodes = []
|
||||||
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
|
if type(node) is not SceneNode:
|
||||||
|
continue
|
||||||
|
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
||||||
|
continue # Node that doesnt have a mesh and is not a group.
|
||||||
|
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||||
|
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||||
|
if not node.isSelectable():
|
||||||
|
continue # i.e. node with layer data
|
||||||
|
nodes.append(node)
|
||||||
|
|
||||||
|
arranger = self._prepareArranger(fixed_nodes = fixed_nodes)
|
||||||
|
for node in nodes:
|
||||||
|
offset_shape_arr, hull_shape_arr = self._nodeAsShapeArr(node, min_offset = min_offset)
|
||||||
|
|
||||||
|
x, y, penalty_points, start_prio = arranger.bestSpot(
|
||||||
|
offset_shape_arr)
|
||||||
|
if x is not None: # We could find a place
|
||||||
|
Logger.log("d", "Best place is: %s %s (points = %s)" % (x, y, penalty_points))
|
||||||
|
arranger.place(x, y, hull_shape_arr) # take place before the next one
|
||||||
|
|
||||||
|
node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
|
||||||
|
if node.getBoundingBox():
|
||||||
|
center_y = node.getWorldPosition().y - node.getBoundingBox().bottom
|
||||||
|
else:
|
||||||
|
center_y = 0
|
||||||
|
|
||||||
|
op = GroupedOperation()
|
||||||
|
op.addOperation(SetTransformOperation(node, Vector(x, center_y, y)))
|
||||||
|
op.push()
|
||||||
|
|
||||||
## Reload all mesh data on the screen from file.
|
## Reload all mesh data on the screen from file.
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def reloadAll(self):
|
def reloadAll(self):
|
||||||
|
@ -1302,6 +1373,11 @@ class CuraApplication(QtApplication):
|
||||||
filename = job.getFileName()
|
filename = job.getFileName()
|
||||||
self._currently_loading_files.remove(filename)
|
self._currently_loading_files.remove(filename)
|
||||||
|
|
||||||
|
arranger = self._prepareArranger()
|
||||||
|
min_offset = 5
|
||||||
|
total_time = 0
|
||||||
|
import time
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
node.setSelectable(True)
|
node.setSelectable(True)
|
||||||
node.setName(os.path.basename(filename))
|
node.setName(os.path.basename(filename))
|
||||||
|
@ -1322,11 +1398,27 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
scene = self.getController().getScene()
|
scene = self.getController().getScene()
|
||||||
|
|
||||||
op = AddSceneNodeOperation(node, scene.getRoot())
|
# If there is no convex hull for the node, start calculating it and continue.
|
||||||
op.push()
|
if not node.getDecorator(ConvexHullDecorator):
|
||||||
|
node.addDecorator(ConvexHullDecorator())
|
||||||
|
|
||||||
|
# find node location
|
||||||
|
if 1:
|
||||||
|
start_time = time.time()
|
||||||
|
offset_shape_arr, hull_shape_arr = self._nodeAsShapeArr(node, min_offset = min_offset)
|
||||||
|
nodes = self._findNodePlacements(arranger, node, offset_shape_arr, hull_shape_arr, count = 1)
|
||||||
|
total_time += (time.time() - start_time)
|
||||||
|
else:
|
||||||
|
nodes = [node]
|
||||||
|
|
||||||
|
for new_node in nodes:
|
||||||
|
op = AddSceneNodeOperation(new_node, scene.getRoot())
|
||||||
|
op.push()
|
||||||
|
|
||||||
scene.sceneChanged.emit(node)
|
scene.sceneChanged.emit(node)
|
||||||
|
|
||||||
|
Logger.log("d", "Placement of %s objects took %.1f seconds" % (len(nodes), total_time))
|
||||||
|
|
||||||
def addNonSliceableExtension(self, extension):
|
def addNonSliceableExtension(self, extension):
|
||||||
self._non_sliceable_extensions.append(extension)
|
self._non_sliceable_extensions.append(extension)
|
||||||
|
|
||||||
|
|
8
resources/qml/Actions.qml
Normal file → Executable file
8
resources/qml/Actions.qml
Normal file → Executable file
|
@ -31,6 +31,7 @@ Item
|
||||||
property alias selectAll: selectAllAction;
|
property alias selectAll: selectAllAction;
|
||||||
property alias deleteAll: deleteAllAction;
|
property alias deleteAll: deleteAllAction;
|
||||||
property alias reloadAll: reloadAllAction;
|
property alias reloadAll: reloadAllAction;
|
||||||
|
property alias arrange: arrangeAction;
|
||||||
property alias resetAllTranslation: resetAllTranslationAction;
|
property alias resetAllTranslation: resetAllTranslationAction;
|
||||||
property alias resetAll: resetAllAction;
|
property alias resetAll: resetAllAction;
|
||||||
|
|
||||||
|
@ -266,6 +267,13 @@ Item
|
||||||
onTriggered: Printer.reloadAll();
|
onTriggered: Printer.reloadAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Action
|
||||||
|
{
|
||||||
|
id: arrangeAction;
|
||||||
|
text: catalog.i18nc("@action:inmenu menubar:edit","Arrange");
|
||||||
|
onTriggered: Printer.arrange();
|
||||||
|
}
|
||||||
|
|
||||||
Action
|
Action
|
||||||
{
|
{
|
||||||
id: resetAllTranslationAction;
|
id: resetAllTranslationAction;
|
||||||
|
|
|
@ -133,6 +133,7 @@ UM.MainWindow
|
||||||
MenuItem { action: Cura.Actions.selectAll; }
|
MenuItem { action: Cura.Actions.selectAll; }
|
||||||
MenuItem { action: Cura.Actions.deleteSelection; }
|
MenuItem { action: Cura.Actions.deleteSelection; }
|
||||||
MenuItem { action: Cura.Actions.deleteAll; }
|
MenuItem { action: Cura.Actions.deleteAll; }
|
||||||
|
MenuItem { action: Cura.Actions.arrange; }
|
||||||
MenuItem { action: Cura.Actions.resetAllTranslation; }
|
MenuItem { action: Cura.Actions.resetAllTranslation; }
|
||||||
MenuItem { action: Cura.Actions.resetAll; }
|
MenuItem { action: Cura.Actions.resetAll; }
|
||||||
MenuSeparator { }
|
MenuSeparator { }
|
||||||
|
@ -638,6 +639,7 @@ UM.MainWindow
|
||||||
MenuItem { action: Cura.Actions.selectAll; }
|
MenuItem { action: Cura.Actions.selectAll; }
|
||||||
MenuItem { action: Cura.Actions.deleteAll; }
|
MenuItem { action: Cura.Actions.deleteAll; }
|
||||||
MenuItem { action: Cura.Actions.reloadAll; }
|
MenuItem { action: Cura.Actions.reloadAll; }
|
||||||
|
MenuItem { action: Cura.Actions.arrange; }
|
||||||
MenuItem { action: Cura.Actions.resetAllTranslation; }
|
MenuItem { action: Cura.Actions.resetAllTranslation; }
|
||||||
MenuItem { action: Cura.Actions.resetAll; }
|
MenuItem { action: Cura.Actions.resetAll; }
|
||||||
MenuSeparator { }
|
MenuSeparator { }
|
||||||
|
@ -698,6 +700,7 @@ UM.MainWindow
|
||||||
MenuItem { action: Cura.Actions.selectAll; }
|
MenuItem { action: Cura.Actions.selectAll; }
|
||||||
MenuItem { action: Cura.Actions.deleteAll; }
|
MenuItem { action: Cura.Actions.deleteAll; }
|
||||||
MenuItem { action: Cura.Actions.reloadAll; }
|
MenuItem { action: Cura.Actions.reloadAll; }
|
||||||
|
MenuItem { action: Cura.Actions.arrange; }
|
||||||
MenuItem { action: Cura.Actions.resetAllTranslation; }
|
MenuItem { action: Cura.Actions.resetAllTranslation; }
|
||||||
MenuItem { action: Cura.Actions.resetAll; }
|
MenuItem { action: Cura.Actions.resetAll; }
|
||||||
MenuSeparator { }
|
MenuSeparator { }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue