Metadata-Version: 2.4
Name: bezier-manager
Version: 0.3.0
Summary: A comprehensive 2D Bézier curve management library with constraints and advanced point handling
Author-email: Grayjou <your.email@example.com>
License: MIT
Project-URL: Homepage, https://github.com/Grayjou/BezierManager
Project-URL: Repository, https://github.com/Grayjou/BezierManager
Project-URL: Issues, https://github.com/Grayjou/BezierManager/issues
Keywords: bezier,curves,2d,graphics,constraints,geometry
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Scientific/Engineering :: Mathematics
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.20.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Requires-Dist: mypy>=1.0.0; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Provides-Extra: test
Requires-Dist: pytest>=7.0.0; extra == "test"
Requires-Dist: pytest-cov>=4.0.0; extra == "test"

# BezierManager

A comprehensive Python library for managing 2D Bézier curves with advanced constraint handling, efficient point management, and curve generation capabilities.

## Features

### Core Functionality

- **Bézier Point Management**: Create, update, and manage 2D Bézier control points with handles
- **Multiple Keeper Types**:
  - `Point2DKeeper`: Basic point management with constraint support
  - `XSortedPoint2DKeeper`: Maintains points sorted by x-coordinate
  - `UniqueXPoint2DKeeper`: Enforces unique x-coordinates with efficient binary search
  - `MonoHandleKeeper`: Enforces mono-handle mode for smooth curves
  - `MonotonousSegmentKeeper`: Enforces monotonous Y-coordinates in segments
- **Constraint System**:
  - Cartesian constraints (fixed coordinates, axis limits)
  - Polar constraints (fixed radius/angle, range limits)
  - Custom center points for polar constraints
  - Multiple constraints per point
  - **Constraint locking**: Lock constraints to prevent removal
  - **Constraint IDs**: Track and manage constraints by unique IDs
- **Bounds Support**:
  - Global point bounds (bounds_x, bounds_y) for canvas-like behavior
  - **Handle bounds**: Optional bounds for handle positions (handle_bounds_x, handle_bounds_y)
  - Independent control of point and handle bounds
- **Batch Operations**: Efficient bulk point addition and removal
- **Curve Generation**: Render smooth Bézier curves from control points

### Advanced Features

- **Efficient X-Coordinate Tracking**: O(log n) lookups using binary search
- **Handle Modes**: Support for FREE, MIRRORED, and ALIGNED handle modes
- **Point Reordering**: Relocate points in sequences while maintaining integrity
- **Range Queries**: Find points within x-coordinate ranges
- **Protected Method Architecture**: Extensible design with protected methods for subclass customization

## Installation

```bash
pip install bezier-manager
```

Or install from source:

```bash
git clone https://github.com/Grayjou/BezierManager.git
cd BezierManager
pip install -e .
```

## Quick Start

### Basic Usage

```python
from bezier.two_d.keeper import Point2DKeeper

# Create a keeper with global bounds (like a canvas)
keeper = Point2DKeeper(bounds_x=(0, 800), bounds_y=(0, 600))

# Add control points
p1 = keeper.add_point(
    location=(100, 200),
    left_handle=(-30, 0),
    right_handle=(30, 0)
)

p2 = keeper.add_point(
    location=(400, 300),
    left_handle=(-50, 20),
    right_handle=(50, -20)
)

# Move a point (respects bounds)
keeper.move_point(p1, (150, 250))

# Get all points
points = keeper.get_all_points()
```

### Using Constraints

```python
from bezier.two_d.keeper import Point2DKeeper
from bezier.two_d.constraints import CartesianConstraint2D, PolarConstraint2D

keeper = Point2DKeeper()

# Add a point
pid = keeper.add_point(location=(100, 100), left_handle=(0, 0), right_handle=(0, 0))

# Add Cartesian constraint (limit x to range)
constraint = CartesianConstraint2D(x_limits=(50, 150))
cid = keeper.add_constraint(pid, constraint)  # Returns constraint ID

# Add polar constraint (fixed radius from center)
polar = PolarConstraint2D(fixed_radius=100.0, center=(200, 200))
polar_cid = keeper.add_constraint(pid, polar)

# Lock a constraint to prevent removal
keeper.lock_constraint(cid)

# Try to remove - will be silently ignored because it's locked
keeper.remove_constraint(pid, constraint)

# Unlock to allow removal
keeper.unlock_constraint(cid)
keeper.remove_constraint(pid, constraint)  # Now it can be removed

# Move point - constraints are automatically applied
keeper.move_point(pid, (80, 120))
```

