A comprehensive 2D Bézier curve management library with constraints and advanced point handling
Project description
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 supportXSortedPoint2DKeeper: Maintains points sorted by x-coordinateUniqueXPoint2DKeeper: Enforces unique x-coordinates with efficient binary searchMonoHandleKeeper: Enforces mono-handle mode for smooth curvesMonotonousSegmentKeeper: 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
pip install bezier-manager
Or install from source:
git clone https://github.com/Grayjou/BezierManager.git
cd BezierManager
pip install -e .
Quick Start
Basic Usage
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
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
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
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
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
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
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
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 renderinggenerator: 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_handleandright_handleare relative to point location by default
- Note:
remove_point(point_id): Remove a pointmove_point(point_id, tentative_location): Move a point (applies constraints and bounds)get_point(point_id): Get point dataget_all_points(): Get all points in orderadd_constraint(point_id, constraint): Add a constraint to a point, returns constraint IDremove_constraint(point_id, constraint): Remove an unlocked constraintlock_constraint(constraint_id): Lock a constraint to prevent removalunlock_constraint(constraint_id): Unlock a constraint to allow removalis_constraint_locked(constraint_id): Check if a constraint is lockedget_constraint_by_id(constraint_id): Get a constraint by its IDget_constraint_point_id(constraint_id): Get the point ID that a constraint is bound toset_bounds(bounds_x, bounds_y): Update global point boundsget_bounds(): Get current point boundsset_handle_bounds(handle_bounds_x, handle_bounds_y): Update global handle boundsget_handle_bounds(): Get current handle boundsis_within_bounds(x, y): Check if coordinates are within point boundsupdate_left_handle(point_id, left_handle, handle_coords=HandleCoords.RELATIVE): Update left handleupdate_right_handle(point_id, right_handle, handle_coords=HandleCoords.RELATIVE): Update right handleupdate_handles(point_id, left_handle, right_handle, handle_coords=HandleCoords.RELATIVE): Update both handlesadd_points_batch(points_data): Add multiple points efficientlyremove_points_batch(point_ids): Remove multiple points efficiently
Constraints
CartesianConstraint2D
CartesianConstraint2D(
fixed_x=None,
fixed_y=None,
x_limits=None,
y_limits=None,
active=True
)
PolarConstraint2D
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) positionleft_handle: Tuple[float, float] - Relative position of left handleright_handle: Tuple[float, float] - Relative position of right handlehandle_mode: HandleMode - FREE, MIRRORED, or ALIGNED
Development
Running Tests
# 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
mypy bezier
Code Quality
# Run linter
ruff check bezier
# Format code
ruff format bezier
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Changelog
See 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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bezier_manager-0.3.0.tar.gz.
File metadata
- Download URL: bezier_manager-0.3.0.tar.gz
- Upload date:
- Size: 52.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ec670160d673d08b03aeb6345629ff4d4ca7f6b1adec4cb73f3877f7b26f3f2
|
|
| MD5 |
4e674e58807b9d1f15a954153c8ec0d2
|
|
| BLAKE2b-256 |
52218755e9adaaf5052ef2223589f44161d2b0bc213848b01f42ad1b9569da1f
|
File details
Details for the file bezier_manager-0.3.0-py3-none-any.whl.
File metadata
- Download URL: bezier_manager-0.3.0-py3-none-any.whl
- Upload date:
- Size: 35.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
80fedbf8165fd6997ccac9b9f14d3ed7c27cfa4c6b41554a0ae5a39944c2e5d5
|
|
| MD5 |
8e4950ce03d123658a11937c3b83d83d
|
|
| BLAKE2b-256 |
5cd6bb4eb072ed2812575b623888bd33132a440d08354a9d825268c22e19cc14
|