Arranger: moved functions, split Arrange into Arrange All and Arrange Selection. CURA-3239

This commit is contained in:
Jack Ha 2017-04-03 10:40:04 +02:00
parent 9db816b0fc
commit abb5d1e76e
4 changed files with 181 additions and 159 deletions

View file

@ -1,6 +1,9 @@
import numpy as np import numpy
from UM.Math.Polygon import Polygon
## Some polygon converted to an array
## Polygon representation as an array
#
class ShapeArray: class ShapeArray:
def __init__(self, arr, offset_x, offset_y, scale = 1): def __init__(self, arr, offset_x, offset_y, scale = 1):
self.arr = arr self.arr = arr
@ -9,39 +12,59 @@ class ShapeArray:
self.scale = scale self.scale = scale
@classmethod @classmethod
def from_polygon(cls, vertices, scale = 1): def fromPolygon(cls, vertices, scale = 1):
# scale # scale
vertices = vertices * scale vertices = vertices * scale
# flip y, x -> x, y # flip y, x -> x, y
flip_vertices = np.zeros((vertices.shape)) flip_vertices = numpy.zeros((vertices.shape))
flip_vertices[:, 0] = vertices[:, 1] flip_vertices[:, 0] = vertices[:, 1]
flip_vertices[:, 1] = vertices[:, 0] flip_vertices[:, 1] = vertices[:, 0]
flip_vertices = flip_vertices[::-1] flip_vertices = flip_vertices[::-1]
# offset, we want that all coordinates have positive values # offset, we want that all coordinates have positive values
offset_y = int(np.amin(flip_vertices[:, 0])) offset_y = int(numpy.amin(flip_vertices[:, 0]))
offset_x = int(np.amin(flip_vertices[:, 1])) offset_x = int(numpy.amin(flip_vertices[:, 1]))
flip_vertices[:, 0] = np.add(flip_vertices[:, 0], -offset_y) flip_vertices[:, 0] = numpy.add(flip_vertices[:, 0], -offset_y)
flip_vertices[:, 1] = np.add(flip_vertices[:, 1], -offset_x) flip_vertices[:, 1] = numpy.add(flip_vertices[:, 1], -offset_x)
shape = [int(np.amax(flip_vertices[:, 0])), int(np.amax(flip_vertices[:, 1]))] shape = [int(numpy.amax(flip_vertices[:, 0])), int(numpy.amax(flip_vertices[:, 1]))]
arr = cls.array_from_polygon(shape, flip_vertices) arr = cls.arrayFromPolygon(shape, flip_vertices)
return cls(arr, offset_x, offset_y) return cls(arr, offset_x, offset_y)
## Return an offset and hull ShapeArray from a scenenode.
@classmethod
def fromNode(cls, node, min_offset, scale = 0.5):
# hacky way to undo transformation
transform = node._transformation
transform_x = transform._data[0][3]
transform_y = transform._data[2][3]
hull_verts = node.callDecoration("getConvexHull")
offset_verts = hull_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
offset_points = copy.deepcopy(offset_verts._points) # x, y
offset_points[:, 0] = numpy.add(offset_points[:, 0], -transform_x)
offset_points[:, 1] = numpy.add(offset_points[:, 1], -transform_y)
offset_shape_arr = ShapeArray.fromPolygon(offset_points, scale = scale)
hull_points = copy.deepcopy(hull_verts._points)
hull_points[:, 0] = numpy.add(hull_points[:, 0], -transform_x)
hull_points[:, 1] = numpy.add(hull_points[:, 1], -transform_y)
hull_shape_arr = ShapeArray.fromPolygon(hull_points, scale = scale) # x, y
return offset_shape_arr, hull_shape_arr
## Create np.array with dimensions defined by shape
# Fills polygon defined by vertices with ones, all other values zero
# Only works correctly for convex hull vertices
# Originally from: http://stackoverflow.com/questions/37117878/generating-a-filled-polygon-inside-a-numpy-array # Originally from: http://stackoverflow.com/questions/37117878/generating-a-filled-polygon-inside-a-numpy-array
@classmethod @classmethod
def array_from_polygon(cls, shape, vertices): def arrayFromPolygon(cls, shape, vertices):
""" base_array = numpy.zeros(shape, dtype=float) # Initialize your array of zeros
Creates np.array with dimensions defined by shape
Fills polygon defined by vertices with ones, all other values zero
Only works correctly for convex hull vertices fill = numpy.ones(base_array.shape) * True # Initialize boolean array defining shape fill
"""
base_array = np.zeros(shape, dtype=float) # Initialize your array of zeros
fill = np.ones(base_array.shape) * True # Initialize boolean array defining shape fill
# Create check array for each edge segment, combine into fill array # Create check array for each edge segment, combine into fill array
for k in range(vertices.shape[0]): for k in range(vertices.shape[0]):
fill = np.all([fill, cls._check(vertices[k - 1], vertices[k], base_array)], axis=0) fill = numpy.all([fill, cls._check(vertices[k - 1], vertices[k], base_array)], axis=0)
# Set all values inside polygon to one # Set all values inside polygon to one
base_array[fill] = 1 base_array[fill] = 1
@ -51,43 +74,102 @@ class ShapeArray:
## Return indices that mark one side of the line, used by array_from_polygon ## Return indices that mark one side of the line, used by array_from_polygon
# Uses the line defined by p1 and p2 to check array of # Uses the line defined by p1 and p2 to check array of
# input indices against interpolated value # input indices against interpolated value
# Returns boolean array, with True inside and False outside of shape # Returns boolean array, with True inside and False outside of shape
# Originally from: http://stackoverflow.com/questions/37117878/generating-a-filled-polygon-inside-a-numpy-array # Originally from: http://stackoverflow.com/questions/37117878/generating-a-filled-polygon-inside-a-numpy-array
@classmethod @classmethod
def _check(cls, p1, p2, base_array): def _check(cls, p1, p2, base_array):
if p1[0] == p2[0] and p1[1] == p2[1]: if p1[0] == p2[0] and p1[1] == p2[1]:
return return
idxs = np.indices(base_array.shape) # Create 3D array of indices idxs = numpy.indices(base_array.shape) # Create 3D array of indices
p1 = p1.astype(float) p1 = p1.astype(float)
p2 = p2.astype(float) p2 = p2.astype(float)
if p2[0] == p1[0]: if p2[0] == p1[0]:
sign = np.sign(p2[1] - p1[1]) sign = numpy.sign(p2[1] - p1[1])
return idxs[1] * sign return idxs[1] * sign
if p2[1] == p1[1]: if p2[1] == p1[1]:
sign = np.sign(p2[0] - p1[0]) sign = numpy.sign(p2[0] - p1[0])
return idxs[1] * sign return idxs[1] * sign
# Calculate max column idx for each row idx based on interpolated line between two points # Calculate max column idx for each row idx based on interpolated line between two points
max_col_idx = (idxs[0] - p1[0]) / (p2[0] - p1[0]) * (p2[1] - p1[1]) + p1[1] max_col_idx = (idxs[0] - p1[0]) / (p2[0] - p1[0]) * (p2[1] - p1[1]) + p1[1]
sign = np.sign(p2[0] - p1[0]) sign = numpy.sign(p2[0] - p1[0])
return idxs[1] * sign <= max_col_idx * sign return idxs[1] * sign <= max_col_idx * sign
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Logger import Logger
import copy
class Arrange: class Arrange:
def __init__(self, x, y, offset_x, offset_y, scale=1): def __init__(self, x, y, offset_x, offset_y, scale=1):
self.shape = (y, x) self.shape = (y, x)
self._priority = np.zeros((x, y), dtype=np.int32) self._priority = numpy.zeros((x, y), dtype=numpy.int32)
self._priority_unique_values = [] self._priority_unique_values = []
self._occupied = np.zeros((x, y), dtype=np.int32) self._occupied = numpy.zeros((x, y), dtype=numpy.int32)
self._scale = scale # convert input coordinates to arrange coordinates self._scale = scale # convert input coordinates to arrange coordinates
self._offset_x = offset_x self._offset_x = offset_x
self._offset_y = offset_y self._offset_y = offset_y
## Helper to create an Arranger instance
#
# Either fill in scene_root and create will find all sliceable nodes by itself,
# or use fixed_nodes to provide the nodes yourself.
# \param scene_root
# \param fixed_nodes
@classmethod
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5):
arranger = Arrange(220, 220, 110, 110, scale = scale)
arranger.centerFirst()
if fixed_nodes is None:
fixed_nodes = []
for node_ in DepthFirstIterator(scene_root):
# Only count sliceable objects
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 = fixed_node.callDecoration("getConvexHull")
points = copy.deepcopy(vertices._points)
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
arranger.place(0, 0, shape_arr)
Logger.log("d", "Current buildplate: \n%s" % str(arranger._occupied[::10, ::10]))
return arranger
## Find placement for a node and place it
#
def findNodePlacements(self, node, offset_shape_arr, hull_shape_arr, count = 1, step = 1):
# offset_shape_arr, hull_shape_arr, arranger -> nodes, arranger
nodes = []
start_prio = 0
for i in range(count):
new_node = copy.deepcopy(node)
Logger.log("d", " # Finding spot for %s" % new_node)
x, y, penalty_points, start_prio = self.bestSpot(
offset_shape_arr, start_prio = start_prio, step = step)
transformation = new_node._transformation
if x is not None: # We could find a place
transformation._data[0][3] = x
transformation._data[2][3] = y
Logger.log("d", "Best place is: %s %s (points = %s)" % (x, y, penalty_points))
self.place(x, y, hull_shape_arr) # take place before the next one
Logger.log("d", "New buildplate: \n%s" % str(self._occupied[::10, ::10]))
else:
Logger.log("d", "Could not find spot!")
transformation._data[0][3] = 200
transformation._data[2][3] = -100 + i * 20
nodes.append(new_node)
return nodes
## Fill priority, take offset as center. lower is better ## Fill priority, take offset as center. lower is better
def centerFirst(self): def centerFirst(self):
# Distance x + distance y # Distance x + distance y
@ -96,16 +178,16 @@ class Arrange:
# Square distance # Square distance
# self._priority = np.fromfunction( # self._priority = np.fromfunction(
# lambda i, j: abs(self._offset_x-i)**2+abs(self._offset_y-j)**2, self.shape, dtype=np.int32) # lambda i, j: abs(self._offset_x-i)**2+abs(self._offset_y-j)**2, self.shape, dtype=np.int32)
self._priority = np.fromfunction( self._priority = numpy.fromfunction(
lambda i, j: abs(self._offset_x-i)**3+abs(self._offset_y-j)**3, self.shape, dtype=np.int32) lambda i, j: abs(self._offset_x-i)**3+abs(self._offset_y-j)**3, self.shape, dtype=numpy.int32)
# self._priority = np.fromfunction( # self._priority = np.fromfunction(
# lambda i, j: max(abs(self._offset_x-i), abs(self._offset_y-j)), self.shape, dtype=np.int32) # lambda i, j: max(abs(self._offset_x-i), abs(self._offset_y-j)), self.shape, dtype=np.int32)
self._priority_unique_values = np.unique(self._priority) self._priority_unique_values = numpy.unique(self._priority)
self._priority_unique_values.sort() self._priority_unique_values.sort()
## Return the amount of "penalty points" for polygon, which is the sum of priority ## Return the amount of "penalty points" for polygon, which is the sum of priority
# 999999 if occupied # 999999 if occupied
def check_shape(self, x, y, shape_arr): def checkShape(self, x, y, shape_arr):
x = int(self._scale * x) x = int(self._scale * x)
y = int(self._scale * y) y = int(self._scale * y)
offset_x = x + self._offset_x + shape_arr.offset_x offset_x = x + self._offset_x + shape_arr.offset_x
@ -114,24 +196,24 @@ class Arrange:
offset_y:offset_y + shape_arr.arr.shape[0], offset_y:offset_y + shape_arr.arr.shape[0],
offset_x:offset_x + shape_arr.arr.shape[1]] offset_x:offset_x + shape_arr.arr.shape[1]]
try: try:
if np.any(occupied_slice[np.where(shape_arr.arr == 1)]): if numpy.any(occupied_slice[numpy.where(shape_arr.arr == 1)]):
return 999999 return 999999
except IndexError: # out of bounds if you try to place an object outside except IndexError: # out of bounds if you try to place an object outside
return 999999 return 999999
prio_slice = self._priority[ prio_slice = self._priority[
offset_y:offset_y + shape_arr.arr.shape[0], offset_y:offset_y + shape_arr.arr.shape[0],
offset_x:offset_x + shape_arr.arr.shape[1]] offset_x:offset_x + shape_arr.arr.shape[1]]
return np.sum(prio_slice[np.where(shape_arr.arr == 1)]) return numpy.sum(prio_slice[numpy.where(shape_arr.arr == 1)])
## Find "best" spot ## Find "best" spot for ShapeArray
def bestSpot(self, shape_arr, start_prio = 0, step = 1): def bestSpot(self, shape_arr, start_prio = 0, step = 1):
start_idx_list = np.where(self._priority_unique_values == start_prio) start_idx_list = numpy.where(self._priority_unique_values == start_prio)
if start_idx_list: if start_idx_list:
start_idx = start_idx_list[0] start_idx = start_idx_list[0]
else: else:
start_idx = 0 start_idx = 0
for prio in self._priority_unique_values[start_idx::step]: for prio in self._priority_unique_values[start_idx::step]:
tryout_idx = np.where(self._priority == prio) tryout_idx = numpy.where(self._priority == prio)
for idx in range(len(tryout_idx[0])): for idx in range(len(tryout_idx[0])):
x = tryout_idx[0][idx] x = tryout_idx[0][idx]
y = tryout_idx[1][idx] y = tryout_idx[1][idx]
@ -139,7 +221,7 @@ class Arrange:
projected_y = y - self._offset_y projected_y = y - self._offset_y
# array to "world" coordinates # array to "world" coordinates
penalty_points = self.check_shape(projected_x, projected_y, shape_arr) penalty_points = self.checkShape(projected_x, projected_y, shape_arr)
if penalty_points != 999999: if penalty_points != 999999:
return projected_x, projected_y, penalty_points, prio return projected_x, projected_y, penalty_points, prio
return None, None, None, prio # No suitable location found :-( return None, None, None, prio # No suitable location found :-(
@ -158,10 +240,10 @@ class Arrange:
max_y = min(max(offset_y + shape_arr.arr.shape[0], 0), shape_y - 1) max_y = min(max(offset_y + shape_arr.arr.shape[0], 0), shape_y - 1)
occupied_slice = self._occupied[min_y:max_y, min_x:max_x] occupied_slice = self._occupied[min_y:max_y, min_x:max_x]
# we use a slice of shape because it can be out of bounds # we use a slice of shape because it can be out of bounds
occupied_slice[np.where(shape_arr.arr[ occupied_slice[numpy.where(shape_arr.arr[
min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 1 min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 1
# Set priority to low (= high number), so it won't get picked at trying out. # Set priority to low (= high number), so it won't get picked at trying out.
prio_slice = self._priority[min_y:max_y, min_x:max_x] prio_slice = self._priority[min_y:max_y, min_x:max_x]
prio_slice[np.where(shape_arr.arr[ prio_slice[numpy.where(shape_arr.arr[
min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 999 min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 999

View file

@ -14,7 +14,6 @@ from UM.Math.Matrix import Matrix
from UM.Resources import Resources from UM.Resources import Resources
from UM.Scene.ToolHandle import ToolHandle from UM.Scene.ToolHandle import ToolHandle
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Math.Polygon import Polygon
from UM.Mesh.ReadMeshJob import ReadMeshJob from UM.Mesh.ReadMeshJob import ReadMeshJob
from UM.Logger import Logger from UM.Logger import Logger
from UM.Preferences import Preferences from UM.Preferences import Preferences
@ -846,79 +845,6 @@ class CuraApplication(QtApplication):
op.push() op.push()
## Testing, prepare arranger for use
def _prepareArranger(self, fixed_nodes = None):
#arranger = Arrange(215, 215, 107, 107) # TODO: fill in dimensions
scale = 0.5
arranger = Arrange(220, 220, 110, 110, scale = scale) # TODO: fill in dimensions
arranger.centerFirst()
if fixed_nodes is None:
fixed_nodes = []
root = self.getController().getScene().getRoot()
for node_ in DepthFirstIterator(root):
# Only count sliceable objects
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 = fixed_node.callDecoration("getConvexHull")
points = copy.deepcopy(vertices._points)
shape_arr = ShapeArray.from_polygon(points, scale=scale)
arranger.place(0, 0, shape_arr)
Logger.log("d", "Current buildplate: \n%s" % str(arranger._occupied[::10, ::10]))
return arranger
@classmethod
def _nodeAsShapeArr(cls, node, min_offset):
scale = 0.5
# hacky way to undo transformation
transform = node._transformation
transform_x = transform._data[0][3]
transform_y = transform._data[2][3]
hull_verts = node.callDecoration("getConvexHull")
offset_verts = hull_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
offset_points = copy.deepcopy(offset_verts._points) # x, y
offset_points[:, 0] = numpy.add(offset_points[:, 0], -transform_x)
offset_points[:, 1] = numpy.add(offset_points[:, 1], -transform_y)
offset_shape_arr = ShapeArray.from_polygon(offset_points, scale = scale)
hull_points = copy.deepcopy(hull_verts._points)
hull_points[:, 0] = numpy.add(hull_points[:, 0], -transform_x)
hull_points[:, 1] = numpy.add(hull_points[:, 1], -transform_y)
hull_shape_arr = ShapeArray.from_polygon(hull_points, scale = scale) # x, y
return offset_shape_arr, hull_shape_arr
@classmethod
def _findNodePlacements(cls, arranger, node, offset_shape_arr, hull_shape_arr, count = 1, step = 1):
# offset_shape_arr, hull_shape_arr, arranger -> nodes, arranger
nodes = []
start_prio = 0
for i in range(count):
new_node = copy.deepcopy(node)
Logger.log("d", " # Finding spot for %s" % new_node)
x, y, penalty_points, start_prio = arranger.bestSpot(
offset_shape_arr, start_prio = start_prio, step = step)
transformation = new_node._transformation
if x is not None: # We could find a place
transformation._data[0][3] = x
transformation._data[2][3] = y
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
Logger.log("d", "New buildplate: \n%s" % str(arranger._occupied[::10, ::10]))
else:
Logger.log("d", "Could not find spot!")
transformation._data[0][3] = 200
transformation._data[2][3] = -100 + i * 20
nodes.append(new_node)
return nodes
## Create a number of copies of existing object. ## Create a number of copies of existing object.
# object_id # object_id
# count: number of copies # count: number of copies
@ -930,9 +856,10 @@ class CuraApplication(QtApplication):
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
node = Selection.getSelectedObject(0) node = Selection.getSelectedObject(0)
arranger = self._prepareArranger() root = self.getController().getScene().getRoot()
offset_shape_arr, hull_shape_arr = self._nodeAsShapeArr(node, min_offset = min_offset) arranger = Arrange.create(scene_root = root)
nodes = self._findNodePlacements(arranger, node, offset_shape_arr, hull_shape_arr, count = count) offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
nodes = arranger.findNodePlacements(node, offset_shape_arr, hull_shape_arr, count = count)
if nodes: if nodes:
current_node = node current_node = node
@ -945,7 +872,6 @@ 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):
@ -1064,10 +990,7 @@ class CuraApplication(QtApplication):
## Testing: arrange selected objects or all objects ## Testing: arrange selected objects or all objects
@pyqtSlot() @pyqtSlot()
def arrange(self): def arrangeSelection(self):
min_offset = 8
if Selection.getAllSelectedObjects():
nodes = Selection.getAllSelectedObjects() nodes = Selection.getAllSelectedObjects()
# What nodes are on the build plate and are not being moved # What nodes are on the build plate and are not being moved
@ -1082,7 +1005,10 @@ class CuraApplication(QtApplication):
if not node.isSelectable(): if not node.isSelectable():
continue # i.e. node with layer data continue # i.e. node with layer data
fixed_nodes.append(node) fixed_nodes.append(node)
else: self.arrange(nodes, fixed_nodes)
@pyqtSlot()
def arrangeAll(self):
nodes = [] nodes = []
fixed_nodes = [] fixed_nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
@ -1095,11 +1021,16 @@ class CuraApplication(QtApplication):
if not node.isSelectable(): if not node.isSelectable():
continue # i.e. node with layer data continue # i.e. node with layer data
nodes.append(node) nodes.append(node)
self.arrange(nodes, fixed_nodes)
arranger = self._prepareArranger(fixed_nodes = fixed_nodes) ## Arrange the nodes, given fixed nodes
def arrange(self, nodes, fixed_nodes):
min_offset = 8
arranger = Arrange.create(fixed_nodes = fixed_nodes)
nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr) nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr)
for node in nodes: for node in nodes:
offset_shape_arr, hull_shape_arr = self._nodeAsShapeArr(node, min_offset = min_offset) offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
nodes_arr.append((offset_shape_arr.arr.shape[0] * offset_shape_arr.arr.shape[1], node, offset_shape_arr, hull_shape_arr)) nodes_arr.append((offset_shape_arr.arr.shape[0] * offset_shape_arr.arr.shape[1], node, offset_shape_arr, hull_shape_arr))
nodes_arr.sort(key = lambda item: item[0]) nodes_arr.sort(key = lambda item: item[0])
@ -1383,7 +1314,8 @@ class CuraApplication(QtApplication):
filename = job.getFileName() filename = job.getFileName()
self._currently_loading_files.remove(filename) self._currently_loading_files.remove(filename)
arranger = self._prepareArranger() root = self.getController().getScene().getRoot()
arranger = Arrange.create(scene_root = root)
min_offset = 8 min_offset = 8
for node in nodes: for node in nodes:
@ -1411,9 +1343,9 @@ class CuraApplication(QtApplication):
node.addDecorator(ConvexHullDecorator()) node.addDecorator(ConvexHullDecorator())
# find node location # find node location
offset_shape_arr, hull_shape_arr = self._nodeAsShapeArr(node, min_offset = min_offset) offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
# step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher # step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
nodes = self._findNodePlacements(arranger, node, offset_shape_arr, hull_shape_arr, count = 1, step = 10) nodes = arranger.findNodePlacements(node, offset_shape_arr, hull_shape_arr, count = 1, step = 10)
for new_node in nodes: for new_node in nodes:
op = AddSceneNodeOperation(new_node, scene.getRoot()) op = AddSceneNodeOperation(new_node, scene.getRoot())

View file

@ -31,7 +31,8 @@ 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 arrangeAll: arrangeAllAction;
property alias arrangeSelection: arrangeSelectionAction;
property alias resetAllTranslation: resetAllTranslationAction; property alias resetAllTranslation: resetAllTranslationAction;
property alias resetAll: resetAllAction; property alias resetAll: resetAllAction;
@ -269,9 +270,16 @@ Item
Action Action
{ {
id: arrangeAction; id: arrangeAllAction;
text: catalog.i18nc("@action:inmenu menubar:edit","Arrange"); text: catalog.i18nc("@action:inmenu menubar:edit","Arrange All");
onTriggered: Printer.arrange(); onTriggered: Printer.arrangeAll();
}
Action
{
id: arrangeSelectionAction;
text: catalog.i18nc("@action:inmenu menubar:edit","Arrange Selection");
onTriggered: Printer.arrangeSelection();
} }
Action Action

View file

@ -133,7 +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.arrangeAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; } MenuItem { action: Cura.Actions.resetAllTranslation; }
MenuItem { action: Cura.Actions.resetAll; } MenuItem { action: Cura.Actions.resetAll; }
MenuSeparator { } MenuSeparator { }
@ -639,7 +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.arrangeSelection; }
MenuItem { action: Cura.Actions.resetAllTranslation; } MenuItem { action: Cura.Actions.resetAllTranslation; }
MenuItem { action: Cura.Actions.resetAll; } MenuItem { action: Cura.Actions.resetAll; }
MenuSeparator { } MenuSeparator { }
@ -700,7 +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.arrangeAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; } MenuItem { action: Cura.Actions.resetAllTranslation; }
MenuItem { action: Cura.Actions.resetAll; } MenuItem { action: Cura.Actions.resetAll; }
MenuSeparator { } MenuSeparator { }