First version of multiply object seems to work quite well. CURA-3239

This commit is contained in:
Jack Ha 2017-03-28 11:33:07 +02:00
parent 462f3abead
commit d8c20b9d6c
2 changed files with 112 additions and 77 deletions

View File

@ -12,47 +12,24 @@ class ShapeArray:
def from_polygon(cls, vertices, scale = 1):
# scale
vertices = vertices * scale
# flip x, y
flip_vertices = np.zeros((vertices.shape))
flip_vertices[:, 0] = vertices[:, 1]
flip_vertices[:, 1] = vertices[:, 0]
flip_vertices = flip_vertices[::-1]
# offset
offset_y = int(np.amin(vertices[:, 0]))
offset_x = int(np.amin(vertices[:, 1]))
# normalize to 0
vertices[:, 0] = np.add(vertices[:, 0], -offset_y)
vertices[:, 1] = np.add(vertices[:, 1], -offset_x)
shape = [int(np.amax(vertices[:, 0])), int(np.amax(vertices[:, 1]))]
arr = cls.array_from_polygon(shape, vertices)
offset_y = int(np.amin(flip_vertices[:, 0]))
offset_x = int(np.amin(flip_vertices[:, 1]))
# offset to 0
flip_vertices[:, 0] = np.add(flip_vertices[:, 0], -offset_y)
flip_vertices[:, 1] = np.add(flip_vertices[:, 1], -offset_x)
shape = [int(np.amax(flip_vertices[:, 0])), int(np.amax(flip_vertices[:, 1]))]
#from UM.Logger import Logger
#Logger.log("d", " Vertices: %s" % str(flip_vertices))
arr = cls.array_from_polygon(shape, flip_vertices)
return cls(arr, offset_x, offset_y)
## 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 = np.indices(base_array.shape) # Create 3D array of indices
p1 = p1.astype(float)
p2 = p2.astype(float)
if p2[0] == p1[0]:
sign = np.sign(p2[1] - p1[1])
return idxs[1] * sign
if p2[1] == p1[1]:
sign = np.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 = np.sign(p2[0] - p1[0])
return idxs[1] * sign <= max_col_idx * sign
@classmethod
def array_from_polygon(cls, shape, vertices):
"""
@ -74,6 +51,35 @@ class ShapeArray:
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 = np.indices(base_array.shape) # Create 3D array of indices
p1 = p1.astype(float)
p2 = p2.astype(float)
if p2[0] == p1[0]:
sign = np.sign(p2[1] - p1[1])
return idxs[1] * sign
if p2[1] == p1[1]:
sign = np.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 = np.sign(p2[0] - p1[0])
return idxs[1] * sign <= max_col_idx * sign
class Arrange:
def __init__(self, x, y, offset_x, offset_y, scale=1):
@ -99,7 +105,10 @@ class Arrange:
occupied_slice = self._occupied[
offset_y:offset_y + shape_arr.arr.shape[0],
offset_x:offset_x + shape_arr.arr.shape[1]]
if np.any(occupied_slice[np.where(shape_arr.arr == 1)]):
try:
if np.any(occupied_slice[np.where(shape_arr.arr == 1)]):
return 999999
except IndexError: # out of bounds if you try to place an object outside
return 999999
prio_slice = self._priority[
offset_y:offset_y + shape_arr.arr.shape[0],
@ -122,33 +131,39 @@ class Arrange:
return best_x, best_y, best_points
## Faster
def bestSpot(self, shape_arr):
min_y = max(-shape_arr.offset_y, 0) - self._offset_y
max_y = self.shape[0] - shape_arr.arr.shape[0] - self._offset_y
min_x = max(-shape_arr.offset_x, 0) - self._offset_x
max_x = self.shape[1] - shape_arr.arr.shape[1] - self._offset_x
for prio in range(200):
def bestSpot(self, shape_arr, start_prio = 0):
for prio in range(start_prio, 300):
tryout_idx = np.where(self._priority == prio)
for idx in range(len(tryout_idx[0])):
x = tryout_idx[0][idx]
y = tryout_idx[1][idx]
projected_x = x - self._offset_x
projected_y = y - self._offset_y
if projected_x < min_x or projected_x > max_x or projected_y < min_y or projected_y > max_y:
continue
# array to "world" coordinates
penalty_points = self.check_shape(projected_x, projected_y, shape_arr)
if penalty_points != 999999:
return projected_x, projected_y, penalty_points
return None, None, None # No suitable location found :-(
return projected_x, projected_y, penalty_points, prio
return None, None, None, prio # No suitable location found :-(
## Place the object
def place(self, x, y, shape_arr):
x = int(self._scale * x)
y = int(self._scale * y)
offset_x = x + self._offset_x + shape_arr.offset_x
offset_y = y + self._offset_y + shape_arr.offset_y
occupied_slice = self._occupied[
offset_y:offset_y + shape_arr.arr.shape[0],
offset_x:offset_x + shape_arr.arr.shape[1]]
occupied_slice[np.where(shape_arr.arr == 1)] = 1
shape_y, shape_x = self._occupied.shape
min_x = min(max(offset_x, 0), shape_x - 1)
min_y = min(max(offset_y, 0), shape_y - 1)
max_x = min(max(offset_x + shape_arr.arr.shape[1], 0), shape_x - 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]
# we use a slice of shape because it can be out of bounds
occupied_slice[np.where(shape_arr.arr[
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.
prio_slice = self._priority[min_y:max_y, min_x:max_x]
prio_slice[np.where(shape_arr.arr[
min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 999

View File

@ -14,6 +14,7 @@ from UM.Math.Matrix import Matrix
from UM.Resources import Resources
from UM.Scene.ToolHandle import ToolHandle
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Math.Polygon import Polygon
from UM.Mesh.ReadMeshJob import ReadMeshJob
from UM.Logger import Logger
from UM.Preferences import Preferences
@ -32,6 +33,7 @@ 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.SetParentOperation import SetParentOperation
from cura.SliceableObjectDecorator import SliceableObjectDecorator
from cura.BlockSlicingDecorator import BlockSlicingDecorator
@ -844,16 +846,16 @@ class CuraApplication(QtApplication):
op.push()
## 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):
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)
### testing
from cura.Arrange import Arrange, ShapeArray
arranger = Arrange(215, 215, 107, 107)
arranger.centerFirst()
@ -863,35 +865,56 @@ class CuraApplication(QtApplication):
# Only count sliceable objects
if node_.callDecoration("isSliceable"):
Logger.log("d", " # Placing [%s]" % str(node_))
vertices = node_.callDecoration("getConvexHull")
points = copy.deepcopy(vertices._points)
#points[:,1] = -points[:,1]
#points = points[::-1] # reverse
shape_arr = ShapeArray.from_polygon(points)
transform = node_._transformation
x = transform._data[0][3]
y = transform._data[2][3]
arranger.place(x, y, shape_arr)
arranger.place(0, 0, shape_arr)
Logger.log("d", "Current buildplate: \n%s" % str(arranger._occupied[::10, ::10]))
Logger.log("d", "Current scrores: \n%s" % str(arranger._priority[::20, ::20]))
nodes = []
for _ in range(count):
# 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)
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) # x, y
start_prio = 0
for i in range(count):
new_node = copy.deepcopy(node)
vertices = new_node.callDecoration("getConvexHull")
points = copy.deepcopy(vertices._points)
#points[:, 1] = -points[:, 1]
#points = points[::-1] # reverse
shape_arr = ShapeArray.from_polygon(points)
transformation = new_node._transformation
Logger.log("d", " # Finding spot for %s" % new_node)
x, y, penalty_points = arranger.bestSpot(shape_arr)
x, y, penalty_points, start_prio = arranger.bestSpot(
offset_shape_arr, start_prio = start_prio)
transformation = new_node._transformation
if x is not None: # We could find a place
transformation._data[0][3] = x
transformation._data[2][3] = y
arranger.place(x, y, shape_arr) # take place before the next one
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
# TODO: where to place it?
# new_node.setTransformation(transformation)
nodes.append(new_node)
### testing
if node:
current_node = node
@ -902,9 +925,6 @@ class CuraApplication(QtApplication):
op = GroupedOperation()
for new_node in nodes:
op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
# for _ in range(count):
# new_node = copy.deepcopy(current_node)
# op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
op.push()
## Center object on platform.