CURA-5370 Small refactor for Arranger: make x and y consistent (numpy arrays start with y first in general), faster, cleanup, more unit tests, take actual build plate size in Arranger instances

This commit is contained in:
Jack Ha 2018-05-22 17:13:35 +02:00
parent 310aee07ac
commit f5bed242ed
8 changed files with 287 additions and 73 deletions

View file

@ -18,17 +18,20 @@ LocationSuggestion = namedtuple("LocationSuggestion", ["x", "y", "penalty_points
# 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.
#
# Note: Make sure the scale is the same between ShapeArray objects and the Arrange instance.
class Arrange:
build_volume = None
def __init__(self, x, y, offset_x, offset_y, scale= 1.0):
self.shape = (y, x)
self._priority = numpy.zeros((x, y), dtype=numpy.int32)
self._priority_unique_values = []
self._occupied = numpy.zeros((x, y), dtype=numpy.int32)
def __init__(self, x, y, offset_x, offset_y, scale= 0.5):
self._scale = scale # convert input coordinates to arrange coordinates
self._offset_x = offset_x
self._offset_y = offset_y
world_x, world_y = int(x * self._scale), int(y * self._scale)
self._shape = (world_y, world_x)
self._priority = numpy.zeros((world_y, world_x), dtype=numpy.int32) # beware: these are indexed (y, x)
self._priority_unique_values = []
self._occupied = numpy.zeros((world_y, world_x), dtype=numpy.int32) # beware: these are indexed (y, x)
self._offset_x = int(offset_x * self._scale)
self._offset_y = int(offset_y * self._scale)
self._last_priority = 0
self._is_empty = True
@ -39,7 +42,7 @@ class Arrange:
# \param scene_root Root for finding all scene nodes
# \param fixed_nodes Scene nodes to be placed
@classmethod
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 220, y = 220):
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250):
arranger = Arrange(x, y, x // 2, y // 2, scale = scale)
arranger.centerFirst()
@ -61,13 +64,17 @@ class Arrange:
# If a build volume was set, add the disallowed areas
if Arrange.build_volume:
disallowed_areas = Arrange.build_volume.getDisallowedAreas()
disallowed_areas = Arrange.build_volume.getDisallowedAreasNoBrim()
for area in disallowed_areas:
points = copy.deepcopy(area._points)
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
arranger.place(0, 0, shape_arr, update_empty = False)
return arranger
## This resets the optimization for finding location based on size
def resetLastPriority(self):
self._last_priority = 0
## Find placement for a node (using offset shape) and place it (using hull shape)
# return the nodes that should be placed
# \param node
@ -104,7 +111,7 @@ class Arrange:
def centerFirst(self):
# 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)
lambda j, i: (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()
@ -112,7 +119,7 @@ class Arrange:
# This is a strategy for the arranger.
def backFirst(self):
self._priority = numpy.fromfunction(
lambda i, j: 10 * j + abs(self._offset_x - i), self.shape, dtype=numpy.int32)
lambda j, i: 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()
@ -126,9 +133,15 @@ class Arrange:
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
if offset_x < 0 or offset_y < 0:
return None # out of bounds in self._occupied
occupied_x_max = offset_x + shape_arr.arr.shape[1]
occupied_y_max = offset_y + shape_arr.arr.shape[0]
if occupied_x_max > self._occupied.shape[1] + 1 or occupied_y_max > self._occupied.shape[0] + 1:
return None # out of bounds in self._occupied
occupied_slice = self._occupied[
offset_y:offset_y + shape_arr.arr.shape[0],
offset_x:offset_x + shape_arr.arr.shape[1]]
offset_y:occupied_y_max,
offset_x:occupied_x_max]
try:
if numpy.any(occupied_slice[numpy.where(shape_arr.arr == 1)]):
return None
@ -140,7 +153,7 @@ class Arrange:
return numpy.sum(prio_slice[numpy.where(shape_arr.arr == 1)])
## Find "best" spot for ShapeArray
# Return namedtuple with properties x, y, penalty_points, priority
# Return namedtuple with properties x, y, penalty_points, priority.
# \param shape_arr ShapeArray
# \param start_prio Start with this priority value (and skip the ones before)
# \param step Slicing value, higher = more skips = faster but less accurate
@ -153,12 +166,11 @@ class Arrange:
for priority in self._priority_unique_values[start_idx::step]:
tryout_idx = numpy.where(self._priority == priority)
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
x = tryout_idx[1][idx]
y = tryout_idx[0][idx]
projected_x = int((x - self._offset_x) / self._scale)
projected_y = int((y - self._offset_y) / self._scale)
# array to "world" coordinates
penalty_points = self.checkShape(projected_x, projected_y, shape_arr)
if penalty_points is not None:
return LocationSuggestion(x = projected_x, y = projected_y, penalty_points = penalty_points, priority = priority)
@ -191,8 +203,12 @@ class Arrange:
# 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[numpy.where(shape_arr.arr[
min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 999
prio_slice[new_occupied] = 999
# If you want to see how the rasterized arranger build plate looks like, uncomment this code
# numpy.set_printoptions(linewidth=500, edgeitems=200)
# print(self._occupied.shape)
# print(self._occupied)
@property
def isEmpty(self):

View file

@ -1,6 +1,7 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Job import Job
from UM.Scene.SceneNode import SceneNode
from UM.Math.Vector import Vector
@ -17,6 +18,7 @@ from cura.Arranging.ShapeArray import ShapeArray
from typing import List
## Do an arrangements on a bunch of build plates
class ArrangeArray:
def __init__(self, x: int, y: int, fixed_nodes: List[SceneNode]):
self._x = x
@ -79,7 +81,11 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
nodes_arr.sort(key=lambda item: item[0])
nodes_arr.reverse()
x, y = 200, 200
global_container_stack = Application.getInstance().getGlobalContainerStack()
machine_width = global_container_stack.getProperty("machine_width", "value")
machine_depth = global_container_stack.getProperty("machine_depth", "value")
x, y = machine_width, machine_depth
arrange_array = ArrangeArray(x = x, y = y, fixed_nodes = [])
arrange_array.add()
@ -93,27 +99,18 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
for idx, (size, node, offset_shape_arr, hull_shape_arr) in enumerate(nodes_arr):
# For performance reasons, we assume that when a location does not fit,
# it will also not fit for the next object (while what can be untrue).
# We also skip possibilities by slicing through the possibilities (step = 10)
try_placement = True
current_build_plate_number = 0 # always start with the first one
# # Only for first build plate
# if last_size == size and last_build_plate_number == current_build_plate_number:
# # This optimization works if many of the objects have the same size
# # Continue with same build plate number
# start_priority = last_priority
# else:
# start_priority = 0
while try_placement:
# make sure that current_build_plate_number is not going crazy or you'll have a lot of arrange objects
while current_build_plate_number >= arrange_array.count():
arrange_array.add()
arranger = arrange_array.get(current_build_plate_number)
best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority, step=10)
best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority)
x, y = best_spot.x, best_spot.y
node.removeDecorator(ZOffsetDecorator)
if node.getBoundingBox():

