Source code for fibomat.shapes.biarc

from typing import Optional, Union, List

import numpy as np

from fibomat.linalg import VectorLike, Vector, Transformable
from fibomat.linalg.transformables.transformable_base import VectorT, BBoxT
from fibomat.shapes.arc import Arc, ArcSpline, ArcSplineCompatible
from fibomat.shapes import Line


[docs]class Biarc(Transformable, ArcSplineCompatible): """ https://www.ryanjuckett.com/biarc-interpolation/ """ @classmethod def _make_biarc_seg(cls, start: Vector, end: Vector, tangent: Vector, reversed: bool) -> Union[Arc, Line, None]: """Create a arc or line from ``start`` to ``end`` and tangent ``tangent`` at start. Args: start (Vector): start end (Vector): tangent (Vector): tangent at start reversed (bool): if True, the arc is reversed Returns: Union[Arc, Line, None]: None is returned if start == end, Line is returned if (end - start) || tangent and Arc otherwise """ # https://www.ryanjuckett.com/biarc-interpolation/ tangent = tangent.normalized() normal = Vector(-tangent.y, tangent.x) end_to_start = end - start if np.allclose(end_to_start, 0.): # raise cls._ZeroArcAngle('arc angle ist zero.') return None u_denominator = 2 * normal.dot(end_to_start) if np.isclose(u_denominator, 0.): if reversed: return Line(end, start) else: return Line(start, end) u = end_to_start.dot(end_to_start) / u_denominator center = start + u * normal radius = abs(u) center_to_start = (start - center) / radius center_to_end = (end - center) / radius sweep = center_to_start.cross(center_to_end) delta_angle = np.sign(sweep) * np.arccos(center_to_start.dot(center_to_end)) start_angle = (start - center).angle_about_x_axis end_angle = start_angle + delta_angle if reversed: return Arc( radius=radius, start_angle=end_angle, end_angle=start_angle, sweep_dir=not(sweep > 0), center=center ) else: return Arc( radius=radius, start_angle=start_angle, end_angle=end_angle, sweep_dir=sweep > 0, center=center ) @classmethod def _make_biarc_segs( cls, p_1: Vector, p_2: Vector, t_1: Vector, t_2: Vector, d: float ) -> List[Union[Arc, Line, None]]: """Create arcs of biarc for given d value. See source for definition of ``d``. Args: p_1 (Vector): start of biarc p_2 (Vector): end of biarc t_1 (Vector): tangent at start t_2 (Vector): tangent at end d (float): d value Returns: Tuple[Arc, Arc] """ midpoint = (p_1 + p_2 + d * (t_1 - t_2)) / 2 seg_1 = cls._make_biarc_seg(p_1, midpoint, t_1, False) seg_2 = cls._make_biarc_seg(p_2, midpoint, t_2, True) return [seg for seg in [seg_1, seg_2] if seg]
[docs] def __init__( self, p_1: VectorLike, p_2: VectorLike, t_1: VectorLike, t_2: VectorLike, description: Optional[str] = None ): """Interpolate a biarc to given points and tangents. For interpolation details see references. References: - https://www.ryanjuckett.com/biarc-interpolation/ - https://ieeexplore.ieee.org/document/5390085 Args: p_1 (Vector): start of biarc p_2 (Vector): end of biarc t_1 (Vector): tangent at start t_2 (Vector): tangent at end description (str, optional): optional description """ super().__init__(description) p_1 = Vector(p_1) p_2 = Vector(p_2) t_1 = Vector(t_1).normalized() t_2 = Vector(t_2).normalized() t = t_1 + t_2 v = p_2 - p_1 if np.allclose(v, 0.): raise ValueError('p_1 == p_2. No biarc can be interpolated.') d_denominator = 2. * (1. - t_1.dot(t_2)) if np.isclose(d_denominator, 0.): if np.isclose(v.dot(t_2), 0.): # create to semicircles midpoint = p_1 + v / 2 c_1 = p_1 + .25 * v c_2 = p_1 + .75 * v sweep = v.cross(t_2) segments = [ Arc.from_points_center(p_1, midpoint, c_1, sweep < 0), Arc.from_points_center(midpoint, p_2, c_2, sweep > 0) ] else: d = v.dot(v) / (4. * v.dot(t_2)) segments = self._make_biarc_segs(p_1, p_2, t_1, t_2, d) else: d_discriminant = v.dot(t) * v.dot(t) + d_denominator * v.dot(v) d = (-v.dot(t) + np.sqrt(d_discriminant)) / d_denominator segments = self._make_biarc_segs(p_1, p_2, t_1, t_2, d) assert 1 <= len(segments) <= 2 self._biarc = ArcSpline.from_segments(segments)
@property def segments(self) -> List[Union[Arc, Line]]: return self._biarc.segments
[docs] def to_arc_spline(self) -> ArcSpline: return self._biarc
@property def center(self) -> VectorT: """Center is mean value of start of first segment and end of last segment (hence, it is compatible with ArSpline) Returns: """ return self._biarc.center @property def bounding_box(self) -> BBoxT: return self._biarc.bounding_box def _impl_translate(self, trans_vec: VectorT) -> None: self._biarc._impl_translate(trans_vec) def _impl_rotate(self, theta: float) -> None: self._biarc._impl_rotate(theta) def _impl_scale(self, fac: float) -> None: self._biarc._impl_scale(fac) def _impl_mirror(self, mirror_axis: VectorT) -> None: self._biarc._impl_mirror(mirror_axis)
# from fibomat import Sample, U_ # from fibomat.shapes import Spot # # s = Sample() # # # two arcs # # biarc = Biarc(Vector(0, 1), Vector(3, 0), Vector(-1, 0), Vector(0, 1)) # # # only one arc # # biarc = Biarc(Vector(-1, 0), Vector(1, 0), Vector(1, -1), Vector(-1, -1)) # # # line + arc # # biarc = Biarc(Vector(0, 0), Vector(3, -1), Vector(1, 0), Vector(0, -1)) # # # line + line # # biarc = Biarc(Vector(0, 0), Vector(3, 0), Vector(1, 0), Vector(1, 0)) # # # two arcs (degenerate case, two semicircles) # # biarc = Biarc(Vector(0, 1), Vector(0, -1), Vector(1, 0), Vector(1, 0)) # # # two arcs (degenerate case, but no semicircles) # biarc = Biarc(Vector(0, 1), Vector(1, -1), Vector(1, 0), Vector(1, 0)) # # # # print() # # # # arc = arc_center_radius_from_start_end_tangent(Vector(100, 100), Vector(200, 0), Vector(-1, 0)) # # print(arc) # # # # s.add_annotation(arc * U_('µm')) # # for a in biarc.segments: # print(a.start, a.end) # s.add_annotation(a * U_('µm')) # # # s.add_annotation(Arc.from(Vector(100, 100), Vector(200, 0), Vector(-1, 0)) * U_('µm')) # # s.plot()