Skip to main content

bezierkit

bezierkit

PyPI Python License Tests

A small mathematical toolkit for constructing and analyzing Bézier curves.

bezierkit is renderer-independent: it provides curve construction, evaluation, subdivision, and sampling while leaving plotting to consumers such as EconViz, Matplotlib, Plotly, SVG, or Typst adapters.

Installation

Add the library to your project with uv:

uv add bezierkit

Add the optional command-line interface:

uv add "bezierkit[cli]"

For development, clone the repo and sync all extras:

uv sync --all-extras --dev

Run anything inside the environment with uv run, e.g. uv run pytest or uv run bezierkit --help. A plain pip install bezierkit / pip install "bezierkit[cli]" also works if you are not using uv.

Python API

from bezierkit import BezierCurve, CubicBezierSegment, PiecewiseBezier, Point
from bezierkit.construction import PlanarSlopes
from bezierkit.sampling import UniformSampler

curve = BezierCurve.cubic(
    Point(0, 0),
    Point(1, 2),
    Point(3, 2),
    Point(4, 0),
)

segment = CubicBezierSegment(
    Point(0, 0),
    Point(1, 2),
    Point(3, 2),
    Point(4, 0),
)
segment.control_points        # (P0, P1, P2, P3)

path = PiecewiseBezier([
    CubicBezierSegment.from_line(Point(0, 0), Point(2, 0)),
    CubicBezierSegment.from_line(Point(2, 0), Point(2, 4)),
])
path.at(0.75)                 # Point(2.0, 2.0)

curve.at(0.5)                 # Point(2.0, 1.5)
curve.derivative().at(0.5)    # first derivative
left, right = curve.split(0.3)
sample = UniformSampler(200).sample(curve)

demand = PlanarSlopes(
    start=Point(0, 5),
    end=Point(5, 0),
    start_slope=-2,
    end_slope=-0.3,
).build()

The engine supports arbitrary degree and dimension. Point, Vector, PointSet, control polygons, and sampling results are immutable value objects.

CubicBezierSegment is the primary renderer-facing value object. It exposes p0, p1, p2, p3, tight axis-aligned bounds, and cubic-preserving split and reversal operations. Existing BezierCurve.cubic(...) code remains valid; use CubicBezierSegment.from_curve(curve) to migrate without changing any control point, or segment.as_curve() when a degree-generic API is required.

PiecewiseBezier divides [0, 1] uniformly across the segments of a single subpath. Closure is metadata and never inserts a hidden closing segment. PiecewiseBezier.compound(...) preserves independent subpaths for fills and holes; evaluate and split each subpath independently because a compound path has no single continuous parameterization. Reversal preserves each subpath; splitting is defined only for a single open subpath.

Linear and quadratic inputs can be elevated without changing their geometry:

from bezierkit.bezier import to_cubic

cubic = to_cubic(BezierCurve.quadratic(
    Point(0, 0), Point(3, 6), Point(9, 0)
))

Batch evaluation uses a vectorized de Casteljau implementation by default. BernsteinEvaluator is also available from bezierkit.bezier.evaluation for explicit low-degree throughput tradeoffs; see benchmarks/ for the reproducible 400, 10,000, and 100,000-value benchmark.

Interpolation, fitting, and level sets

from bezierkit.fitting import fit_graph
from bezierkit.implicit import trace_implicit
from bezierkit.interpolation import graph_hermite

segment = graph_hermite(
    x0=1, x1=1.5,
    y0=4, y1=8 / 3,
    m0=-8, m1=-32 / 9,
)

path = fit_graph(
    lambda x: 4 / x**2,
    lambda x: -8 / x**3,
    x0=0.8, x1=3,
    tolerance=1e-3,
)

contours = trace_implicit(
    lambda x, y: x**2 * y,
    levels=[1, 2, 4],
    viewport=(0.5, 4, 0, 6),
    resolution=(121, 121),
    tolerance=0.01,
    gradient=lambda x, y: (2 * x * y, x**2),
)