View file

@ -1,6 +1,7 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Job import Job
from UM.Scene.SceneNode import SceneNode
from UM.Math.Vector import Vector
@ -32,7 +33,11 @@ class ArrangeObjectsJob(Job):
progress = 0,
title = i18n_catalog.i18nc("@info:title", "Finding Location"))
status_message.show()
arranger = Arrange.create(fixed_nodes = self._fixed_nodes)
global_container_stack = Application.getInstance().getGlobalContainerStack()
machine_width = global_container_stack.getProperty("machine_width", "value")
machine_depth = global_container_stack.getProperty("machine_depth", "value")
arranger = Arrange.create(x = machine_width, y = machine_depth, fixed_nodes = self._fixed_nodes)
# Collect nodes to be placed
nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr)
@ -50,15 +55,15 @@ class ArrangeObjectsJob(Job):
last_size = None
grouped_operation = GroupedOperation()
found_solution_for_all = True
not_fit_count = 0
for idx, (size, node, offset_shape_arr, hull_shape_arr) in enumerate(nodes_arr):
# For performance reasons, we assume that when a location does not fit,
# it will also not fit for the next object (while what can be untrue).
# We also skip possibilities by slicing through the possibilities (step = 10)
if last_size == size: # This optimization works if many of the objects have the same size
start_priority = last_priority
else:
start_priority = 0
best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority, step=10)
best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority)
x, y = best_spot.x, best_spot.y
node.removeDecorator(ZOffsetDecorator)
if node.getBoundingBox():
@ -70,12 +75,12 @@ class ArrangeObjectsJob(Job):
last_priority = best_spot.priority
arranger.place(x, y, hull_shape_arr) # take place before the next one
grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True))
else:
Logger.log("d", "Arrange all: could not find spot!")
found_solution_for_all = False
grouped_operation.addOperation(TranslateOperation(node, Vector(200, center_y, - idx * 20), set_position = True))
grouped_operation.addOperation(TranslateOperation(node, Vector(200, center_y, -not_fit_count * 20), set_position = True))
not_fit_count += 1
status_message.setProgress((idx + 1) / len(nodes_arr) * 100)
Job.yieldThread()

