Source code for api.selection

from __future__ import annotations

import itertools

from typing import Sequence, List, Iterator, Tuple, Union, cast

from api.types import Point


[docs]class SubBox: """ A SubBox is a box that can represent the entirety of a SelectionBox or just a subsection of one. This allows for non-rectangular and non-contiguous selections. The both the minimum and maximum coordinate points are inclusive. """ def __init__(self, min_point: Point, max_point: Point): self.min = min_point self.max = max_point def __iter__(self): return itertools.product( range(self.min[0], self.max[0] + 1), range(self.min[1], self.max[1] + 1), range(self.min[2], self.max[2] + 1), ) def __str__(self): return f"({self.min}, {self.max})" def __contains__(self, item: Union[Point, Tuple[int, int, int]]): return ( self.min[0] <= item[0] <= self.max[0] and self.min[1] <= item[1] <= self.max[1] and self.min[2] <= item[2] <= self.max[2] )
[docs] def to_slice(self) -> List[slice]: """ Converts the SubBoxes minimum/maximum coordinates into slice arguments :return: The SubBoxes coordinates as slices in (x,y,z) order """ return [ slice(self.min[0], self.max[0] + 1), slice(self.min[1], self.max[1] + 1), slice(self.min[2], self.max[2] + 1), ]
@property def min_x(self): return self.min[0] @property def min_y(self): return self.min[1] @property def min_z(self): return self.min[2] @property def max_x(self): return self.max[0] @property def max_y(self): return self.max[1] @property def max_z(self): return self.max[2] @property def shape(self): return self.max_x - self.min_x, self.max_y - self.min_y, self.max_z - self.min_z
[docs] def intersects(self, other: SubBox) -> bool: """ Method to check whether this instance of SubBox intersects another SubBox :param other: The other SubBox to check for intersection :return: True if the two SubBoxes intersect, False otherwise """ return not ( self.min_x > other.max_x or self.min_y > other.max_y or self.min_z > other.max_z or self.max_x < other.min_x or self.max_y < other.min_y or self.max_z < other.min_z )
[docs]class SelectionBox: """ Holding class for multiple SubBoxes which allows for non-rectangular and non-contiguous selections """ def __init__(self, boxes: Sequence[SubBox] = None): if not boxes: boxes = [] if isinstance(boxes, tuple): boxes = list(boxes) self._boxes = boxes def __iter__(self): return itertools.chain.from_iterable(sorted(self._boxes, key=hash)) def __len__(self): return len(self._boxes) def __contains__(self, item: Union[Point, Tuple[int, int, int]]): for subbox in self._boxes: if item in subbox: return True return False
[docs] def add_box(self, other: SubBox, do_merge_check: bool = True): """ Adds a SubBox to the selection box. If `other` is next to another SubBox in the selection, matches in any 2 dimensions, and `do_merge_check` is True, then the 2 boxes will be combined into 1 box. :param other: The box to add :param do_merge_check: Boolean flag to merge boxes if able """ if do_merge_check: boxes_to_remove = None new_box = None for box in self._boxes: x_dim = box.min_x == other.min_x and box.max_x == other.max_x y_dim = box.min_y == other.min_y and box.max_y == other.max_y z_dim = box.min_z == other.min_z and box.max_z == other.max_z x_border = box.max_x == other.min_x or other.max_x == box.min_x y_border = box.max_y == other.min_y or other.max_y == box.min_y z_border = box.max_z == other.min_z or other.max_z == box.min_z if ( (x_dim and y_dim and z_border) or (x_dim and z_dim and y_border) or (y_dim and z_dim and x_border) ): boxes_to_remove = box new_box = SubBox(box.min, other.max) break if new_box: self._boxes.remove(boxes_to_remove) self.add_box(new_box) else: self._boxes.append(other) else: self._boxes.append(other)
def is_contiguous(self) -> bool: if len(self._boxes) == 1: return True for i in range(len(self._boxes) - 1): sub_box = self._boxes[i] next_box = self._boxes[i + 1] if ( abs(sub_box.max[0] - next_box.min[0]) > 1 and abs(sub_box.max[1] - next_box.min[1]) > 1 and abs(sub_box.max[2] - next_box.min[2]) > 1 ): return False return True
[docs] def is_rectangular(self) -> bool: """ Checks if the SelectionBox is a rectangle :return: True is the selection is a rectangle, False otherwise """ return len(self._boxes) == 1
[docs] def subboxes(self) -> Iterator[SubBox]: """ Returns an iterator of the SubBoxes in the SelectionBoxes :return: An iterator of the SubBoxes """ return cast(Iterator[SubBox], iter(sorted(self._boxes, key=hash)))
if __name__ == "__main__": b1 = SubBox((0, 0, 0), (4, 4, 4)) b2 = SubBox((7, 7, 7), (10, 10, 10)) sel_box = SelectionBox((b1, b2)) # for obj in sel_box: # for x, y, z in obj: # print(x,y,z) for x, y, z in sel_box: print(x, y, z)