Source code for shapepy.geometry.jordancurve

"""
This modules contains a powerful class called JordanCurve which
is in fact, stores a list of spline-curves.
"""

from __future__ import annotations

from collections import deque
from typing import Iterable, Iterator, List, Union

from ..analytic import IAnalytic
from ..loggers import debug, get_logger
from ..scalar.reals import Real
from ..tools import CyclicContainer, Is, pairs, reverse
from .base import Future
from .point import Point2D, cross
from .segment import Segment
from .unparam import UPiecewiseCurve, USegment, self_intersect


[docs] class JordanCurve(UPiecewiseCurve): """ Jordan Curve is an arbitrary closed curve which doesn't intersect itself. It stores a list of 'segments', each segment is a bezier curve """ def __init__(self, usegments: Iterable[Union[Segment, USegment]]): super().__init__(clean_jordan(usegments)) for usegi in self: if self_intersect(usegi): raise ValueError(f"Segment self-intersect! {usegi}") self.__area = None @property def area(self) -> Real: """The internal area""" if self.__area is None: self.__area = compute_area(self) return self.__area
[docs] def vertices(self) -> Iterator[Point2D]: """Vertices Returns in order, all the non-repeted control points from jordan curve's segments :getter: Returns a tuple of :type: Tuple[Point2D] Example use ----------- >>> from shapepy import JordanCurve >>> vertices = [(0, 0), (4, 0), (0, 3)] >>> jordan = FactoryJordan.polygon(vertices) >>> print(jordan.vertices) ((0, 0), (4, 0), (0, 3)) """ for useg in self: seg = useg.parametrize() yield seg.eval(seg.knots[0])
def __str__(self) -> str: msg = f"Jordan Curve with {len(self)} segments and vertices\n" msg += str(self.vertices()) return msg def __repr__(self) -> str: box = self.box() return f"JC[{len(self)}:{box.lowpt},{box.toppt}]" def __eq__(self, other: JordanCurve) -> bool: logger = get_logger("shapepy.geometry.jordancurve") logger.debug(f" type: {type(other)}") logger.debug(f" box: {self.box() == other.box()}") logger.debug(f" len: {self.length == other.length}") logger.debug(f" area: {self.area == other.area}") logger.debug( f" all1: {all(point in self for point in other.vertices())}" ) logger.debug( f" all2: {all(point in other for point in self.vertices())}" ) logger.debug( f" cycl: {CyclicContainer(self) == CyclicContainer(other)}" ) return ( Is.instance(other, JordanCurve) and self.box() == other.box() and self.length == other.length and self.area == other.area and all(point in self for point in other.vertices()) and all(point in other for point in self.vertices()) and CyclicContainer(self) == CyclicContainer(other) ) def __invert__(self) -> JordanCurve: return JordanCurve(reverse(~useg for useg in self))
@debug("shapepy.geometry.jordancurve") def compute_area(jordan: JordanCurve) -> Real: """ Computes the area inside of the jordan curve If jordan is clockwise, then the area is negative """ total = 0 for usegment in jordan: segment = usegment.parametrize() xfunc = segment.xfunc yfunc = segment.yfunc poly = xfunc * yfunc.derivate() poly -= yfunc * xfunc.derivate() assert Is.instance(poly, IAnalytic) total += poly.integrate(segment.domain) return total / 2 @debug("shapepy.geometry.jordan") def clean_jordan( usegments: Iterable[Union[Segment, USegment]], ) -> Iterator[Union[Segment, USegment]]: """Cleans the jordan curve Removes the uncessary nodes from jordan curve :return: The set of segments :rtype: A set of segments Example use ----------- >>> from shapepy import JordanCurve >>> vertices = [(0, 0), (1, 0), (4, 0), (0, 3)] >>> jordan = FactoryJordan.polygon(vertices) >>> jordan.clean() Jordan Curve of degree 1 and vertices ((0, 0), (4, 0), (0, 3)) """ logger = get_logger("shapepy.geometry.jordan") logger.debug("Segments = ") usegments = deque(usegments) endvectors = [] for i, usegment in enumerate(usegments): if not Is.instance(usegment, (Segment, USegment)): raise ValueError segment = usegment.parametrize() logger.debug(f" {i}: {segment}") vectora = segment.eval(segment.knots[0], 1) vectorb = segment.eval(segment.knots[-1], 1) endvectors.append((vectora, vectorb)) crosses: List[Real] = [] for (_, vectb), (vecta, _) in pairs(endvectors, cyclic=True): crosses.append(cross(vectb, vecta)) logger.debug(f"End vectors = {endvectors}") logger.debug(f"Crosses = {crosses}") if not any(c == 0 for c in crosses): return usegments index = len(crosses) - 1 while index > 0 and crosses[index] == 0: usegments.rotate() index -= 1 usegments = Future.concatenate(usegments) return usegments