Converted comments in dir Cura/cura/Arranging to rst style

Converted doxygen style comments to reStructuredText style in the files found in Cura/cura/Arranging directory recursively  using the script dox_2_rst.py (provided in the Uranium repo). Comments were manually checked and changed if needed.

Various missing return types were added to complete Typing support
This commit is contained in:
Jelle Spijker 2020-04-22 21:42:09 +02:00 committed by Jelle Spijker
parent 68318d20fd
commit 9b44ca37ef
No known key found for this signature in database
GPG key ID: 6662DC033BE6B99A
3 changed files with 88 additions and 64 deletions

View file

@ -16,17 +16,20 @@ from collections import namedtuple
import numpy import numpy
import copy import copy
## Return object for bestSpot
LocationSuggestion = namedtuple("LocationSuggestion", ["x", "y", "penalty_points", "priority"]) LocationSuggestion = namedtuple("LocationSuggestion", ["x", "y", "penalty_points", "priority"])
"""Return object for bestSpot"""
class Arrange: class Arrange:
""" """
The Arrange classed is used together with ShapeArray. Use it to find good locations for objects that you try to put The Arrange classed is used together with :py:class:`cura.Arranging.ShapeArray.ShapeArray`. Use it 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. 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. .. note::
Make sure the scale is the same between :py:class:`cura.Arranging.ShapeArray.ShapeArray` objects and the :py:class:`cura.Arranging.Arrange.Arrange` instance.
""" """
build_volume = None # type: Optional[BuildVolume] build_volume = None # type: Optional[BuildVolume]
def __init__(self, x, y, offset_x, offset_y, scale = 0.5): def __init__(self, x, y, offset_x, offset_y, scale = 0.5):
@ -42,20 +45,20 @@ class Arrange:
self._is_empty = True self._is_empty = True
@classmethod @classmethod
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250, min_offset = 8): def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250, min_offset = 8) -> "Arrange":
""" """Helper to create an :py:class:`cura.Arranging.Arrange.Arrange` instance
Helper to create an Arranger instance
Either fill in scene_root and create will find all sliceable nodes by itself, or use fixed_nodes to provide the Either fill in scene_root and create will find all sliceable nodes by itself, or use fixed_nodes to provide the
nodes yourself. nodes yourself.
:param scene_root: Root for finding all scene nodes
:param fixed_nodes: Scene nodes to be placed :param scene_root: Root for finding all scene nodes default = None
:param scale: :param fixed_nodes: Scene nodes to be placed default = None
:param x: :param scale: default = 0.5
:param y: :param x: default = 350
:param min_offset: :param y: default = 250
:return: :param min_offset: default = 8
""" """
arranger = Arrange(x, y, x // 2, y // 2, scale = scale) arranger = Arrange(x, y, x // 2, y // 2, scale = scale)
arranger.centerFirst() arranger.centerFirst()
@ -90,19 +93,21 @@ class Arrange:
arranger.place(0, 0, shape_arr, update_empty = False) arranger.place(0, 0, shape_arr, update_empty = False)
return arranger return arranger
## This resets the optimization for finding location based on size
def resetLastPriority(self): def resetLastPriority(self):
"""This resets the optimization for finding location based on size"""
self._last_priority = 0 self._last_priority = 0
def findNodePlacement(self, node: SceneNode, offset_shape_arr: ShapeArray, hull_shape_arr: ShapeArray, step = 1): def findNodePlacement(self, node: SceneNode, offset_shape_arr: ShapeArray, hull_shape_arr: ShapeArray, step = 1) -> bool:
""" """ Find placement for a node (using offset shape) and place it (using hull shape)
Find placement for a node (using offset shape) and place it (using hull shape)
:param node: :param node: The node to be placed
:param offset_shape_arr: hapeArray with offset, for placing the shape :param offset_shape_arr: shape array with offset, for placing the shape
:param hull_shape_arr: ShapeArray without offset, used to find location :param hull_shape_arr: shape array without offset, used to find location
:param step: :param step: default = 1
:return: the nodes that should be placed :return: the nodes that should be placed
""" """
best_spot = self.bestSpot( best_spot = self.bestSpot(
hull_shape_arr, start_prio = self._last_priority, step = step) hull_shape_arr, start_prio = self._last_priority, step = step)
x, y = best_spot.x, best_spot.y x, y = best_spot.x, best_spot.y
@ -129,10 +134,8 @@ class Arrange:
return found_spot return found_spot
def centerFirst(self): def centerFirst(self):
""" """ Fill priority, center is best. Lower value is better. """
Fill priority, center is best. Lower value is better.
:return:
"""
# Square distance: creates a more round shape # Square distance: creates a more round shape
self._priority = numpy.fromfunction( self._priority = numpy.fromfunction(
lambda j, i: (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)
@ -140,23 +143,22 @@ class Arrange:
self._priority_unique_values.sort() self._priority_unique_values.sort()
def backFirst(self): def backFirst(self):
""" """ Fill priority, back is best. Lower value is better """
Fill priority, back is best. Lower value is better
:return:
"""
self._priority = numpy.fromfunction( self._priority = numpy.fromfunction(
lambda j, i: 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 = numpy.unique(self._priority)
self._priority_unique_values.sort() self._priority_unique_values.sort()
def checkShape(self, x, y, shape_arr): def checkShape(self, x, y, shape_arr) -> Optional[numpy.ndarray]:
""" """ Return the amount of "penalty points" for polygon, which is the sum of priority
Return the amount of "penalty points" for polygon, which is the sum of priority
:param x: x-coordinate to check shape :param x: x-coordinate to check shape
:param y: :param y: y-coordinate to check shape
:param shape_arr: the ShapeArray object to place :param shape_arr: the shape array object to place
:return: None if occupied :return: None if occupied
""" """
x = int(self._scale * x) x = int(self._scale * x)
y = int(self._scale * y) y = int(self._scale * y)
offset_x = x + self._offset_x + shape_arr.offset_x offset_x = x + self._offset_x + shape_arr.offset_x
@ -180,14 +182,15 @@ class Arrange:
offset_x:offset_x + shape_arr.arr.shape[1]] offset_x:offset_x + shape_arr.arr.shape[1]]
return numpy.sum(prio_slice[numpy.where(shape_arr.arr == 1)]) return numpy.sum(prio_slice[numpy.where(shape_arr.arr == 1)])
def bestSpot(self, shape_arr, start_prio = 0, step = 1): def bestSpot(self, shape_arr, start_prio = 0, step = 1) -> LocationSuggestion:
""" """ Find "best" spot for ShapeArray
Find "best" spot for ShapeArray
:param shape_arr: :param shape_arr: shape array
:param start_prio: Start with this priority value (and skip the ones before) :param start_prio: Start with this priority value (and skip the ones before)
:param step: Slicing value, higher = more skips = faster but less accurate :param step: Slicing value, higher = more skips = faster but less accurate
:return: namedtuple with properties x, y, penalty_points, priority. :return: namedtuple with properties x, y, penalty_points, priority.
""" """
start_idx_list = numpy.where(self._priority_unique_values == start_prio) start_idx_list = numpy.where(self._priority_unique_values == start_prio)
if start_idx_list: if start_idx_list:
try: try:
@ -210,15 +213,16 @@ class Arrange:
return LocationSuggestion(x = None, y = None, penalty_points = None, priority = priority) # No suitable location found :-( return LocationSuggestion(x = None, y = None, penalty_points = None, priority = priority) # No suitable location found :-(
def place(self, x, y, shape_arr, update_empty = True): def place(self, x, y, shape_arr, update_empty = True):
""" """ Place the object.
Place the object.
Marks the locations in self._occupied and self._priority Marks the locations in self._occupied and self._priority
:param x: :param x:
:param y: :param y:
:param shape_arr: :param shape_arr:
:param update_empty: updates the _is_empty, used when adding disallowed areas :param update_empty: updates the _is_empty, used when adding disallowed areas
:return:
""" """
x = int(self._scale * x) x = int(self._scale * x)
y = int(self._scale * y) y = int(self._scale * y)
offset_x = x + self._offset_x + shape_arr.offset_x offset_x = x + self._offset_x + shape_arr.offset_x

View file

@ -18,8 +18,9 @@ from cura.Arranging.ShapeArray import ShapeArray
from typing import List from typing import List
## Do arrangements on multiple build plates (aka builtiplexer)
class ArrangeArray: class ArrangeArray:
"""Do arrangements on multiple build plates (aka builtiplexer)"""
def __init__(self, x: int, y: int, fixed_nodes: List[SceneNode]) -> None: def __init__(self, x: int, y: int, fixed_nodes: List[SceneNode]) -> None:
self._x = x self._x = x
self._y = y self._y = y

View file

@ -11,19 +11,24 @@ if TYPE_CHECKING:
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
## Polygon representation as an array for use with Arrange
class ShapeArray: class ShapeArray:
"""Polygon representation as an array for use with :py:class:`cura.Arranging.Arrange.Arrange`"""
def __init__(self, arr: numpy.array, offset_x: float, offset_y: float, scale: float = 1) -> None: def __init__(self, arr: numpy.array, offset_x: float, offset_y: float, scale: float = 1) -> None:
self.arr = arr self.arr = arr
self.offset_x = offset_x self.offset_x = offset_x
self.offset_y = offset_y self.offset_y = offset_y
self.scale = scale self.scale = scale
## Instantiate from a bunch of vertices
# \param vertices
# \param scale scale the coordinates
@classmethod @classmethod
def fromPolygon(cls, vertices: numpy.array, scale: float = 1) -> "ShapeArray": def fromPolygon(cls, vertices: numpy.array, scale: float = 1) -> "ShapeArray":
"""Instantiate from a bunch of vertices
:param vertices:
:param scale: scale the coordinates
:return: a shape array instantiated from a bunch of vertices
"""
# scale # scale
vertices = vertices * scale vertices = vertices * scale
# flip y, x -> x, y # flip y, x -> x, y
@ -44,12 +49,16 @@ class ShapeArray:
arr[0][0] = 1 arr[0][0] = 1
return cls(arr, offset_x, offset_y) return cls(arr, offset_x, offset_y)
## Instantiate an offset and hull ShapeArray from a scene node.
# \param node source node where the convex hull must be present
# \param min_offset offset for the offset ShapeArray
# \param scale scale the coordinates
@classmethod @classmethod
def fromNode(cls, node: "SceneNode", min_offset: float, scale: float = 0.5, include_children: bool = False) -> Tuple[Optional["ShapeArray"], Optional["ShapeArray"]]: def fromNode(cls, node: "SceneNode", min_offset: float, scale: float = 0.5, include_children: bool = False) -> Tuple[Optional["ShapeArray"], Optional["ShapeArray"]]:
"""Instantiate an offset and hull ShapeArray from a scene node.
:param node: source node where the convex hull must be present
:param min_offset: offset for the offset ShapeArray
:param scale: scale the coordinates
:return: A tuple containing an offset and hull shape array
"""
transform = node._transformation transform = node._transformation
transform_x = transform._data[0][3] transform_x = transform._data[0][3]
transform_y = transform._data[2][3] transform_y = transform._data[2][3]
@ -88,14 +97,19 @@ class ShapeArray:
return offset_shape_arr, hull_shape_arr 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
# \param shape numpy format shape, [x-size, y-size]
# \param vertices
@classmethod @classmethod
def arrayFromPolygon(cls, shape: Tuple[int, int], vertices: numpy.array) -> numpy.array: def arrayFromPolygon(cls, shape: Tuple[int, int], vertices: numpy.array) -> numpy.array:
"""Create :py:class:`numpy.ndarray` 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: `Stackoverflow - generating a filled polygon inside a numpy array <https://stackoverflow.com/questions/37117878/generating-a-filled-polygon-inside-a-numpy-array>`_
:param shape: numpy format shape, [x-size, y-size]
:param vertices:
:return: numpy array with dimensions defined by shape
"""
base_array = numpy.zeros(shape, dtype = numpy.int32) # 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 fill = numpy.ones(base_array.shape) * True # Initialize boolean array defining shape fill
@ -111,16 +125,21 @@ class ShapeArray:
return base_array return base_array
## Return indices that mark one side of the line, used by arrayFromPolygon
# 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
# \param p1 2-tuple with x, y for point 1
# \param p2 2-tuple with x, y for point 2
# \param base_array boolean array to project the line on
@classmethod @classmethod
def _check(cls, p1: numpy.array, p2: numpy.array, base_array: numpy.array) -> Optional[numpy.array]: def _check(cls, p1: numpy.array, p2: numpy.array, base_array: numpy.array) -> Optional[numpy.array]:
"""Return indices that mark one side of the line, used by arrayFromPolygon
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: `Stackoverflow - generating a filled polygon inside a numpy array <https://stackoverflow.com/questions/37117878/generating-a-filled-polygon-inside-a-numpy-array>`_
:param p1: 2-tuple with x, y for point 1
:param p2: 2-tuple with x, y for point 2
:param base_array: boolean array to project the line on
:return: A numpy array with indices that mark one side of the line
"""
if p1[0] == p2[0] and p1[1] == p2[1]: if p1[0] == p2[0] and p1[1] == p2[1]:
return None return None
idxs = numpy.indices(base_array.shape) # Create 3D array of indices idxs = numpy.indices(base_array.shape) # Create 3D array of indices