Browse Source

Split ShapeArray from Arranger. CURA-3239

Jack Ha 8 years ago
parent
commit
a83b1dd638
5 changed files with 131 additions and 118 deletions
  1. 18 111
      cura/Arrange.py
  2. 5 3
      cura/CuraApplication.py
  3. 103 0
      cura/ShapeArray.py
  4. 2 1
      resources/qml/Actions.qml
  5. 3 3
      resources/qml/Cura.qml

+ 18 - 111
cura/Arrange.py

@@ -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.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
 from UM.Logger import Logger
 from UM.Logger import Logger
+from cura.ShapeArray import ShapeArray
+
+import numpy
 import copy
 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:
 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)
@@ -166,16 +71,18 @@ class Arrange:
 
 
     ##  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
-        #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: (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(
         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: 10 * j + abs(self._offset_x - i), self.shape, dtype=numpy.int32)
         self._priority_unique_values = numpy.unique(self._priority)
         self._priority_unique_values = numpy.unique(self._priority)
         self._priority_unique_values.sort()
         self._priority_unique_values.sort()
 
 

+ 5 - 3
cura/CuraApplication.py

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

+ 103 - 0
cura/ShapeArray.py

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

+ 2 - 1
resources/qml/Actions.qml

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

+ 3 - 3
resources/qml/Cura.qml

@@ -131,9 +131,9 @@ UM.MainWindow
                 MenuItem { action: Cura.Actions.redo; }
                 MenuItem { action: Cura.Actions.redo; }
                 MenuSeparator { }
                 MenuSeparator { }
                 MenuItem { action: Cura.Actions.selectAll; }
                 MenuItem { action: Cura.Actions.selectAll; }
+                MenuItem { action: Cura.Actions.arrangeAll; }
                 MenuItem { action: Cura.Actions.deleteSelection; }
                 MenuItem { action: Cura.Actions.deleteSelection; }
                 MenuItem { action: Cura.Actions.deleteAll; }
                 MenuItem { action: Cura.Actions.deleteAll; }
-                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 { }
@@ -637,9 +637,9 @@ UM.MainWindow
         MenuItem { action: Cura.Actions.multiplyObject; }
         MenuItem { action: Cura.Actions.multiplyObject; }
         MenuSeparator { }
         MenuSeparator { }
         MenuItem { action: Cura.Actions.selectAll; }
         MenuItem { action: Cura.Actions.selectAll; }
+        MenuItem { action: Cura.Actions.arrangeAll; }
         MenuItem { action: Cura.Actions.deleteAll; }
         MenuItem { action: Cura.Actions.deleteAll; }
         MenuItem { action: Cura.Actions.reloadAll; }
         MenuItem { action: Cura.Actions.reloadAll; }
-        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 { }
@@ -698,9 +698,9 @@ UM.MainWindow
     {
     {
         id: contextMenu;
         id: contextMenu;
         MenuItem { action: Cura.Actions.selectAll; }
         MenuItem { action: Cura.Actions.selectAll; }
+        MenuItem { action: Cura.Actions.arrangeAll; }
         MenuItem { action: Cura.Actions.deleteAll; }
         MenuItem { action: Cura.Actions.deleteAll; }
         MenuItem { action: Cura.Actions.reloadAll; }
         MenuItem { action: Cura.Actions.reloadAll; }
-        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 { }