Split ShapeArray from Arranger. CURA-3239

This commit is contained in:
Jack Ha 2017-04-03 14:48:31 +02:00
parent d6cd37626b
commit a83b1dd638
5 changed files with 131 additions and 118 deletions

View file

@ -1,110 +1,15 @@
import numpy
from UM.Math.Polygon import Polygon
## Polygon representation as an array
#
class ShapeArray:
def __init__(self, arr, offset_x, offset_y, scale = 1):
self.arr = arr
self.offset_x = offset_x
self.offset_y = offset_y
self.scale = scale
@classmethod
def fromPolygon(cls, vertices, scale = 1):
# scale
vertices = vertices * scale
# flip y, x -> x, y
flip_vertices = numpy.zeros((vertices.shape))
flip_vertices[:, 0] = vertices[:, 1]
flip_vertices[:, 1] = vertices[:, 0]
flip_vertices = flip_vertices[::-1]
# offset, we want that all coordinates have positive values
offset_y = int(numpy.amin(flip_vertices[:, 0]))
offset_x = int(numpy.amin(flip_vertices[:, 1]))
flip_vertices[:, 0] = numpy.add(flip_vertices[:, 0], -offset_y)
flip_vertices[:, 1] = numpy.add(flip_vertices[:, 1], -offset_x)
shape = [int(numpy.amax(flip_vertices[:, 0])), int(numpy.amax(flip_vertices[:, 1]))]
arr = cls.arrayFromPolygon(shape, flip_vertices)
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
@classmethod
def arrayFromPolygon(cls, shape, vertices):
base_array = numpy.zeros(shape, dtype=float) # Initialize your array of zeros
fill = numpy.ones(base_array.shape) * True # Initialize boolean array defining shape fill
# Create check array for each edge segment, combine into fill array
for k in range(vertices.shape[0]):
fill = numpy.all([fill, cls._check(vertices[k - 1], vertices[k], base_array)], axis=0)
# Set all values inside polygon to one
base_array[fill] = 1
return base_array
## 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
# input indices against interpolated value
# 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
@classmethod
def _check(cls, p1, p2, base_array):
if p1[0] == p2[0] and p1[1] == p2[1]:
return
idxs = numpy.indices(base_array.shape) # Create 3D array of indices
p1 = p1.astype(float)
p2 = p2.astype(float)
if p2[0] == p1[0]:
sign = numpy.sign(p2[1] - p1[1])
return idxs[1] * sign
if p2[1] == p1[1]:
sign = numpy.sign(p2[0] - p1[0])
return idxs[1] * sign
# 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]
sign = numpy.sign(p2[0] - p1[0])
return idxs[1] * sign <= max_col_idx * sign
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Logger import Logger
from cura.ShapeArray import ShapeArray
import numpy
import copy
## The Arrange classed is used together with ShapeArray. The class tries to find
# good locations for objects that you try to put on a build place.
# Different priority schemes can be defined so it alters the behavior while using
# the same logic.
class Arrange:
def __init__(self, x, y, offset_x, offset_y, scale=1):
self.shape = (y, x)
@ -166,16 +71,18 @@ class Arrange:
## Fill priority, take offset as center. lower is better
def centerFirst(self):
# Distance x + distance y
#self._priority = np.fromfunction(
# lambda i, j: abs(self._offset_x-i)+abs(self._offset_y-j), self.shape, dtype=np.int32)
# Square distance
# self._priority = np.fromfunction(
# lambda i, j: abs(self._offset_x-i)**2+abs(self._offset_y-j)**2, self.shape, dtype=np.int32)
# Distance x + distance y: creates diamond shape
#self._priority = numpy.fromfunction(
# lambda i, j: abs(self._offset_x-i)+abs(self._offset_y-j), self.shape, dtype=numpy.int32)
# Square distance: creates a more round shape
self._priority = numpy.fromfunction(
lambda i, j: abs(self._offset_x-i)**3+abs(self._offset_y-j)**3, self.shape, dtype=numpy.int32)
# 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: (self._offset_x - i) ** 2 + (self._offset_y - j) ** 2, self.shape, dtype=numpy.int32)
self._priority_unique_values = numpy.unique(self._priority)
self._priority_unique_values.sort()
def backFirst(self):
self._priority = numpy.fromfunction(
lambda i, j: 10 * j + abs(self._offset_x - i), self.shape, dtype=numpy.int32)
self._priority_unique_values = numpy.unique(self._priority)
self._priority_unique_values.sort()

View file