### Handle Bounds

```python
from bezier.two_d.keeper import Point2DKeeper
from bezier.two_d.point import HandleCoords

# Create keeper with bounds for both points and handles
keeper = Point2DKeeper(
    bounds_x=(0, 800),        # Point positions limited to 0-800
    bounds_y=(0, 600),        # Point positions limited to 0-600
    handle_bounds_x=(-100, 900),  # Handles can extend slightly beyond
    handle_bounds_y=(-100, 700)
)

# Add point with handles (handles are relative by default)
pid = keeper.add_point(
    location=(400, 300),
    left_handle=(-50, -30),   # Relative to point location
    right_handle=(50, 30),
    handle_coords=HandleCoords.RELATIVE  # Explicit (this is the default)
)

# Update handle with absolute coordinates
keeper.update_right_handle(
    pid,
    (500, 350),  # Absolute position
    handle_coords=HandleCoords.ABSOLUTE
)

# Handles exceeding bounds will be clamped automatically
```

### X-Sorted Points

```python
from bezier.two_d.x_sorted_keeper import XSortedPoint2DKeeper

# Points are automatically sorted by x-coordinate
keeper = XSortedPoint2DKeeper(bounds_x=(0, 1000), bounds_y=(0, 1000))

keeper.add_point((300, 100), (0, 0), (0, 0))
keeper.add_point((100, 200), (0, 0), (0, 0))  # Automatically inserted before first point
keeper.add_point((500, 150), (0, 0), (0, 0))  # Automatically inserted at end

# Points are always sorted by x
points = keeper.get_all_points()
assert points[0].location[0] < points[1].location[0] < points[2].location[0]

# Query points in x range
points_in_range = keeper.get_points_in_x_range(150, 400)
```

### Unique X-Coordinates

```python
from bezier.two_d.unique_x_keeper import UniqueXPoint2DKeeper

# Enforces unique x-coordinates
keeper = UniqueXPoint2DKeeper(tolerance=1e-9)

keeper.add_point((100, 100), (0, 0), (0, 0))
keeper.add_point((100, 200), (0, 0), (0, 0))  # X automatically adjusted

points = keeper.get_all_points()
# Second point's x will be slightly different (100 + tolerance * 2)
```

### Generating Curves

```python
from bezier.two_d.bezier_gen import Bezier2DGenerator
from bezier.two_d.point import BezierPoint2D

# Create control points
p0 = BezierPoint2D(location=(0, 0), left_handle=(0, 0), right_handle=(1, 0))
p1 = BezierPoint2D(location=(3, 3), left_handle=(-1, 0), right_handle=(1, 0))
p2 = BezierPoint2D(location=(6, 0), left_handle=(-1, 0), right_handle=(0, 0))

points = [p0, p1, p2]

# Generate smooth curve
generator = Bezier2DGenerator()
curve_points = generator.render_bezier_curve(points, samples_per_segment=20)

# curve_points is a list of (x, y) tuples along the curve
for x, y in curve_points:
    print(f"Point: ({x}, {y})")
```

### Batch Operations

```python
from bezier.two_d.keeper import Point2DKeeper

keeper = Point2DKeeper()

# Add multiple points efficiently
points_data = [
    ((100, 100), (-10, 0), (10, 0)),
    ((200, 150), (-15, 5), (15, -5)),
    ((300, 120), (-12, 3), (12, -3)),
]
point_ids = keeper.add_points_batch(points_data)

# Remove multiple points efficiently
keeper.remove_points_batch([point_ids[0], point_ids[2]])
```

## API Reference

### Point2DKeeper

Main class for managing Bézier points with constraints.