Adaptive fitting reports a measured Euclidean error on every segment and raises ToleranceNotMet if configured limits prevent the requested tolerance. Implicit tracing preserves disconnected and closed components as independent paths and uses parametric gradient tangents, including at vertical tangencies. The derivations, error bound, continuity conditions, and sampling limitations are documented in docs/math/interpolation.md.

Exporters and adapters

from bezierkit.export.json import dumps, loads
from bezierkit.export.svg import to_svg_path_data
from bezierkit.export.tikz import to_tikz

json_document = dumps(path, metadata={"name": "indifference-curve"})
same_path = loads(json_document).path
svg_path_data = to_svg_path_data(path, precision=5)
tikz = to_tikz(path, precision=5, options="thick")

TikZ uses native .. controls ... and ... .. commands and SVG uses native C commands; neither exporter flattens cubic geometry or chooses a theme. The versioned JSON schema preserves all controls, subpaths, closure, dimension, and caller metadata.

Matplotlib interoperability is optional:

uv add "bezierkit[matplotlib]"
from bezierkit.adapters.matplotlib import from_path, to_path

geometry = from_path(matplotlib_path, transform=affine_transform)
round_trip = to_path(geometry)

Affine transforms preserve controls exactly. Non-affine transforms require the explicit approximate_path(..., tolerance=...) API. Format guarantees and round-trip tolerances are documented in docs/exporters.md.

Command-line interface

Run bezierkit directly if it's installed in your active environment, or prefix every command with uv run (e.g. uv run bezierkit --help) when working inside a uv project without activating the venv.

Evaluate a curve or derivative:

bezierkit evaluate \
  --points "0,0" --points "1,2" --points "3,2" --points "4,0" \
  --t 0:1:0.25 --order 1 --json

Sample as CSV:

bezierkit sample \
  --points "0,0" --points "1,2" --points "3,2" --points "4,0" \
  --count 50 --format csv > curve.csv

Construct a curve from planar slopes and pipe its control points directly into another command:

bezierkit construct slopes \
  --start "0,5" --end "5,0" \
  --start-slope -2 --end-slope -0.3 \
| bezierkit sample --count 20 --format json

Run bezierkit --help for the complete command tree. Expected domain and input errors are printed without a traceback; add the global --debug option before the subcommand when diagnosing unexpected failures.

Scope

Version 1.0 is the first stable release: the public API listed in bezierkit.__all__ and the subpackages documented above follow semantic versioning from here on, and the JSON path schema stays at version 1. See CHANGELOG.md for the release history. Rendering style and diagram semantics remain intentionally outside this package. Intersections, B-splines, and NURBS are future work.

Metadata

Release files for bezierkit 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bezierkit 1.1.0
File Size Uploaded Provenance
bezierkit-1.1.0.tar.gz 175.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for bezierkit 1.1.0
File Interpreter ABI Platform
bezierkit-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 230.5 kB

Release files / bezierkit-1.1.0.tar.gz

Download URL bezierkit-1.1.0.tar.gz
Size 175.6 kB
Tags Source
SHA-256 checksum
How to use checksums
0623ec9a6a6bd76d650ff41edab701e0ceed9a511099e08e607d392ddebd2e49
BLAKE2b-256 checksum
How to use checksums
df36acba99ef8c4f9ef60b158931cfb6c154c6b53c0e5b305fbe33305f730f53
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / bezierkit-1.1.0-py3-none-any.whl

Download URL bezierkit-1.1.0-py3-none-any.whl
Size 54.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
aa335bc523273704a0420ccfbd9ce2de52d5d054c57b43bfe17f297a530dbaf0
BLAKE2b-256 checksum
How to use checksums
7b1ae382527d19574e92d1c312a6c4615636aa74b0d9ab62a3e7c7b94b20389b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for bezierkit 1.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page