@ -32,7 +32,8 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.SetTransformOperation import SetTransformOperation
from cura.Arrange import Arrange, ShapeArray
from cura.Arrange import Arrange
from cura.ShapeArray import ShapeArray
from cura.ConvexHullDecorator import ConvexHullDecorator
from cura.SetParentOperation import SetParentOperation
from cura.SliceableObjectDecorator import SliceableObjectDecorator
@ -992,7 +993,6 @@ class CuraApplication(QtApplication):
@pyqtSlot()
def arrangeAll(self):
nodes = []
fixed_nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode:
continue
@ -1003,7 +1003,7 @@ class CuraApplication(QtApplication):
if not node.isSelectable():
continue # i.e. node with layer data
nodes.append(node)
self.arrange(nodes, fixed_nodes)
self.arrange(nodes, fixed_nodes = [])
## Arrange Selection
@pyqtSlot()
@ -1021,6 +1021,8 @@ class CuraApplication(QtApplication):
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
if node in nodes: # exclude selected node from fixed_nodes
continue
fixed_nodes.append(node)
self.arrange(nodes, fixed_nodes)

103
cura/ShapeArray.py Executable file
View file

@ -0,0 +1,103 @@
import numpy
import copy
from UM.Math.Polygon import Polygon
## Polygon representation as an array
#
class ShapeArray:
def __init__(self, arr, offset_x, offset_y, scale = 1):
self.arr = arr
self.offset_x = offset_x
self.offset_y = offset_y
self.scale = scale
@classmethod
def fromPolygon(cls, vertices, scale = 1):
# scale
vertices = vertices * scale
# flip y, x -> x, y
flip_vertices = numpy.zeros((vertices.shape))
flip_vertices[:, 0] = vertices[:, 1]
flip_vertices[:, 1] = vertices[:, 0]
flip_vertices = flip_vertices[::-1]
# offset, we want that all coordinates have positive values
offset_y = int(numpy.amin(flip_vertices[:, 0]))
offset_x = int(numpy.amin(flip_vertices[:, 1]))
flip_vertices[:, 0] = numpy.add(flip_vertices[:, 0], -offset_y)
flip_vertices[:, 1] = numpy.add(flip_vertices[:, 1], -offset_x)
shape = [int(numpy.amax(flip_vertices[:, 0])), int(numpy.amax(flip_vertices[:, 1]))]
arr = cls.arrayFromPolygon(shape, flip_vertices)
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
@classmethod
def arrayFromPolygon(cls, shape, vertices):
base_array = numpy.zeros(shape, dtype=float) # Initialize your array of zeros
fill = numpy.ones(base_array.shape) * True # Initialize boolean array defining shape fill
# Create check array for each edge segment, combine into fill array
for k in range(vertices.shape[0]):
fill = numpy.all([fill, cls._check(vertices[k - 1], vertices[k], base_array)], axis=0)
# Set all values inside polygon to one
base_array[fill] = 1
return base_array
## 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
# input indices against interpolated value
# 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
@classmethod
def _check(cls, p1, p2, base_array):
if p1[0] == p2[0] and p1[1] == p2[1]:
return
idxs = numpy.indices(base_array.shape) # Create 3D array of indices
p1 = p1.astype(float)
p2 = p2.astype(float)
if p2[0] == p1[0]:
sign = numpy.sign(p2[1] - p1[1])
return idxs[1] * sign
if p2[1] == p1[1]:
sign = numpy.sign(p2[0] - p1[0])
return idxs[1] * sign
# 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]
sign = numpy.sign(p2[0] - p1[0])
return idxs[1] * sign <= max_col_idx * sign

View file

@ -271,8 +271,9 @@ Item
Action
{
id: arrangeAllAction;
text: catalog.i18nc("@action:inmenu menubar:edit","Arrange All");
text: catalog.i18nc("@action:inmenu menubar:edit","Arrange All Models");
onTriggered: Printer.arrangeAll();
shortcut: "Ctrl+R";
}
Action

View file

@ -131,9 +131,9 @@ UM.MainWindow
MenuItem { action: Cura.Actions.redo; }
MenuSeparator { }
MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.arrangeAll; }
MenuItem { action: Cura.Actions.deleteSelection; }
MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.arrangeAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; }
MenuItem { action: Cura.Actions.resetAll; }
MenuSeparator { }
@ -637,9 +637,9 @@ UM.MainWindow
MenuItem { action: Cura.Actions.multiplyObject; }
MenuSeparator { }
MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.arrangeAll; }
MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.reloadAll; }
MenuItem { action: Cura.Actions.arrangeSelection; }
MenuItem { action: Cura.Actions.resetAllTranslation; }
MenuItem { action: Cura.Actions.resetAll; }
MenuSeparator { }
@ -698,9 +698,9 @@ UM.MainWindow
{
id: contextMenu;
MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.arrangeAll; }
MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.reloadAll; }
MenuItem { action: Cura.Actions.arrangeAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; }
MenuItem { action: Cura.Actions.resetAll; }
MenuSeparator { }