View file

@ -74,7 +74,7 @@ class ShapeArray:
# \param vertices
@classmethod
def arrayFromPolygon(cls, shape, vertices):
base_array = numpy.zeros(shape, dtype=float) # Initialize your array of zeros
base_array = numpy.zeros(shape, dtype = numpy.int32) # Initialize your array of zeros
fill = numpy.ones(base_array.shape) * True # Initialize boolean array defining shape fill

View file

@ -25,6 +25,7 @@ catalog = i18nCatalog("cura")
import numpy
import math
import copy
from typing import List, Optional
@ -61,6 +62,7 @@ class BuildVolume(SceneNode):
self._grid_shader = None
self._disallowed_areas = []
self._disallowed_areas_no_brim = []
self._disallowed_area_mesh = None
self._error_areas = []
@ -171,6 +173,9 @@ class BuildVolume(SceneNode):
def getDisallowedAreas(self) -> List[Polygon]:
return self._disallowed_areas
def getDisallowedAreasNoBrim(self) -> List[Polygon]:
return self._disallowed_areas_no_brim
def setDisallowedAreas(self, areas: List[Polygon]):
self._disallowed_areas = areas
@ -658,7 +663,8 @@ class BuildVolume(SceneNode):
result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) #Normal machine disallowed areas can always be added.
prime_areas = self._computeDisallowedAreasPrimeBlob(disallowed_border_size, used_extruders)
prime_disallowed_areas = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking.
result_areas_no_brim = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking.
prime_disallowed_areas = copy.deepcopy(result_areas_no_brim)
#Check if prime positions intersect with disallowed areas.
for extruder in used_extruders:
@ -687,12 +693,15 @@ class BuildVolume(SceneNode):
break
result_areas[extruder_id].extend(prime_areas[extruder_id])
result_areas_no_brim[extruder_id].extend(prime_areas[extruder_id])
nozzle_disallowed_areas = extruder.getProperty("nozzle_disallowed_areas", "value")
for area in nozzle_disallowed_areas:
polygon = Polygon(numpy.array(area, numpy.float32))
polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(disallowed_border_size))
result_areas[extruder_id].append(polygon) #Don't perform the offset on these.
polygon_disallowed_border = polygon.getMinkowskiHull(Polygon.approximatedCircle(disallowed_border_size))
result_areas[extruder_id].append(polygon_disallowed_border) #Don't perform the offset on these.
#polygon_minimal_border = polygon.getMinkowskiHull(5)
result_areas_no_brim[extruder_id].append(polygon) # no brim
# Add prime tower location as disallowed area.
if len(used_extruders) > 1: #No prime tower in single-extrusion.
@ -708,6 +717,7 @@ class BuildVolume(SceneNode):
break
if not prime_tower_collision:
result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
result_areas_no_brim[extruder_id].extend(prime_tower_areas[extruder_id])
else:
self._error_areas.extend(prime_tower_areas[extruder_id])
@ -716,6 +726,9 @@ class BuildVolume(SceneNode):
self._disallowed_areas = []
for extruder_id in result_areas:
self._disallowed_areas.extend(result_areas[extruder_id])
self._disallowed_areas_no_brim = []
for extruder_id in result_areas_no_brim:
self._disallowed_areas_no_brim.extend(result_areas_no_brim[extruder_id])
## Computes the disallowed areas for objects that are printed with print
# features.

