# brickalize/bricks.py
"""
Defines the fundamental Brick object and a collection class, BrickSet.
"""
# No external libraries like numpy, open3d etc. are directly used by these two classes.
# Standard library imports might be needed for older Python versions for type hints,
# but for Python 3.9+ list, tuple, set, dict work directly.
[docs]
class Brick:
# Property for all bricks
height = 1.2
[docs]
def __init__(self, d1:int, d2:int, is_support:bool = False):
"""
Brick object
Args:
d1 (int): First dimension of the lego brick (length)
d2 (int): Second dimension of the lego brick (width)
is_support (bool): Whether the brick is a normal (model) or support brick (for lego printers)
Raises:
TypeError: if the variable types are invalid
ValueError: if the dimensions are negative
"""
# Verify that the inputs are valid
self.__verify_inputs(d1, d2, is_support)
# Store the parameters privately
self.__length = max(d1, d2)
self.__width = min(d1, d2)
self.__is_support = is_support
def __verify_inputs(self, d1, d2, is_support):
if not (isinstance(d1, int) and isinstance(d2, int)):
raise TypeError("Dimensions of Brick must be integers.")
if d1 < 1 or d2 < 1:
raise ValueError("Dimensions of Brick must be at least 1")
if not isinstance(is_support, bool):
raise TypeError("is_support for Brick must be a boolean")
@property
def length(self) -> int:
"""Biggest dimension of the Lego Brick (excluding height)."""
return self.__length
@property
def width(self) -> int:
"""Smallest dimension of the Lego Brick (excluding height)."""
return self.__width
@property
def orientations(self) -> list[tuple[int, int]]:
"""
A list of all of the possible orientations that the brick can be in (1 or 2).
Returns:
list[tuple[int, int]]: The list of all possible orientations. These are tuples of x and y.
"""
if self.__length != self.__width:
return [(self.__length, self.__width), (self.__width, self.__length)]
else:
return [(self.__length, self.__width)]
@property
def is_support(self) -> bool:
"""Whether or not the brick type is a support or a building brick."""
return self.__is_support
[docs]
def oriented(self, x_long:bool) -> tuple[tuple[int, int], bool]:
"""
Get an oriented, 2D version of the brick.
Args:
x_long (bool): Whether the x-axis contains the length (largest dimension).
The y-axis will contain the other value (width).
Returns:
tuple: A tuple containing:
- size (tuple[int, int]): The (x, y) size of the brick.
- type (bool): True if it is a support, otherwise False.
"""
if x_long: return ((self.length, self.width), self.is_support)
else: return ((self.width, self.length), self.is_support)
def __str__(self):
if self.__is_support:
return f"Support Brick ({self.__length} x {self.__width} x {Brick.height})"
else:
return f"Brick ({self.__length} x {self.__width} x {Brick.height})"
[docs]
class BrickSet:
[docs]
def __init__(self, bricks:list[Brick]):
"""
Creates a set of unique bricks.
This class is an iterable, which iterates over all bricks, in all orientations
Args:
bricks (list[Brick]): A list of bricks
Raises:
TypeError: if bricks is not a list of Brick objects.
ValueError: if there are no (non-support) building bricks in the list
"""
# Verify bricks is a valid type
self.__verify_inputs(bricks)
# Use a set to store unique bricks
unique_bricks = set()
self.__building_bricks = []
self.__support_bricks = []
# Append unique bricks to the bricks property
for brick in bricks:
brick_tuple = (brick.length, brick.width, brick.is_support)
if brick_tuple not in unique_bricks:
unique_bricks.add(brick_tuple)
if brick.is_support:
self.__support_bricks.append(brick)
else:
self.__building_bricks.append(brick)
@property
def has_support(self) -> bool:
"""Whether or not the brickset includes support bricks"""
return len(self.__support_bricks) > 0
@property
def building_brick_orientations(self) -> list[tuple[int, int]]:
"""
A list of all of the possible orientations that all of the building bricks in the set can be in.
Returns:
list[tuple[int, int]]: The list of all possible orientations. These are tuples of x and y.
"""
orientations = []
for brick in self.__building_bricks:
for orientation in brick.orientations:
orientations.append(orientation)
return orientations
@property
def support_brick_orientations(self) -> list[tuple[int, int]]:
"""
A list of all of the possible orientations that all of the support bricks in the set can be in.
Returns:
list[tuple[int, int]]: The list of all possible orientations. These are tuples of x and y.
"""
orientations = []
for brick in self.__support_bricks:
for orientation in brick.orientations:
orientations.append(orientation)
return orientations
@property
def building_bricks(self) -> list[Brick]:
"""List of all the building bricks."""
return self.__building_bricks
@property
def support_bricks(self) -> list[Brick]:
"""List of all the support bricks."""
return self.__support_bricks
@property
def bricks(self) -> list[Brick]:
"""List of all the bricks in the set, including both building and support bricks."""
return self.__building_bricks + self.__support_bricks
[docs]
def get_building_bricks_by_dimension(self, dimension: int) -> set:
"""
Get a set of all dimension by x bricks, where the set contains all complementary x
Args:
dimension (int): the dimension the bricks must have
Returns:
set[int]: A set containing the other dimension of the bricks that satisfy the dimension, sorted from largest to smallest
"""
brick_set = set()
for orientation in self.building_brick_orientations:
if orientation[0] == dimension:
brick_set.add(orientation[1])
return sorted(brick_set, reverse=True)
[docs]
def get_building_brick_dimensions(self) -> set:
"""
Get a set of all dimensions available with the bricks.
Returns:
set[int]: A set containing all dimensions of the bricks, sorted from largest to smallest
"""
dimension_set = set()
for brick in self.building_brick_orientations:
for dimension in brick:
dimension_set.add(dimension)
return sorted(dimension_set, reverse=True)
[docs]
def get_support_bricks_by_dimension(self, dimension: int) -> set:
"""
Get a set of all dimension by x bricks, where the set contains all complementary x
Args:
dimension (int): the dimension the bricks must have
Returns:
set[int]: A set containing the other dimension of the bricks that satisfy the dimension, sorted from largest to smallest
"""
brick_set = set()
for orientation in self.support_brick_orientations:
if orientation[0] == dimension:
brick_set.add(orientation[1])
return sorted(brick_set, reverse=True)
[docs]
def get_support_brick_dimensions(self) -> set:
"""
Get a set of all dimensions available with the bricks.
Returns:
set[int]: A set containing all dimensions of the bricks, sorted from largest to smallest
"""
dimension_set = set()
for brick in self.support_brick_orientations:
for dimension in brick:
dimension_set.add(dimension)
return sorted(dimension_set, reverse=True)
def __verify_inputs(self, bricks):
if not isinstance(bricks, list):
raise TypeError("bricks must be a list of Brick objects.")
if not all(isinstance(brick, Brick) for brick in bricks):
raise TypeError("bricks must contain only Brick objects.")
for brick in bricks:
if not brick.is_support:
return
raise ValueError("There must be at leat one building brick, that is not a support brick.")
[docs]
def __iter__(self):
"""
yield each brick.
First yields all of the building bricks.
Then yields all of the support bricks.
"""
for brick in self.__building_bricks:
yield brick
for brick in self.__support_bricks:
yield brick
def __str__(self):
string = "Building bricks:"
for brick in self.__building_bricks:
string += "\n" + str(brick)
if self.has_support:
string += "\nSupport bricks:"
for brick in self.__support_bricks:
string += "\n" + str(brick)
return string