diff --git a/cura/Arranging/Arrange.py b/cura/Arranging/Arrange.py index a90a97c3c2..f8c6ae8a31 100644 --- a/cura/Arranging/Arrange.py +++ b/cura/Arranging/Arrange.py @@ -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): diff --git a/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py b/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py index 3f23b0dbe7..1918b32907 100644 --- a/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py +++ b/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py @@ -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(): diff --git a/cura/Arranging/ArrangeObjectsJob.py b/cura/Arranging/ArrangeObjectsJob.py index 765c3333cb..01a91a3c22 100644 --- a/cura/Arranging/ArrangeObjectsJob.py +++ b/cura/Arranging/ArrangeObjectsJob.py @@ -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() diff --git a/cura/Arranging/ShapeArray.py b/cura/Arranging/ShapeArray.py index 68be9a6478..ab785cc3e1 100644 --- a/cura/Arranging/ShapeArray.py +++ b/cura/Arranging/ShapeArray.py @@ -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 diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 98e087707a..ea0d8e1565 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -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. diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 034d045ce6..9c752cb7a2 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -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 diff --git a/cura/MultiplyObjectsJob.py b/cura/MultiplyObjectsJob.py index 3444da249f..46f7f56f8a 100644 --- a/cura/MultiplyObjectsJob.py +++ b/cura/MultiplyObjectsJob.py @@ -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") diff --git a/tests/TestArrange.py b/tests/TestArrange.py index 4f6bb64118..354bbf4962 100755 --- a/tests/TestArrange.py +++ b/tests/TestArrange.py @@ -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] + +