View file

@ -1260,29 +1260,6 @@ class CuraApplication(QtApplication):
nodes.append(node)
self.arrange(nodes, fixed_nodes = [])
## Arrange Selection
@pyqtSlot()
def arrangeSelection(self):
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 not isinstance(node, 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
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
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)
## Arrange a set of nodes given a set of fixed nodes
# \param nodes nodes that we have to place
# \param fixed_nodes nodes that are placed in the arranger before finding spots for nodes

View file

@ -30,11 +30,18 @@ class MultiplyObjectsJob(Job):
total_progress = len(self._objects) * self._count
current_progress = 0
global_container_stack = Application.getInstance().getGlobalContainerStack()
machine_width = global_container_stack.getProperty("machine_width", "value")
machine_depth = global_container_stack.getProperty("machine_depth", "value")
root = scene.getRoot()
arranger = Arrange.create(scene_root=root)
scale = 0.5
arranger = Arrange.create(x = machine_width, y = machine_depth, scene_root = root, scale = scale)
processed_nodes = []
nodes = []
not_fit_count = 0
for node in self._objects:
# If object is part of a group, multiply group
current_node = node
@ -46,12 +53,13 @@ class MultiplyObjectsJob(Job):
processed_nodes.append(current_node)
node_too_big = False
if node.getBoundingBox().width < 300 or node.getBoundingBox().depth < 300:
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset)
if node.getBoundingBox().width < machine_width or node.getBoundingBox().depth < machine_depth:
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset = self._min_offset, scale = scale)
else:
node_too_big = True
found_solution_for_all = True
arranger.resetLastPriority()
for i in range(self._count):
# We do place the nodes one by one, as we want to yield in between.
if not node_too_big:
@ -59,8 +67,9 @@ class MultiplyObjectsJob(Job):
if node_too_big or not solution_found:
found_solution_for_all = False
new_location = new_node.getPosition()
new_location = new_location.set(z = 100 - i * 20)
new_location = new_location.set(z = - not_fit_count * 20)
new_node.setPosition(new_location)
not_fit_count += 1
# Same build plate
build_plate_number = current_node.callDecoration("getBuildPlateNumber")

View file

