Source code for fibomat.curve_tools.biarc_approximation.monotone_intervals

from __future__ import annotations
from typing import Tuple, List, TYPE_CHECKING

import enum

import numpy as np

if TYPE_CHECKING:
    from fibomat.shapes.parametric_curve import ParametricCurve

from .utils import _make_perp_vector


@enum.unique
class _CircleRelations(enum.Enum):
    CIRCLE_1_IN_CIRCLE_2 = enum.auto()
    CIRCLE_2_IN_CIRCLE_1 = enum.auto()
    IDENTICAL = enum.auto()
    NO_INTERSECTION = enum.auto()
    OVERLAP = enum.auto()


@enum.unique
class _CurvatureState(enum.Enum):
    INCREASING = enum.auto()
    DECREASING = enum.auto()
    CONSTANT = enum.auto()
    INFINITE = enum.auto()

    UNDEFINED = enum.auto()

    # at begin of new segment
    NONE = enum.auto()


@enum.unique
class _FlushMode(enum.Enum):
    NO_FLUSH = enum.auto()
    FLUSH_WITH_TRANSITION = enum.auto()
    FLUSH_WITHOUT_TRANSITION = enum.auto()


[docs]@enum.unique class IntervalType(enum.Enum): LINE = enum.auto() ARC = enum.auto() BIARC = enum.auto() SPIRAL = enum.auto()
def _osculating_circle(param_curve: 'ParametricCurve', parameter: float): curvature = param_curve.curvature(parameter) radius = abs(1 / curvature) tangent = param_curve.df(parameter) tangent /= np.linalg.norm(tangent) t_perp = np.sign(curvature) * _make_perp_vector(tangent) center_osc_circle = param_curve.f(parameter) + radius * t_perp return center_osc_circle, radius def _circle_relation(circle_1: Tuple[np.ndarray, float], circle_2: Tuple[np.ndarray, float]): c_1, r_1 = circle_1 c_2, r_2 = circle_2 d = np.linalg.norm(c_1 - c_2) if np.allclose(c_1, c_2) and np.allclose(r_1, r_2): return _CircleRelations.IDENTICAL if r_1 >= d + r_2: return _CircleRelations.CIRCLE_2_IN_CIRCLE_1 elif r_2 >= d + r_1: return _CircleRelations.CIRCLE_1_IN_CIRCLE_2 elif d >= r_1 + r_2 + d: return _CircleRelations.NO_INTERSECTION else: return _CircleRelations.OVERLAP
[docs]def interval_type(n_points: int, curvature_state: _CurvatureState): if n_points < 2: raise RuntimeError elif n_points == 2: return IntervalType.BIARC else: if curvature_state == _CurvatureState.INFINITE: return IntervalType.LINE elif curvature_state == _CurvatureState.CONSTANT: return IntervalType.ARC elif curvature_state in (_CurvatureState.INCREASING, _CurvatureState.DECREASING): return IntervalType.SPIRAL else: raise RuntimeError
[docs]def intervals_by_osculating_circles( param_curve: ParametricCurve, parameter_values: np.ndarray, epsilon: float ) -> List[Tuple[IntervalType, np.ndarray]]: # TODO: INFINITE should be really ZERO_CURVATURE if len(parameter_values) == 0: raise RuntimeError prev_osculating_circle = None curvature_state = _CurvatureState.NONE intervals = [] current_interval = [] for i, param in enumerate(parameter_values): if np.allclose(param_curve.df(param), 0): if i == len(parameter_values) - 1: k = param_curve.curvature(param - epsilon) else: k = param_curve.curvature(param + epsilon) else: k = param_curve.curvature(param) flush = _FlushMode.NO_FLUSH if np.isclose(k, 0.): if curvature_state == _CurvatureState.NONE: current_interval.append(param) curvature_state = _CurvatureState.UNDEFINED elif curvature_state == _CurvatureState.UNDEFINED: current_interval.append(param) curvature_state = _CurvatureState.INFINITE elif curvature_state == _CurvatureState.INFINITE: current_interval.append(param) elif curvature_state == _CurvatureState.DECREASING: # finish current interval and start a new one but without a connecting biarc segment flush = _FlushMode.FLUSH_WITHOUT_TRANSITION else: flush = _FlushMode.FLUSH_WITH_TRANSITION else: osculating_circle = _osculating_circle(param_curve, param) if curvature_state == _CurvatureState.NONE: current_interval.append(param) curvature_state = _CurvatureState.UNDEFINED elif curvature_state == _CurvatureState.INFINITE: # this could be handled better most likely flush = _FlushMode.FLUSH_WITH_TRANSITION elif curvature_state == _CurvatureState.CONSTANT: relation = _circle_relation(osculating_circle, prev_osculating_circle) if relation == _CircleRelations.IDENTICAL: current_interval.append(param) else: flush = _FlushMode.FLUSH_WITH_TRANSITION elif curvature_state == _CurvatureState.UNDEFINED and prev_osculating_circle is None: # Can this happen at all? flush = _FlushMode.FLUSH_WITHOUT_TRANSITION elif curvature_state == _CurvatureState.UNDEFINED: current_interval.append(param) # determine curvature state relation = _circle_relation(osculating_circle, prev_osculating_circle) if relation == _CircleRelations.NO_INTERSECTION: raise RuntimeError( 'No intersection between osculating circles. Try to decrease the rasterization pitch.' ) elif relation == _CircleRelations.IDENTICAL: current_interval.append(param) curvature_state = _CurvatureState.CONSTANT # if np.isclose(osculating_circle[1], prev_osculating_circle[1]): # curvature_state = _CurvatureState.CONSTANT # else: # flush = _FlushMode.FLUSH_WITHOUT_TRANSITION else: if relation == _CircleRelations.CIRCLE_1_IN_CIRCLE_2: curvature_state = _CurvatureState.INCREASING else: curvature_state = _CurvatureState.DECREASING elif curvature_state in (_CurvatureState.INCREASING, _CurvatureState.DECREASING): relation = _circle_relation(osculating_circle, prev_osculating_circle) if relation == _CircleRelations.NO_INTERSECTION: raise RuntimeError( 'No intersection between osculating circles. Try to decrease the rasterization pitch.' ) elif curvature_state == _CurvatureState.INCREASING and relation == _CircleRelations.CIRCLE_1_IN_CIRCLE_2: current_interval.append(param) prev_osculating_circle = osculating_circle elif curvature_state == _CurvatureState.DECREASING and relation == _CircleRelations.CIRCLE_2_IN_CIRCLE_1: current_interval.append(param) prev_osculating_circle = osculating_circle else: # _CircleRelations.OVERLAP or something else => make a transition segment and new interval flush = _FlushMode.FLUSH_WITH_TRANSITION else: raise RuntimeError('Failed.') prev_osculating_circle = osculating_circle if flush == _FlushMode.FLUSH_WITH_TRANSITION: prev_osculating_circle = None intervals.append((interval_type(len(current_interval), curvature_state), np.array(current_interval))) intervals.append((IntervalType.BIARC, np.array([parameter_values[i-1], param]))) curvature_state = _CurvatureState.NONE prev_osculating_circle = None current_interval = [param] elif flush == _FlushMode.FLUSH_WITHOUT_TRANSITION: prev_osculating_circle = None current_interval.append(param) intervals.append((interval_type(len(current_interval), curvature_state), np.array(current_interval))) curvature_state = _CurvatureState.NONE prev_osculating_circle = None current_interval = [param] if flush == _FlushMode.NO_FLUSH: intervals.append((interval_type(len(current_interval), curvature_state), np.array(current_interval))) return intervals