# geo/primitives_2d/curve/base.py"""Defines a base class for 2D curves in a parametric form C(t), typically with t ∈ [0, 1]."""fromabcimportABC,abstractmethodfromtypingimportList,Sequence,Iteratorfromgeo.coreimportPoint2D,Vector2Dfromgeo.core.precisionimportis_equal
[docs]classCurve2D(ABC):""" Abstract base class for a 2D parametric curve. A curve is typically defined by C(t), where t is a parameter in [0, 1]. """def__init__(self,control_points:Sequence[Point2D]):ifnotcontrol_points:raiseValueError("A curve must have at least one control point.")fori,ptinenumerate(control_points):ifnotisinstance(pt,Point2D):raiseTypeError(f"Expected Point2D at index {i}, got {type(pt).__name__}.")self.control_points:tuple[Point2D,...]=tuple(control_points)
[docs]@abstractmethoddefpoint_at(self,t:float)->Point2D:""" Calculates the point on the curve at parameter t. Args: t (float): The parameter (typically in the range [0, 1]). Returns: Point2D: The point on the curve. """pass
[docs]@abstractmethoddeftangent_at(self,t:float)->Vector2D:""" Calculates the (non-normalized) tangent vector to the curve at parameter t. If the tangent is undefined (e.g., cusp), a zero vector or exception may be returned/raised. Args: t (float): The parameter. Returns: Vector2D: The tangent vector. """pass
[docs]defderivative_at(self,t:float)->Vector2D:""" Alias for tangent_at, representing the first derivative C'(t). """returnself.tangent_at(t)
[docs]deflength(self,t0:float=0.0,t1:float=1.0,num_segments:int=100)->float:""" Approximates the curve length from t0 to t1 using numerical integration. Args: t0 (float): Starting parameter. t1 (float): Ending parameter. num_segments (int): Number of segments for numerical approximation. Returns: float: Approximate curve length. Raises: ValueError: If num_segments is not positive. """ifis_equal(t0,t1):return0.0ifnum_segments<=0:raiseValueError("Number of segments must be positive for length calculation.")# Ensure t0 <= t1ift0>t1:t0,t1=t1,t0total_length=0.0dt=(t1-t0)/num_segmentsprev_point=self.point_at(t0)foriinrange(1,num_segments+1):current_t=t0+i*dtcurrent_point=self.point_at(current_t)total_length+=prev_point.distance_to(current_point)prev_point=current_pointreturntotal_length
def__len__(self)->int:""" Returns the number of control points. """returnlen(self.control_points)def__iter__(self)->Iterator[Point2D]:""" Allows iteration over control points. """returniter(self.control_points)def__repr__(self)->str:returnf"{self.__class__.__name__}(control_points={self.control_points})"