@ -4,9 +4,17 @@ from cura.Arranging.Arrange import Arrange
from cura.Arranging.ShapeArray import ShapeArray
def gimmeShapeArray():
vertices = numpy.array([[-3, 1], [3, 1], [0, -3]])
shape_arr = ShapeArray.fromPolygon(vertices)
## Triangle of area 12
def gimmeShapeArray(scale = 1.0):
vertices = numpy.array([[-3, 1], [3, 1], [0, -3]], dtype=numpy.int32)
shape_arr = ShapeArray.fromPolygon(vertices, scale = scale)
return shape_arr
## Boring square
def gimmeShapeArraySquare(scale = 1.0):
vertices = numpy.array([[-2, -2], [2, -2], [2, 2], [-2, 2]], dtype=numpy.int32)
shape_arr = ShapeArray.fromPolygon(vertices, scale = scale)
return shape_arr
@ -20,6 +28,45 @@ def test_smoke_ShapeArray():
shape_arr = gimmeShapeArray()
## Test ShapeArray
def test_ShapeArray():
scale = 1
ar = Arrange(16, 16, 8, 8, scale = scale)
ar.centerFirst()
shape_arr = gimmeShapeArray(scale)
print(shape_arr.arr)
count = len(numpy.where(shape_arr.arr == 1)[0])
print(count)
assert count >= 10 # should approach 12
## Test ShapeArray with scaling
def test_ShapeArray_scaling():
scale = 2
ar = Arrange(16, 16, 8, 8, scale = scale)
ar.centerFirst()
shape_arr = gimmeShapeArray(scale)
print(shape_arr.arr)
count = len(numpy.where(shape_arr.arr == 1)[0])
print(count)
assert count >= 40 # should approach 2*2*12 = 48
## Test ShapeArray with scaling
def test_ShapeArray_scaling2():
scale = 0.5
ar = Arrange(16, 16, 8, 8, scale = scale)
ar.centerFirst()
shape_arr = gimmeShapeArray(scale)
print(shape_arr.arr)
count = len(numpy.where(shape_arr.arr == 1)[0])
print(count)
assert count >= 1 # should approach 3, but it can be inaccurate due to pixel rounding
## Test centerFirst
def test_centerFirst():
ar = Arrange(300, 300, 150, 150)
@ -32,13 +79,33 @@ def test_centerFirst():
assert ar._priority[150][150] < ar._priority[130][130]
## Test centerFirst
def test_centerFirst_rectangular():
ar = Arrange(400, 300, 200, 150)
ar.centerFirst()
assert ar._priority[150][200] < ar._priority[150][220]
assert ar._priority[150][200] < ar._priority[170][200]
assert ar._priority[150][200] < ar._priority[170][220]
assert ar._priority[150][200] < ar._priority[180][150]
assert ar._priority[150][200] < ar._priority[130][200]
assert ar._priority[150][200] < ar._priority[130][180]
## Test centerFirst
def test_centerFirst_rectangular():
ar = Arrange(10, 20, 5, 10)
ar.centerFirst()
print(ar._priority)
assert ar._priority[10][5] < ar._priority[10][7]
## Test backFirst
def test_backFirst():
ar = Arrange(300, 300, 150, 150)
ar.backFirst()
assert ar._priority[150][150] < ar._priority[150][170]
assert ar._priority[150][150] < ar._priority[170][150]
assert ar._priority[150][150] < ar._priority[170][170]
assert ar._priority[150][150] > ar._priority[150][130]
assert ar._priority[150][150] > ar._priority[130][150]
assert ar._priority[150][150] > ar._priority[130][130]
@ -55,6 +122,113 @@ def test_smoke_bestSpot():
assert hasattr(best_spot, "priority")
## Real life test
def test_bestSpot():
ar = Arrange(16, 16, 8, 8)
ar.centerFirst()
shape_arr = gimmeShapeArray()
best_spot = ar.bestSpot(shape_arr)
assert best_spot.x == 0
assert best_spot.y == 0
ar.place(best_spot.x, best_spot.y, shape_arr)
# Place object a second time
best_spot = ar.bestSpot(shape_arr)
assert best_spot.x is not None # we found a location
assert best_spot.x != 0 or best_spot.y != 0 # it can't be on the same location
ar.place(best_spot.x, best_spot.y, shape_arr)
print(ar._occupied) # For debugging
## Real life test rectangular build plate
def test_bestSpot_rectangular_build_plate():
ar = Arrange(16, 40, 8, 20)
ar.centerFirst()
shape_arr = gimmeShapeArray()
best_spot = ar.bestSpot(shape_arr)
ar.place(best_spot.x, best_spot.y, shape_arr)
assert best_spot.x == 0
assert best_spot.y == 0
# Place object a second time
best_spot2 = ar.bestSpot(shape_arr)
assert best_spot2.x is not None # we found a location
assert best_spot2.x != 0 or best_spot2.y != 0 # it can't be on the same location
ar.place(best_spot2.x, best_spot2.y, shape_arr)
# Place object a 3rd time
best_spot3 = ar.bestSpot(shape_arr)
assert best_spot3.x is not None # we found a location
assert best_spot3.x != best_spot.x or best_spot3.y != best_spot.y # it can't be on the same location
assert best_spot3.x != best_spot2.x or best_spot3.y != best_spot2.y # it can't be on the same location
ar.place(best_spot3.x, best_spot3.y, shape_arr)
best_spot_x = ar.bestSpot(shape_arr)
ar.place(best_spot_x.x, best_spot_x.y, shape_arr)
best_spot_x = ar.bestSpot(shape_arr)
ar.place(best_spot_x.x, best_spot_x.y, shape_arr)
best_spot_x = ar.bestSpot(shape_arr)
ar.place(best_spot_x.x, best_spot_x.y, shape_arr)
print(ar._occupied) # For debugging
## Real life test
def test_bestSpot_scale():
scale = 0.5
ar = Arrange(16, 16, 8, 8, scale = scale)
ar.centerFirst()
shape_arr = gimmeShapeArray(scale)
best_spot = ar.bestSpot(shape_arr)
assert best_spot.x == 0
assert best_spot.y == 0
ar.place(best_spot.x, best_spot.y, shape_arr)
print(ar._occupied)
# Place object a second time
best_spot = ar.bestSpot(shape_arr)
assert best_spot.x is not None # we found a location
assert best_spot.x != 0 or best_spot.y != 0 # it can't be on the same location
ar.place(best_spot.x, best_spot.y, shape_arr)
print(ar._occupied) # For debugging
## Real life test
def test_bestSpot_scale_rectangular():
scale = 0.5
ar = Arrange(16, 40, 8, 20, scale = scale)
ar.centerFirst()
shape_arr = gimmeShapeArray(scale)
shape_arr_square = gimmeShapeArraySquare(scale)
best_spot = ar.bestSpot(shape_arr_square)
assert best_spot.x == 0
assert best_spot.y == 0
ar.place(best_spot.x, best_spot.y, shape_arr_square)
print(ar._occupied)
# Place object a second time
best_spot = ar.bestSpot(shape_arr)
assert best_spot.x is not None # we found a location
assert best_spot.x != 0 or best_spot.y != 0 # it can't be on the same location
ar.place(best_spot.x, best_spot.y, shape_arr)
best_spot = ar.bestSpot(shape_arr_square)
ar.place(best_spot.x, best_spot.y, shape_arr_square)
print(ar._occupied) # For debugging
## Try to place an object and see if something explodes
def test_smoke_place():
ar = Arrange(30, 30, 15, 15)
@ -80,6 +254,20 @@ def test_checkShape():
assert points3 > points
## See of our center has less penalty points than out of the center
def test_checkShape_rectangular():
ar = Arrange(20, 30, 10, 15)
ar.centerFirst()
print(ar._priority)
shape_arr = gimmeShapeArray()
points = ar.checkShape(0, 0, shape_arr)
points2 = ar.checkShape(5, 0, shape_arr)
points3 = ar.checkShape(0, 5, shape_arr)
assert points2 > points
assert points3 > points
## Check that placing an object on occupied place returns None.
def test_checkShape_place():
ar = Arrange(30, 30, 15, 15)
@ -104,6 +292,13 @@ def test_smoke_place_objects():
ar.place(best_spot_x, best_spot_y, shape_arr)
# Test some internals
def test_compare_occupied_and_priority_tables():
ar = Arrange(10, 15, 5, 7)
ar.centerFirst()
assert ar._priority.shape == ar._occupied.shape
## Polygon -> array
def test_arrayFromPolygon():
vertices = numpy.array([[-3, 1], [3, 1], [0, -3]])
@ -145,3 +340,5 @@ def test_check2():
assert numpy.any(check_array)
assert not check_array[3][0]
assert check_array[3][4]