#### Constructor
```python
Point2DKeeper(
    bounds_x=None, 
    bounds_y=None,
    handle_bounds_x=None,
    handle_bounds_y=None,
    samples_per_segment=20,
    generator=None
)
```
- `bounds_x`: Optional tuple `(min_x, max_x)` for global x-axis bounds (point positions)
- `bounds_y`: Optional tuple `(min_y, max_y)` for global y-axis bounds (point positions)
- `handle_bounds_x`: Optional tuple `(min_x, max_x)` for handle x-axis bounds (absolute handle positions)
- `handle_bounds_y`: Optional tuple `(min_y, max_y)` for handle y-axis bounds (absolute handle positions)
- `samples_per_segment`: Number of samples per curve segment for rendering
- `generator`: Optional custom Bezier2DGenerator instance

#### Methods
- `add_point(location, left_handle, right_handle, handle_mode=None, handle_coords=HandleCoords.RELATIVE)`: Add a new point
  - Note: `left_handle` and `right_handle` are relative to point location by default
- `remove_point(point_id)`: Remove a point
- `move_point(point_id, tentative_location)`: Move a point (applies constraints and bounds)
- `get_point(point_id)`: Get point data
- `get_all_points()`: Get all points in order
- `add_constraint(point_id, constraint)`: Add a constraint to a point, returns constraint ID
- `remove_constraint(point_id, constraint)`: Remove an unlocked constraint
- `lock_constraint(constraint_id)`: Lock a constraint to prevent removal
- `unlock_constraint(constraint_id)`: Unlock a constraint to allow removal
- `is_constraint_locked(constraint_id)`: Check if a constraint is locked
- `get_constraint_by_id(constraint_id)`: Get a constraint by its ID
- `get_constraint_point_id(constraint_id)`: Get the point ID that a constraint is bound to
- `set_bounds(bounds_x, bounds_y)`: Update global point bounds
- `get_bounds()`: Get current point bounds
- `set_handle_bounds(handle_bounds_x, handle_bounds_y)`: Update global handle bounds
- `get_handle_bounds()`: Get current handle bounds
- `is_within_bounds(x, y)`: Check if coordinates are within point bounds
- `update_left_handle(point_id, left_handle, handle_coords=HandleCoords.RELATIVE)`: Update left handle
- `update_right_handle(point_id, right_handle, handle_coords=HandleCoords.RELATIVE)`: Update right handle
- `update_handles(point_id, left_handle, right_handle, handle_coords=HandleCoords.RELATIVE)`: Update both handles
- `add_points_batch(points_data)`: Add multiple points efficiently
- `remove_points_batch(point_ids)`: Remove multiple points efficiently

### Constraints

#### CartesianConstraint2D
```python
CartesianConstraint2D(
    fixed_x=None,
    fixed_y=None,
    x_limits=None,
    y_limits=None,
    active=True
)
```

#### PolarConstraint2D
```python
PolarConstraint2D(
    fixed_radius=None,
    fixed_angle=None,
    radius_limits=None,
    angle_limits=None,
    center=None,
    active=True
)
```

### BezierPoint2D

Data class representing a Bézier control point.

#### Attributes
- `location`: Tuple[float, float] - The point's (x, y) position
- `left_handle`: Tuple[float, float] - Relative position of left handle
- `right_handle`: Tuple[float, float] - Relative position of right handle
- `handle_mode`: HandleMode - FREE, MIRRORED, or ALIGNED

## Development

### Running Tests

```bash
# Install dev dependencies
pip install -e ".[dev]"

# Run all tests
pytest

# Run with coverage
pytest --cov=bezier --cov-report=html

# Run specific test file
pytest tests/test_cartesian_constraints.py
```

### Type Checking

```bash
mypy bezier
```

### Code Quality

```bash
# Run linter
ruff check bezier

# Format code
ruff format bezier
```

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

This project is licensed under the MIT License - see the LICENSE file for details.

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for a list of changes in each version.

## Acknowledgments

- Inspired by Bézier curve editing in graphics applications
- Built with efficiency and scalability in mind
- Uses NumPy for efficient curve generation
