Source code for fibomat.shapes.hollow_arc_spline
from typing import Optional, Sequence, List
from collections import deque
import numpy as np
from fibomat.linalg import VectorLike, BoundingBox, Vector
from fibomat.shapes.shape import Shape
from fibomat.shapes.arc_spline import ArcSpline
from fibomat.curve_tools import combine_curves
[docs]class HollowArcSpline(Shape):
[docs] def __init__(
self,
boundary: ArcSpline,
holes: Optional[Sequence[ArcSpline]] = None,
description: Optional[str] = None,
disable_checks=False
):
super().__init__(description)
if not isinstance(boundary, ArcSpline):
raise TypeError('boundary must be an ArcSplines.')
if not all(isinstance(hole, ArcSpline) for hole in holes):
raise TypeError('holes must be ArcSplines.')
if not boundary.is_closed:
raise ValueError('boundary must be closed arc spline.')
if not holes:
holes = []
if not all(hole.is_closed for hole in holes):
raise ValueError('holes must be closed arc splines')
if disable_checks:
self._boundary, self._holes = boundary, holes
else:
self._boundary, self._holes = self._exclude_holes(boundary, self._merge_holes(holes))
@staticmethod
def _exclude_holes(boundary: ArcSpline, holes: Sequence[ArcSpline]):
queue = deque(holes)
non_intersecting_holes = []
while queue:
hole = queue.popleft()
# if not combine_curves(boundary, hole, mode='intersect'):
# raise RuntimeError('Hole is not included in boundary.')
excluded = combine_curves(boundary, hole, mode='exclude')
if not excluded['remaining']:
print(boundary.bounding_box, [hole.bounding_box for hole in holes], excluded)
# something strange happened
raise RuntimeError
if len(excluded['remaining']) == 1:
if len(excluded['remaining'][0].vertices) == len(boundary.vertices) and np.allclose(excluded['remaining'][0].vertices, boundary.vertices):
non_intersecting_holes.append(hole)
else:
boundary = excluded['remaining'][0]
if len(excluded['remaining']) > 1:
raise RuntimeError(
'Shape is not simply connected.'
'This is most likely caused by a hole cutting the shape in two or more pieces.'
)
return boundary, non_intersecting_holes
@staticmethod
def _merge_holes(holes: Sequence[ArcSpline]):
if holes:
queue = deque(holes)
non_intersection_holes = []
while queue:
hole = queue.popleft()
non_intersecting = True
for i in range(len(queue)):
if hole.bounding_box.overlaps_with(queue[i].bounding_box):
union = combine_curves(hole, queue[i], mode='union')
if union['subtracted']:
raise RuntimeError(
'Shape is not simply connected. '
'This is most likely caused by holes which separate the shape in two or more parts.'
)
if len(union['remaining']) == 1:
del queue[i]
queue.append(union['remaining'][0])
non_intersecting = False
break
if non_intersecting:
non_intersection_holes.append(hole)
return non_intersection_holes
else:
return []
@property
def boundary(self) -> ArcSpline:
return self._boundary
@property
def holes(self) -> List[ArcSpline]:
return self._holes
def __repr__(self) -> str:
return self.__class__.__name__
@property
def is_closed(self) -> bool:
return True
[docs] def contains(self, pos: VectorLike) -> bool:
"""Returns True if pos is contained in shape
Args:
pos (VectorLike): point to be tested
Returns:
bool
"""
return self._boundary.contains(pos) and not any([hole.contains(pos) for hole in self._holes])
@property
def center(self) -> Vector:
return self._boundary.center
@property
def bounding_box(self) -> BoundingBox:
return self._boundary.bounding_box
def _impl_translate(self, trans_vec: VectorLike) -> None:
self._boundary._impl_translate(trans_vec)
for hole in self._holes:
hole._impl_translate(trans_vec)
def _impl_rotate(self, theta: float) -> None:
self._boundary._impl_rotate(theta)
for hole in self._holes:
hole._impl_rotate(theta)
def _impl_scale(self, fac: float) -> None:
self._boundary._impl_scale(fac)
for hole in self._holes:
hole._impl_scale(fac)
def _impl_mirror(self, mirror_axis: VectorLike) -> None:
self._boundary._impl_mirror(mirror_axis)
for hole in self._holes:
hole._impl_mirror(mirror_axis)