"""Provides the :class:`Site` class."""
from __future__ import annotations
from typing import Optional, List, Union
import numpy as np
from fibomat.linalg import (
DimTransformable, DimVectorLike, DimVector, DimBoundingBox
)
from fibomat.raster_styles.rasterstyle import RasterStyle
from fibomat.mill import MillBase
from fibomat import layout
from fibomat.pattern import Pattern
from fibomat.shapes import DimShape
[docs]class Site(DimTransformable):
"""
The `Site` class is used to collect shapes with its patterning settings.
.. note:: All shape positions added to each site are interpreted relative to the site's position!
.. note:: If fov is not passed, it will be determined by the bounding box of the added patterns. But if a fixed FOV
is given, it is **not** checked if the added shapes fit inside the fov.
"""
[docs] def __init__(
self,
dim_center: DimVectorLike,
dim_fov: Optional[DimVectorLike] = None,
*,
description: Optional[str] = None
):
"""
Args:
dim_center (DimVectorLike): Center coordinate of the site.
dim_fov (DimVectorLike, optional): The fov (field of view) to be used. If not given, the fov will be
calculated automatically.
description (str, optional): description
"""
super().__init__(description=description)
self._center = DimVector(dim_center)
# TODO: check if fov is valid?
self._fov = DimVector(dim_fov) if dim_fov is not None else None
self._patterns: List[Pattern] = []
@property
def fov(self) -> DimVector:
"""Field-of-view of the site.
Access:
get
Returns:
DimVector
"""
if self._fov:
return self._fov
else:
bbox = self.bounding_box
return DimVector(bbox.width, bbox.height)
@property
def square_fov(self) -> DimVector:
"""Squared field-of-view of the site.
Access:
get
Returns:
DimVector
"""
fov = self.fov
size = fov.x if fov.x > fov.y else fov.y
return DimVector(size, size)
@property
def fov_bounding_box(self) -> DimBoundingBox:
"""Bounding box given by fov and center rather from the contained patterns.
Returns:
DimBoundingBox
"""
fov_x_2 = self._fov.x / 2
fov_y_2 = self._fov.y / 2
return DimBoundingBox(self._center - (fov_x_2, fov_x_2), self._center + (fov_x_2, fov_x_2))
@property
def empty(self) -> bool:
"""If True, site does not contain any shapes
Access:
get
Returns:
bool
"""
return not self._patterns
@property
def bounding_box(self) -> DimBoundingBox:
"""Bounding box of the added patterns.
Access:
get
Returns:
DimBoundingBox
"""
# bbox = DimBoundingBox(self._center, self._center)
if not self._patterns:
raise RuntimeError('Cannot calculate bounding box of empty site.')
bbox = self._patterns[0].bounding_box
for pattern in self._patterns[1:]:
bbox = bbox.extended(pattern.bounding_box)
return bbox
@property
def patterns(self):
"""Contained patterns in site
Access:
get
Returns:
List[Pattern]
"""
return self._patterns
@property
def patterns_absolute(self):
"""Return a list of all patterns contained in the side which are shifted by the side's center.
Hence, these patterns' positions are **not** relative to the site's center anymore but absolute to the
coordinate origin.
Access:
get
Returns:
List[Pattern]
"""
return [pattern.translated(self._center) for pattern in self._patterns]
[docs] def create_pattern(
self,
dim_shape: DimShape,
mill: MillBase,
raster_style: RasterStyle,
description: Optional[str] = None,
**kwargs
) -> Pattern:
"""Creates a pattern in-place (returned pattern is automatically added to the site).
The parameters are identical to the __init__method of the :class:`fibomat.pattern.Pattern` class.
Args:
dim_shape:
mill:
raster_style:
description:
**kwargs:
Returns:
Pattern
"""
pattern = Pattern(dim_shape, mill, raster_style, description=description, **kwargs)
self.add_pattern(pattern)
return pattern
[docs] def add_pattern(self, ptn: Union[Pattern, layout.LayoutBase]) -> None:
"""Adds a :class:`fibomat.pattern.Pattern` or Layoutbase[Pattern] to the site.
Args:
ptn (Pattern): new pattern
Returns:
None
"""
if isinstance(ptn, layout.LayoutBase):
for extracted_pattern in ptn.layout_elements():
self._patterns.append(extracted_pattern)
else:
self._patterns.append(ptn)
def __iadd__(self, ptn: Union[Pattern, layout.LayoutBase]) -> Site:
"""Adds a :class:`fibomat.pattern.Pattern` to the site.
Identical to :meth:`add_pattern`
Args:
ptn: new pattern
Returns:
None
"""
self.add_pattern(ptn)
return self
@property
def center(self) -> DimVector:
"""Center of the site.
Access:
get
Returns:
DimVector
"""
return self._center
def _impl_translate(self, trans_vec: DimVectorLike) -> None:
trans_vec = DimVector(trans_vec)
self._center += DimVector(trans_vec)
def _impl_rotate(self, theta: float) -> None:
if not np.isclose(np.mod(theta, np.pi/2), 0.):
raise ValueError('Sites can only be rotated by multiples of pi/2')
self._center = self._center.rotated(theta)
if not np.isclose(np.mod(theta, np.pi), 0.):
self._fov = DimVector(self._fov.y, self._fov.x)
for ptn in self._patterns:
ptn._impl_rotate(theta)
def _impl_scale(self, fac: float) -> None:
self._fov *= float(fac)
self._center *= float(fac)
for ptn in self._patterns:
ptn._impl_scale(fac)
def _impl_mirror(self, mirror_axis: DimVectorLike) -> None:
mirror_axis = DimVector(mirror_axis)
if not np.isclose(np.mod(mirror_axis.vector.angle_about_x_axis, np.pi/4), 0.):
raise ValueError(
'Sites can only be mirrored on the axes or their diagonals.'
)
self._center = self._center.mirrored(mirror_axis)
if not np.isclose(np.mod(mirror_axis.vector.angle_about_x_axis, np.pi), 0.):
self._fov = DimVector(self._fov.y, self._fov.x)
for ptn in self._patterns:
ptn._impl_mirror(mirror_axis)