bezierkit 0.5.0rc1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. bezierkit-0.5.0rc1/.github/workflows/publish.yml +30 -0
  2. bezierkit-0.5.0rc1/.github/workflows/test.yml +24 -0
  3. bezierkit-0.5.0rc1/.gitignore +16 -0
  4. bezierkit-0.5.0rc1/.import_linter_cache/.gitignore +2 -0
  5. bezierkit-0.5.0rc1/.import_linter_cache/CACHEDIR.TAG +3 -0
  6. bezierkit-0.5.0rc1/.import_linter_cache/a9abca5d6e0845446e009b50919d871bac847893.data.json +1 -0
  7. bezierkit-0.5.0rc1/.import_linter_cache/bezierkit.meta.json +1 -0
  8. bezierkit-0.5.0rc1/.pre-commit-config.yaml +26 -0
  9. bezierkit-0.5.0rc1/LICENSE +21 -0
  10. bezierkit-0.5.0rc1/PKG-INFO +258 -0
  11. bezierkit-0.5.0rc1/README.md +229 -0
  12. bezierkit-0.5.0rc1/benchmarks/README.md +35 -0
  13. bezierkit-0.5.0rc1/benchmarks/benchmark_evaluation.py +82 -0
  14. bezierkit-0.5.0rc1/benchmarks/benchmark_polyline.py +54 -0
  15. bezierkit-0.5.0rc1/benchmarks/results/macos-arm64-python3.13.json +32 -0
  16. bezierkit-0.5.0rc1/benchmarks/results/polyline-macos-arm64-python3.13.json +23 -0
  17. bezierkit-0.5.0rc1/docs/assets/banner.svg +23 -0
  18. bezierkit-0.5.0rc1/docs/exporters.md +40 -0
  19. bezierkit-0.5.0rc1/docs/formats/json-path-v1.md +37 -0
  20. bezierkit-0.5.0rc1/docs/math/interpolation.md +74 -0
  21. bezierkit-0.5.0rc1/pyproject.toml +107 -0
  22. bezierkit-0.5.0rc1/src/bezierkit/__init__.py +36 -0
  23. bezierkit-0.5.0rc1/src/bezierkit/adapters/__init__.py +1 -0
  24. bezierkit-0.5.0rc1/src/bezierkit/adapters/matplotlib.py +223 -0
  25. bezierkit-0.5.0rc1/src/bezierkit/bezier/__init__.py +16 -0
  26. bezierkit-0.5.0rc1/src/bezierkit/bezier/basis/__init__.py +1 -0
  27. bezierkit-0.5.0rc1/src/bezierkit/bezier/basis/bernstein.py +43 -0
  28. bezierkit-0.5.0rc1/src/bezierkit/bezier/basis/binomial.py +13 -0
  29. bezierkit-0.5.0rc1/src/bezierkit/bezier/conversion.py +41 -0
  30. bezierkit-0.5.0rc1/src/bezierkit/bezier/curve.py +104 -0
  31. bezierkit-0.5.0rc1/src/bezierkit/bezier/evaluation/__init__.py +6 -0
  32. bezierkit-0.5.0rc1/src/bezierkit/bezier/evaluation/bernstein.py +23 -0
  33. bezierkit-0.5.0rc1/src/bezierkit/bezier/evaluation/casteljau.py +24 -0
  34. bezierkit-0.5.0rc1/src/bezierkit/bezier/evaluation/evaluator.py +14 -0
  35. bezierkit-0.5.0rc1/src/bezierkit/bezier/operations/__init__.py +1 -0
  36. bezierkit-0.5.0rc1/src/bezierkit/bezier/operations/hodograph.py +15 -0
  37. bezierkit-0.5.0rc1/src/bezierkit/bezier/operations/subdivision.py +42 -0
  38. bezierkit-0.5.0rc1/src/bezierkit/bezier/path.py +221 -0
  39. bezierkit-0.5.0rc1/src/bezierkit/bezier/polygon.py +42 -0
  40. bezierkit-0.5.0rc1/src/bezierkit/bezier/segment.py +134 -0
  41. bezierkit-0.5.0rc1/src/bezierkit/cli/__init__.py +1 -0
  42. bezierkit-0.5.0rc1/src/bezierkit/cli/__main__.py +4 -0
  43. bezierkit-0.5.0rc1/src/bezierkit/cli/app.py +47 -0
  44. bezierkit-0.5.0rc1/src/bezierkit/cli/commands/__init__.py +1 -0
  45. bezierkit-0.5.0rc1/src/bezierkit/cli/commands/construct.py +61 -0
  46. bezierkit-0.5.0rc1/src/bezierkit/cli/commands/evaluate.py +36 -0
  47. bezierkit-0.5.0rc1/src/bezierkit/cli/commands/sample.py +55 -0
  48. bezierkit-0.5.0rc1/src/bezierkit/cli/formatting.py +42 -0
  49. bezierkit-0.5.0rc1/src/bezierkit/cli/parsing.py +82 -0
  50. bezierkit-0.5.0rc1/src/bezierkit/construction/__init__.py +7 -0
  51. bezierkit-0.5.0rc1/src/bezierkit/construction/construction.py +11 -0
  52. bezierkit-0.5.0rc1/src/bezierkit/construction/endpoint/__init__.py +1 -0
  53. bezierkit-0.5.0rc1/src/bezierkit/construction/endpoint/derivatives.py +37 -0
  54. bezierkit-0.5.0rc1/src/bezierkit/construction/endpoint/tangents.py +47 -0
  55. bezierkit-0.5.0rc1/src/bezierkit/construction/planar/__init__.py +1 -0
  56. bezierkit-0.5.0rc1/src/bezierkit/construction/planar/slopes.py +39 -0
  57. bezierkit-0.5.0rc1/src/bezierkit/core/__init__.py +1 -0
  58. bezierkit-0.5.0rc1/src/bezierkit/core/capabilities/__init__.py +8 -0
  59. bezierkit-0.5.0rc1/src/bezierkit/core/capabilities/curve.py +31 -0
  60. bezierkit-0.5.0rc1/src/bezierkit/core/capabilities/differentiable.py +10 -0
  61. bezierkit-0.5.0rc1/src/bezierkit/core/capabilities/reversible.py +10 -0
  62. bezierkit-0.5.0rc1/src/bezierkit/core/capabilities/subdividable.py +13 -0
  63. bezierkit-0.5.0rc1/src/bezierkit/core/domain/__init__.py +1 -0
  64. bezierkit-0.5.0rc1/src/bezierkit/core/domain/interval.py +29 -0
  65. bezierkit-0.5.0rc1/src/bezierkit/core/domain/parameter.py +47 -0
  66. bezierkit-0.5.0rc1/src/bezierkit/core/errors.py +18 -0
  67. bezierkit-0.5.0rc1/src/bezierkit/core/geometry/__init__.py +1 -0
  68. bezierkit-0.5.0rc1/src/bezierkit/core/geometry/point.py +70 -0
  69. bezierkit-0.5.0rc1/src/bezierkit/core/geometry/point_set.py +72 -0
  70. bezierkit-0.5.0rc1/src/bezierkit/core/geometry/vector.py +63 -0
  71. bezierkit-0.5.0rc1/src/bezierkit/export/__init__.py +13 -0
  72. bezierkit-0.5.0rc1/src/bezierkit/export/_coordinates.py +28 -0
  73. bezierkit-0.5.0rc1/src/bezierkit/export/json.py +108 -0
  74. bezierkit-0.5.0rc1/src/bezierkit/export/svg.py +98 -0
  75. bezierkit-0.5.0rc1/src/bezierkit/export/tikz.py +55 -0
  76. bezierkit-0.5.0rc1/src/bezierkit/fitting/__init__.py +12 -0
  77. bezierkit-0.5.0rc1/src/bezierkit/fitting/adaptive.py +115 -0
  78. bezierkit-0.5.0rc1/src/bezierkit/fitting/polyline.py +137 -0
  79. bezierkit-0.5.0rc1/src/bezierkit/implicit/__init__.py +5 -0
  80. bezierkit-0.5.0rc1/src/bezierkit/implicit/trace.py +270 -0
  81. bezierkit-0.5.0rc1/src/bezierkit/interpolation/__init__.py +5 -0
  82. bezierkit-0.5.0rc1/src/bezierkit/interpolation/hermite.py +57 -0
  83. bezierkit-0.5.0rc1/src/bezierkit/py.typed +1 -0
  84. bezierkit-0.5.0rc1/src/bezierkit/sampling/__init__.py +6 -0
  85. bezierkit-0.5.0rc1/src/bezierkit/sampling/sample.py +49 -0
  86. bezierkit-0.5.0rc1/src/bezierkit/sampling/sampler.py +13 -0
  87. bezierkit-0.5.0rc1/src/bezierkit/sampling/uniform.py +22 -0
  88. bezierkit-0.5.0rc1/tests/adapters/test_matplotlib.py +89 -0
  89. bezierkit-0.5.0rc1/tests/bezier/basis/test_bernstein.py +28 -0
  90. bezierkit-0.5.0rc1/tests/bezier/basis/test_binomial.py +13 -0
  91. bezierkit-0.5.0rc1/tests/bezier/evaluation/test_bernstein_evaluator.py +21 -0
  92. bezierkit-0.5.0rc1/tests/bezier/evaluation/test_casteljau.py +32 -0
  93. bezierkit-0.5.0rc1/tests/bezier/operations/test_hodograph.py +24 -0
  94. bezierkit-0.5.0rc1/tests/bezier/operations/test_subdivision.py +44 -0
  95. bezierkit-0.5.0rc1/tests/bezier/test_conversion.py +33 -0
  96. bezierkit-0.5.0rc1/tests/bezier/test_curve.py +107 -0
  97. bezierkit-0.5.0rc1/tests/bezier/test_path.py +73 -0
  98. bezierkit-0.5.0rc1/tests/bezier/test_polygon.py +34 -0
  99. bezierkit-0.5.0rc1/tests/bezier/test_segment.py +75 -0
  100. bezierkit-0.5.0rc1/tests/cli/test_app.py +73 -0
  101. bezierkit-0.5.0rc1/tests/cli/test_construct.py +55 -0
  102. bezierkit-0.5.0rc1/tests/cli/test_evaluate.py +45 -0
  103. bezierkit-0.5.0rc1/tests/cli/test_parsing.py +40 -0
  104. bezierkit-0.5.0rc1/tests/cli/test_sample.py +76 -0
  105. bezierkit-0.5.0rc1/tests/conformance/test_exporters.py +86 -0
  106. bezierkit-0.5.0rc1/tests/construction/endpoint/test_derivatives.py +29 -0
  107. bezierkit-0.5.0rc1/tests/construction/endpoint/test_tangents.py +46 -0
  108. bezierkit-0.5.0rc1/tests/construction/planar/test_slopes.py +34 -0
  109. bezierkit-0.5.0rc1/tests/construction/test_construction.py +8 -0
  110. bezierkit-0.5.0rc1/tests/core/capabilities/test_capabilities.py +41 -0
  111. bezierkit-0.5.0rc1/tests/core/domain/test_interval.py +30 -0
  112. bezierkit-0.5.0rc1/tests/core/domain/test_parameter.py +33 -0
  113. bezierkit-0.5.0rc1/tests/core/geometry/test_point.py +50 -0
  114. bezierkit-0.5.0rc1/tests/core/geometry/test_point_set.py +47 -0
  115. bezierkit-0.5.0rc1/tests/core/geometry/test_vector.py +36 -0
  116. bezierkit-0.5.0rc1/tests/core/test_errors.py +21 -0
  117. bezierkit-0.5.0rc1/tests/export/test_json.py +53 -0
  118. bezierkit-0.5.0rc1/tests/export/test_svg.py +45 -0
  119. bezierkit-0.5.0rc1/tests/export/test_tikz.py +42 -0
  120. bezierkit-0.5.0rc1/tests/fitting/test_adaptive.py +52 -0
  121. bezierkit-0.5.0rc1/tests/fitting/test_polyline.py +28 -0
  122. bezierkit-0.5.0rc1/tests/implicit/test_trace.py +67 -0
  123. bezierkit-0.5.0rc1/tests/interpolation/test_hermite.py +49 -0
  124. bezierkit-0.5.0rc1/tests/sampling/test_sample.py +40 -0
  125. bezierkit-0.5.0rc1/tests/sampling/test_uniform.py +42 -0
  126. bezierkit-0.5.0rc1/tests/test_public_api.py +41 -0
  127. bezierkit-0.5.0rc1/uv.lock +1629 -0
@@ -0,0 +1,30 @@
1
+ name: publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ id-token: write # required for PyPI Trusted Publishing (OIDC)
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - uses: astral-sh/setup-uv@v3
17
+ - run: uv python install 3.12
18
+
19
+ - name: Run test suite
20
+ run: |
21
+ uv sync --all-extras --dev
22
+ uv run ruff check src tests
23
+ uv run lint-imports
24
+ uv run pytest -v
25
+
26
+ - name: Build distributions
27
+ run: uv build
28
+
29
+ - name: Publish to PyPI
30
+ run: uv publish
@@ -0,0 +1,24 @@
1
+ name: test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.10", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v6
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ enable-cache: true
20
+ - run: uv sync --all-extras --dev
21
+ - run: uv run ruff check src tests benchmarks
22
+ - run: uv run lint-imports
23
+ - run: uv run pytest -v
24
+ - run: uv build
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .mypy_cache/
7
+ .coverage
8
+ htmlcov/
9
+ dist/
10
+ *.egg-info/
11
+ .superpowers/
12
+ .worktrees/
13
+
14
+ # Planning docs — local-only, not tracked
15
+ docs/spec/
16
+ docs/plan/
@@ -0,0 +1,2 @@
1
+ # Automatically created by Grimp.
2
+ *
@@ -0,0 +1,3 @@
1
+ Signature: 8a477f597d28d172789f06886806bc55
2
+ # This file is a cache directory tag automatically created by Grimp.
3
+ # For information about cache directory tags see https://bford.info/cachedir/
@@ -0,0 +1 @@
1
+ {"imports_by_module":{"bezierkit":[["bezierkit.bezier.curve",false,5,"from bezierkit.bezier.curve import BezierCurve"],["bezierkit.bezier.segment",false,7,"from bezierkit.bezier.segment import CubicBezierSegment"],["bezierkit.core.geometry.point",false,15,"from bezierkit.core.geometry.point import Point"],["bezierkit.core.geometry.vector",false,16,"from bezierkit.core.geometry.vector import Vector"],["bezierkit.core.errors",false,8,"from bezierkit.core.errors import ("],["bezierkit.bezier.path",false,6,"from bezierkit.bezier.path import BezierSubpath, PiecewiseBezier"]],"bezierkit.adapters":[],"bezierkit.adapters.matplotlib":[["bezierkit.fitting.polyline",false,12,"from bezierkit.fitting.polyline import fit_polyline"],["bezierkit.bezier.conversion",false,8,"from bezierkit.bezier.conversion import line_to_cubic, quadratic_to_cubic"],["bezierkit.bezier.path",false,9,"from bezierkit.bezier.path import BezierSubpath, PiecewiseBezier"],["bezierkit.core.geometry.point",false,11,"from bezierkit.core.geometry.point import Point"],["bezierkit.bezier.segment",false,10,"from bezierkit.bezier.segment import CubicBezierSegment"]],"bezierkit.bezier":[["bezierkit.bezier.segment",false,6,"from bezierkit.bezier.segment import CubicBezierSegment"],["bezierkit.bezier.conversion",false,3,"from bezierkit.bezier.conversion import line_to_cubic, quadratic_to_cubic, to_cubic"],["bezierkit.bezier.path",false,5,"from bezierkit.bezier.path import BezierSubpath, PiecewiseBezier"],["bezierkit.bezier.curve",false,4,"from bezierkit.bezier.curve import BezierCurve"]],"bezierkit.bezier.basis":[],"bezierkit.bezier.basis.bernstein":[["bezierkit.bezier.basis.binomial",false,7,"from bezierkit.bezier.basis.binomial import BinomialTable"]],"bezierkit.bezier.basis.binomial":[],"bezierkit.bezier.conversion":[["bezierkit.core.errors",false,5,"from bezierkit.core.errors import DegreeError, DimensionMismatch"],["bezierkit.bezier.curve",false,3,"from bezierkit.bezier.curve import BezierCurve"],["bezierkit.bezier.segment",false,4,"from bezierkit.bezier.segment import CubicBezierSegment"],["bezierkit.core.geometry.point",false,6,"from bezierkit.core.geometry.point import Point"]],"bezierkit.bezier.curve":[["bezierkit.bezier.operations.subdivision",false,10,"from bezierkit.bezier.operations.subdivision import Subdivider"],["bezierkit.core.errors",false,18,"from bezierkit.core.errors import DimensionMismatch"],["bezierkit.core.capabilities.differentiable",false,13,"from bezierkit.core.capabilities.differentiable import Differentiable"],["bezierkit.core.capabilities.reversible",false,14,"from bezierkit.core.capabilities.reversible import Reversible"],["bezierkit.core.geometry.point",false,19,"from bezierkit.core.geometry.point import Point"],["bezierkit.bezier.evaluation.casteljau",false,7,"from bezierkit.bezier.evaluation.casteljau import DeCasteljauEvaluator"],["bezierkit.bezier.evaluation.evaluator",false,8,"from bezierkit.bezier.evaluation.evaluator import Evaluator"],["bezierkit.bezier.polygon",false,11,"from bezierkit.bezier.polygon import ControlPolygon"],["bezierkit.core.geometry.point_set",false,20,"from bezierkit.core.geometry.point_set import PointSet"],["bezierkit.core.domain.interval",false,16,"from bezierkit.core.domain.interval import Interval"],["bezierkit.bezier.operations.hodograph",false,9,"from bezierkit.bezier.operations.hodograph import Hodograph"],["bezierkit.core.capabilities.subdividable",false,15,"from bezierkit.core.capabilities.subdividable import Subdividable"],["bezierkit.core.domain.parameter",false,17,"from bezierkit.core.domain.parameter import ParameterValues"],["bezierkit.core.capabilities.curve",false,12,"from bezierkit.core.capabilities.curve import ParametricCurve"]],"bezierkit.bezier.evaluation":[["bezierkit.bezier.evaluation.bernstein",false,2,"from bezierkit.bezier.evaluation.bernstein import BernsteinEvaluator"],["bezierkit.bezier.evaluation.casteljau",false,3,"from bezierkit.bezier.evaluation.casteljau import DeCasteljauEvaluator"],["bezierkit.bezier.evaluation.evaluator",false,4,"from bezierkit.bezier.evaluation.evaluator import Evaluator"]],"bezierkit.bezier.evaluation.bernstein":[["bezierkit.bezier.evaluation.evaluator",false,6,"from bezierkit.bezier.evaluation.evaluator import Evaluator"],["bezierkit.bezier.basis.bernstein",false,5,"from bezierkit.bezier.basis.bernstein import BernsteinBasis"],["bezierkit.bezier.polygon",false,7,"from bezierkit.bezier.polygon import ControlPolygon"]],"bezierkit.bezier.evaluation.casteljau":[["bezierkit.bezier.evaluation.evaluator",false,5,"from bezierkit.bezier.evaluation.evaluator import Evaluator"],["bezierkit.bezier.polygon",false,6,"from bezierkit.bezier.polygon import ControlPolygon"]],"bezierkit.bezier.evaluation.evaluator":[["bezierkit.bezier.polygon",false,7,"from bezierkit.bezier.polygon import ControlPolygon"]],"bezierkit.bezier.operations":[],"bezierkit.bezier.operations.hodograph":[["bezierkit.bezier.polygon",false,5,"from bezierkit.bezier.polygon import ControlPolygon"]],"bezierkit.bezier.operations.subdivision":[["bezierkit.bezier.polygon",false,5,"from bezierkit.bezier.polygon import ControlPolygon"],["bezierkit.core.errors",false,6,"from bezierkit.core.errors import ParameterOutOfDomain"]],"bezierkit.bezier.path":[["bezierkit.core.capabilities.reversible",false,11,"from bezierkit.core.capabilities.reversible import Reversible"],["bezierkit.core.capabilities.curve",false,10,"from bezierkit.core.capabilities.curve import ParametricCurve"],["bezierkit.core.domain.interval",false,13,"from bezierkit.core.domain.interval import Interval"],["bezierkit.bezier.segment",false,9,"from bezierkit.bezier.segment import CubicBezierSegment"],["bezierkit.core.geometry.point_set",false,17,"from bezierkit.core.geometry.point_set import PointSet"],["bezierkit.core.geometry.point",false,16,"from bezierkit.core.geometry.point import Point"],["bezierkit.core.capabilities.subdividable",false,12,"from bezierkit.core.capabilities.subdividable import Subdividable"],["bezierkit.core.errors",false,15,"from bezierkit.core.errors import DimensionMismatch"],["bezierkit.core.domain.parameter",false,14,"from bezierkit.core.domain.parameter import ParameterValues"]],"bezierkit.bezier.polygon":[["bezierkit.core.errors",false,9,"from bezierkit.core.errors import DegreeError"],["bezierkit.core.geometry.point_set",false,11,"from bezierkit.core.geometry.point_set import PointSet"],["bezierkit.core.geometry.point",false,10,"from bezierkit.core.geometry.point import Point"]],"bezierkit.bezier.segment":[["bezierkit.core.capabilities.curve",false,12,"from bezierkit.core.capabilities.curve import ParametricCurve"],["bezierkit.core.capabilities.differentiable",false,13,"from bezierkit.core.capabilities.differentiable import Differentiable"],["bezierkit.core.capabilities.subdividable",false,15,"from bezierkit.core.capabilities.subdividable import Subdividable"],["bezierkit.bezier.conversion",false,126,"from bezierkit.bezier.conversion import line_to_cubic"],["bezierkit.core.geometry.point_set",false,19,"from bezierkit.core.geometry.point_set import PointSet"],["bezierkit.core.errors",false,17,"from bezierkit.core.errors import DegreeError, DimensionMismatch"],["bezierkit.bezier.evaluation.evaluator",false,11,"from bezierkit.bezier.evaluation.evaluator import Evaluator"],["bezierkit.core.capabilities.reversible",false,14,"from bezierkit.core.capabilities.reversible import Reversible"],["bezierkit.core.geometry.point",false,18,"from bezierkit.core.geometry.point import Point"],["bezierkit.core.domain.interval",false,16,"from bezierkit.core.domain.interval import Interval"],["bezierkit.bezier.curve",false,9,"from bezierkit.bezier.curve import BezierCurve"],["bezierkit.bezier.conversion",false,132,"from bezierkit.bezier.conversion import quadratic_to_cubic"],["bezierkit.bezier.evaluation.casteljau",false,10,"from bezierkit.bezier.evaluation.casteljau import DeCasteljauEvaluator"]],"bezierkit.cli":[],"bezierkit.cli.__main__":[["bezierkit.cli.app",false,1,"from bezierkit.cli.app import main"]],"bezierkit.cli.app":[["bezierkit.cli.commands.evaluate",false,9,"from bezierkit.cli.commands.evaluate import app as evaluate_app"],["bezierkit.cli.commands.construct",false,8,"from bezierkit.cli.commands.construct import app as construct_app"],["bezierkit.core.errors",false,11,"from bezierkit.core.errors import BezierKitError"],["bezierkit.cli.commands.sample",false,10,"from bezierkit.cli.commands.sample import app as sample_app"]],"bezierkit.cli.commands":[],"bezierkit.cli.commands.construct":[["bezierkit",false,8,"from bezierkit import BezierCurve"],["bezierkit.cli.parsing",false,9,"from bezierkit.cli.parsing import parse_point, parse_vector"],["bezierkit.construction",false,10,"from bezierkit.construction import PlanarSlopes, TangentDirections"]],"bezierkit.cli.commands.evaluate":[["bezierkit.cli.parsing",false,9,"from bezierkit.cli.parsing import parse_t_values, resolve_control_points"],["bezierkit",false,7,"from bezierkit import BezierCurve"],["bezierkit.cli.formatting",false,8,"from bezierkit.cli.formatting import render_json, render_table"]],"bezierkit.cli.commands.sample":[["bezierkit.sampling",false,11,"from bezierkit.sampling import Sample, UniformSampler"],["bezierkit.cli.formatting",false,9,"from bezierkit.cli.formatting import render_csv, render_json, render_table"],["bezierkit.cli.parsing",false,10,"from bezierkit.cli.parsing import resolve_control_points"],["bezierkit",false,8,"from bezierkit import BezierCurve, BezierKitError"]],"bezierkit.cli.formatting":[],"bezierkit.cli.parsing":[["bezierkit",false,8,"from bezierkit import Point, Vector"],["bezierkit.core.errors",false,9,"from bezierkit.core.errors import BezierKitError"]],"bezierkit.construction":[["bezierkit.construction.endpoint.tangents",false,4,"from bezierkit.construction.endpoint.tangents import TangentDirections"],["bezierkit.construction.planar.slopes",false,5,"from bezierkit.construction.planar.slopes import PlanarSlopes"],["bezierkit.construction.endpoint.derivatives",false,3,"from bezierkit.construction.endpoint.derivatives import EndpointDerivatives"]],"bezierkit.construction.construction":[],"bezierkit.construction.endpoint":[],"bezierkit.construction.endpoint.derivatives":[["bezierkit.bezier.curve",false,5,"from bezierkit.bezier.curve import BezierCurve"],["bezierkit.core.geometry.point",false,8,"from bezierkit.core.geometry.point import Point"],["bezierkit.construction.construction",false,6,"from bezierkit.construction.construction import Construction"],["bezierkit.core.errors",false,7,"from bezierkit.core.errors import DimensionMismatch"],["bezierkit.core.geometry.vector",false,9,"from bezierkit.core.geometry.vector import Vector"]],"bezierkit.construction.endpoint.tangents":[["bezierkit.bezier.curve",false,6,"from bezierkit.bezier.curve import BezierCurve"],["bezierkit.core.errors",false,9,"from bezierkit.core.errors import DimensionMismatch"],["bezierkit.core.geometry.point",false,10,"from bezierkit.core.geometry.point import Point"],["bezierkit.construction.endpoint.derivatives",false,8,"from bezierkit.construction.endpoint.derivatives import EndpointDerivatives"],["bezierkit.core.geometry.vector",false,11,"from bezierkit.core.geometry.vector import Vector"],["bezierkit.construction.construction",false,7,"from bezierkit.construction.construction import Construction"]],"bezierkit.construction.planar":[],"bezierkit.construction.planar.slopes":[["bezierkit.core.errors",false,9,"from bezierkit.core.errors import DimensionMismatch"],["bezierkit.construction.construction",false,7,"from bezierkit.construction.construction import Construction"],["bezierkit.core.geometry.vector",false,11,"from bezierkit.core.geometry.vector import Vector"],["bezierkit.bezier.curve",false,6,"from bezierkit.bezier.curve import BezierCurve"],["bezierkit.core.geometry.point",false,10,"from bezierkit.core.geometry.point import Point"],["bezierkit.construction.endpoint.tangents",false,8,"from bezierkit.construction.endpoint.tangents import TangentDirections"]],"bezierkit.core":[],"bezierkit.core.capabilities":[["bezierkit.core.capabilities.reversible",false,5,"from bezierkit.core.capabilities.reversible import Reversible"],["bezierkit.core.capabilities.differentiable",false,4,"from bezierkit.core.capabilities.differentiable import Differentiable"],["bezierkit.core.capabilities.subdividable",false,6,"from bezierkit.core.capabilities.subdividable import Subdividable"],["bezierkit.core.capabilities.curve",false,3,"from bezierkit.core.capabilities.curve import ParametricCurve"]],"bezierkit.core.capabilities.curve":[["bezierkit.core.domain.interval",false,8,"from bezierkit.core.domain.interval import Interval"],["bezierkit.core.geometry.point",false,9,"from bezierkit.core.geometry.point import Point"],["bezierkit.core.geometry.point_set",false,10,"from bezierkit.core.geometry.point_set import PointSet"]],"bezierkit.core.capabilities.differentiable":[],"bezierkit.core.capabilities.reversible":[],"bezierkit.core.capabilities.subdividable":[],"bezierkit.core.domain":[],"bezierkit.core.domain.interval":[],"bezierkit.core.domain.parameter":[["bezierkit.core.errors",false,9,"from bezierkit.core.errors import ParameterOutOfDomain"],["bezierkit.core.domain.interval",false,8,"from bezierkit.core.domain.interval import Interval"]],"bezierkit.core.errors":[],"bezierkit.core.geometry":[],"bezierkit.core.geometry.point":[["bezierkit.core.geometry.vector",false,12,"from bezierkit.core.geometry.vector import Vector"],["bezierkit.core.errors",false,9,"from bezierkit.core.errors import DimensionMismatch"],["bezierkit.core.geometry.vector",false,62,"from bezierkit.core.geometry.vector import Vector"]],"bezierkit.core.geometry.point_set":[["bezierkit.core.geometry.point",false,9,"from bezierkit.core.geometry.point import Point"]],"bezierkit.core.geometry.vector":[["bezierkit.core.errors",false,8,"from bezierkit.core.errors import DimensionMismatch"]],"bezierkit.export":[["bezierkit.export.tikz",false,5,"from bezierkit.export.tikz import segment_to_tikz, to_tikz"],["bezierkit.export.json",false,3,"from bezierkit.export.json import PathDocument"],["bezierkit.export.svg",false,4,"from bezierkit.export.svg import from_svg_path_data, to_svg_path_data"]],"bezierkit.export._coordinates":[["bezierkit.core.geometry.point",false,6,"from bezierkit.core.geometry.point import Point"]],"bezierkit.export.json":[["bezierkit.bezier.segment",false,10,"from bezierkit.bezier.segment import CubicBezierSegment"],["bezierkit.bezier.path",false,9,"from bezierkit.bezier.path import BezierSubpath, PiecewiseBezier"],["bezierkit.core.geometry.point",false,11,"from bezierkit.core.geometry.point import Point"]],"bezierkit.export.svg":[["bezierkit.bezier.path",false,6,"from bezierkit.bezier.path import BezierSubpath, PiecewiseBezier"],["bezierkit.bezier.segment",false,7,"from bezierkit.bezier.segment import CubicBezierSegment"],["bezierkit.export._coordinates",false,9,"from bezierkit.export._coordinates import CoordinateTransform, number, transformed"],["bezierkit.core.geometry.point",false,8,"from bezierkit.core.geometry.point import Point"]],"bezierkit.export.tikz":[["bezierkit.bezier.segment",false,4,"from bezierkit.bezier.segment import CubicBezierSegment"],["bezierkit.export._coordinates",false,6,"from bezierkit.export._coordinates import CoordinateTransform, number, transformed"],["bezierkit.bezier.path",false,3,"from bezierkit.bezier.path import PiecewiseBezier"],["bezierkit.core.geometry.point",false,5,"from bezierkit.core.geometry.point import Point"]],"bezierkit.fitting":[["bezierkit.fitting.adaptive",false,3,"from bezierkit.fitting.adaptive import fit_graph, fit_parametric, hermite_error_bound"],["bezierkit.fitting.polyline",false,4,"from bezierkit.fitting.polyline import fit_polyline, maximum_polyline_deviation"]],"bezierkit.fitting.adaptive":[["bezierkit.bezier.segment",false,9,"from bezierkit.bezier.segment import CubicBezierSegment"],["bezierkit.bezier.path",false,8,"from bezierkit.bezier.path import PiecewiseBezier"],["bezierkit.core.errors",false,10,"from bezierkit.core.errors import ToleranceNotMet"],["bezierkit.interpolation.hermite",false,13,"from bezierkit.interpolation.hermite import parametric_hermite"],["bezierkit.core.geometry.point",false,11,"from bezierkit.core.geometry.point import Point"],["bezierkit.core.geometry.vector",false,12,"from bezierkit.core.geometry.vector import Vector"]],"bezierkit.fitting.polyline":[["bezierkit.bezier.path",false,8,"from bezierkit.bezier.path import PiecewiseBezier"],["bezierkit.bezier.segment",false,9,"from bezierkit.bezier.segment import CubicBezierSegment"],["bezierkit.core.geometry.point",false,10,"from bezierkit.core.geometry.point import Point"]],"bezierkit.implicit":[["bezierkit.implicit.trace",false,3,"from bezierkit.implicit.trace import ContourSet, LevelContours, trace_implicit"]],"bezierkit.implicit.trace":[["bezierkit.bezier.segment",false,11,"from bezierkit.bezier.segment import CubicBezierSegment"],["bezierkit.core.geometry.vector",false,13,"from bezierkit.core.geometry.vector import Vector"],["bezierkit.core.geometry.point",false,12,"from bezierkit.core.geometry.point import Point"],["bezierkit.fitting.polyline",false,14,"from bezierkit.fitting.polyline import fit_polyline"],["bezierkit.bezier.path",false,10,"from bezierkit.bezier.path import PiecewiseBezier"]],"bezierkit.interpolation":[["bezierkit.interpolation.hermite",false,3,"from bezierkit.interpolation.hermite import graph_hermite, parametric_hermite"]],"bezierkit.interpolation.hermite":[["bezierkit.core.geometry.point",false,7,"from bezierkit.core.geometry.point import Point"],["bezierkit.core.geometry.vector",false,8,"from bezierkit.core.geometry.vector import Vector"],["bezierkit.core.errors",false,6,"from bezierkit.core.errors import DimensionMismatch"],["bezierkit.bezier.segment",false,5,"from bezierkit.bezier.segment import CubicBezierSegment"]],"bezierkit.sampling":[["bezierkit.sampling.uniform",false,4,"from bezierkit.sampling.uniform import UniformSampler"],["bezierkit.sampling.sample",false,3,"from bezierkit.sampling.sample import Sample"]],"bezierkit.sampling.sample":[["bezierkit.core.geometry.point_set",false,9,"from bezierkit.core.geometry.point_set import PointSet"],["bezierkit.core.geometry.point",false,8,"from bezierkit.core.geometry.point import Point"]],"bezierkit.sampling.sampler":[["bezierkit.core.capabilities.curve",false,5,"from bezierkit.core.capabilities.curve import ParametricCurve"],["bezierkit.sampling.sample",false,6,"from bezierkit.sampling.sample import Sample"]],"bezierkit.sampling.uniform":[["bezierkit.sampling.sampler",false,7,"from bezierkit.sampling.sampler import Sampler"],["bezierkit.core.capabilities.curve",false,5,"from bezierkit.core.capabilities.curve import ParametricCurve"],["bezierkit.sampling.sample",false,6,"from bezierkit.sampling.sample import Sample"]]},"version":2}
@@ -0,0 +1 @@
1
+ {"bezierkit.bezier.evaluation": 1790608899.2898939, "bezierkit": 1790608899.2895749, "bezierkit.core.errors": 1790608899.291696, "bezierkit.export": 1790608899.2918577, "bezierkit.bezier": 1790608899.2896984, "bezierkit.export.tikz": 1790608899.2920394, "bezierkit.bezier.segment": 1790608899.2903366, "bezierkit.implicit.trace": 1790608899.2924604, "bezierkit.construction.endpoint.derivatives": 1790608899.291118, "bezierkit.sampling": 1790608899.2926018, "bezierkit.core.capabilities.differentiable": 1790608899.2914712, "bezierkit.bezier.operations": 1790608899.2901056, "bezierkit.bezier.conversion": 1790608899.2898939, "bezierkit.bezier.polygon": 1790608899.2903366, "bezierkit.sampling.uniform": 1790608899.2927358, "bezierkit.cli": 1790608899.2903366, "bezierkit.core.capabilities.curve": 1790608899.2914712, "bezierkit.construction.endpoint.tangents": 1790608899.291118, "bezierkit.bezier.basis": 1790608899.2898448, "bezierkit.core.domain.parameter": 1790608899.291696, "bezierkit.export.svg": 1790608899.2920394, "bezierkit.core.domain.interval": 1790608899.291696, "bezierkit.construction.planar": 1790608899.291118, "bezierkit.adapters.matplotlib": 1790608899.2896984, "bezierkit.bezier.operations.subdivision": 1790608899.2903366, "bezierkit.export.json": 1790608899.2920394, "bezierkit.sampling.sample": 1790608899.2927358, "bezierkit.core.geometry.point_set": 1790608899.2918577, "bezierkit.cli.formatting": 1790608899.2907736, "bezierkit.bezier.evaluation.bernstein": 1790608899.2901056, "bezierkit.adapters": 1790608899.2896466, "bezierkit.core": 1790608899.2913053, "bezierkit.core.geometry.vector": 1790608899.2918577, "bezierkit.export._coordinates": 1790608899.2920394, "bezierkit.cli.app": 1790608899.2906537, "bezierkit.bezier.basis.bernstein": 1790608899.2898939, "bezierkit.bezier.basis.binomial": 1790608899.2898939, "bezierkit.cli.commands.construct": 1790608899.2907736, "bezierkit.construction": 1790608899.2907736, "bezierkit.cli.parsing": 1790608899.2907736, "bezierkit.fitting.adaptive": 1790608899.2923026, "bezierkit.construction.construction": 1790608899.2910295, "bezierkit.fitting": 1790608899.2920394, "bezierkit.cli.__main__": 1790608899.2906537, "bezierkit.bezier.path": 1790608899.2903366, "bezierkit.bezier.evaluation.evaluator": 1790608899.2901056, "bezierkit.core.capabilities.reversible": 1790608899.2914712, "bezierkit.bezier.curve": 1790608899.2898939, "bezierkit.core.domain": 1790608899.2914712, "bezierkit.implicit": 1790608899.2923026, "bezierkit.core.capabilities.subdividable": 1790608899.2914712, "bezierkit.bezier.operations.hodograph": 1790608899.2903366, "bezierkit.fitting.polyline": 1790608899.2923026, "bezierkit.interpolation": 1790608899.2924604, "bezierkit.core.capabilities": 1790608899.291418, "bezierkit.construction.endpoint": 1790608899.2910295, "bezierkit.core.geometry.point": 1790608899.2918577, "bezierkit.interpolation.hermite": 1790608899.2926018, "bezierkit.sampling.sampler": 1790608899.2927358, "bezierkit.bezier.evaluation.casteljau": 1790608899.2901056, "bezierkit.construction.planar.slopes": 1790608899.2913053, "bezierkit.cli.commands": 1790608899.2906537, "bezierkit.cli.commands.sample": 1790608899.2907736, "bezierkit.core.geometry": 1790608899.291696, "bezierkit.cli.commands.evaluate": 1790608899.2907736}
@@ -0,0 +1,26 @@
1
+ repos:
2
+ - repo: local
3
+ hooks:
4
+ - id: uv-lock
5
+ name: uv lock --check
6
+ entry: uv lock --check
7
+ language: system
8
+ pass_filenames: false
9
+
10
+ - id: ruff-check
11
+ name: ruff check
12
+ entry: uv run ruff check src tests benchmarks
13
+ language: system
14
+ pass_filenames: false
15
+
16
+ - id: import-contracts
17
+ name: import-linter contracts
18
+ entry: uv run lint-imports
19
+ language: system
20
+ pass_filenames: false
21
+
22
+ - id: pytest
23
+ name: pytest
24
+ entry: uv run pytest -q
25
+ language: system
26
+ pass_filenames: false
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 EconViz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,258 @@
1
+ Metadata-Version: 2.5
2
+ Name: bezierkit
3
+ Version: 0.5.0rc1
4
+ Summary: A small mathematical toolkit for constructing and analyzing Bezier curves.
5
+ Project-URL: Homepage, https://github.com/EconViz/bezierkit
6
+ Project-URL: Repository, https://github.com/EconViz/bezierkit
7
+ Project-URL: Issues, https://github.com/EconViz/bezierkit/issues
8
+ Author: EconViz
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: numpy>=1.24
23
+ Provides-Extra: cli
24
+ Requires-Dist: rich>=13; extra == 'cli'
25
+ Requires-Dist: typer>=0.12; extra == 'cli'
26
+ Provides-Extra: matplotlib
27
+ Requires-Dist: matplotlib>=3.8; extra == 'matplotlib'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # bezierkit
31
+
32
+ <p align="center">
33
+ <img src="docs/assets/banner.svg" alt="bezierkit" width="480">
34
+ </p>
35
+
36
+ <p align="center">
37
+ <a href="https://pypi.org/project/bezierkit/"><img alt="PyPI" src="https://img.shields.io/pypi/v/bezierkit?style=flat-square&color=181818&labelColor=f3f3f3&cacheSeconds=300"></a>
38
+ <a href="https://pypi.org/project/bezierkit/"><img alt="Python" src="https://img.shields.io/pypi/pyversions/bezierkit?style=flat-square&color=181818&labelColor=f3f3f3"></a>
39
+ <a href="https://opensource.org/licenses/MIT"><img alt="License" src="https://img.shields.io/badge/License-MIT-181818?style=flat-square&color=181818&labelColor=f3f3f3"></a>
40
+ <img alt="Tests" src="https://img.shields.io/badge/tests-176%20passed-181818?style=flat-square&color=181818&labelColor=f3f3f3">
41
+ </p>
42
+
43
+ A small mathematical toolkit for constructing and analyzing Bézier curves.
44
+
45
+ `bezierkit` is renderer-independent: it provides curve construction,
46
+ evaluation, subdivision, and sampling while leaving plotting to consumers such
47
+ as EconViz, Matplotlib, Plotly, SVG, or Typst adapters.
48
+
49
+ ## Installation
50
+
51
+ Add the library to your project with [uv](https://docs.astral.sh/uv/):
52
+
53
+ ```bash
54
+ uv add bezierkit
55
+ ```
56
+
57
+ Add the optional command-line interface:
58
+
59
+ ```bash
60
+ uv add "bezierkit[cli]"
61
+ ```
62
+
63
+ For development, clone the repo and sync all extras:
64
+
65
+ ```bash
66
+ uv sync --all-extras --dev
67
+ ```
68
+
69
+ Run anything inside the environment with `uv run`, e.g. `uv run pytest` or
70
+ `uv run bezierkit --help`. A plain `pip install bezierkit` /
71
+ `pip install "bezierkit[cli]"` also works if you are not using uv.
72
+
73
+ ## Python API
74
+
75
+ ```python
76
+ from bezierkit import BezierCurve, CubicBezierSegment, PiecewiseBezier, Point
77
+ from bezierkit.construction import PlanarSlopes
78
+ from bezierkit.sampling import UniformSampler
79
+
80
+ curve = BezierCurve.cubic(
81
+ Point(0, 0),
82
+ Point(1, 2),
83
+ Point(3, 2),
84
+ Point(4, 0),
85
+ )
86
+
87
+ segment = CubicBezierSegment(
88
+ Point(0, 0),
89
+ Point(1, 2),
90
+ Point(3, 2),
91
+ Point(4, 0),
92
+ )
93
+ segment.control_points # (P0, P1, P2, P3)
94
+
95
+ path = PiecewiseBezier([
96
+ CubicBezierSegment.from_line(Point(0, 0), Point(2, 0)),
97
+ CubicBezierSegment.from_line(Point(2, 0), Point(2, 4)),
98
+ ])
99
+ path.at(0.75) # Point(2.0, 2.0)
100
+
101
+ curve.at(0.5) # Point(2.0, 1.5)
102
+ curve.derivative().at(0.5) # first derivative
103
+ left, right = curve.split(0.3)
104
+ sample = UniformSampler(200).sample(curve)
105
+
106
+ demand = PlanarSlopes(
107
+ start=Point(0, 5),
108
+ end=Point(5, 0),
109
+ start_slope=-2,
110
+ end_slope=-0.3,
111
+ ).build()
112
+ ```
113
+
114
+ The engine supports arbitrary degree and dimension. `Point`, `Vector`,
115
+ `PointSet`, control polygons, and sampling results are immutable value objects.
116
+
117
+ `CubicBezierSegment` is the primary renderer-facing value object. It exposes
118
+ `p0`, `p1`, `p2`, `p3`, tight axis-aligned bounds, and cubic-preserving split
119
+ and reversal operations. Existing `BezierCurve.cubic(...)` code remains valid;
120
+ use `CubicBezierSegment.from_curve(curve)` to migrate without changing any
121
+ control point, or `segment.as_curve()` when a degree-generic API is required.
122
+
123
+ `PiecewiseBezier` divides `[0, 1]` uniformly across the segments of a single
124
+ subpath. Closure is metadata and never inserts a hidden closing segment.
125
+ `PiecewiseBezier.compound(...)` preserves independent subpaths for fills and
126
+ holes; evaluate and split each subpath independently because a compound path
127
+ has no single continuous parameterization. Reversal preserves each subpath;
128
+ splitting is defined only for a single open subpath.
129
+
130
+ Linear and quadratic inputs can be elevated without changing their geometry:
131
+
132
+ ```python
133
+ from bezierkit.bezier import to_cubic
134
+
135
+ cubic = to_cubic(BezierCurve.quadratic(
136
+ Point(0, 0), Point(3, 6), Point(9, 0)
137
+ ))
138
+ ```
139
+
140
+ Batch evaluation uses a vectorized de Casteljau implementation by default.
141
+ `BernsteinEvaluator` is also available from `bezierkit.bezier.evaluation` for
142
+ explicit low-degree throughput tradeoffs; see `benchmarks/` for the
143
+ reproducible 400, 10,000, and 100,000-value benchmark.
144
+
145
+ ## Interpolation, fitting, and level sets
146
+
147
+ ```python
148
+ from bezierkit.fitting import fit_graph
149
+ from bezierkit.implicit import trace_implicit
150
+ from bezierkit.interpolation import graph_hermite
151
+
152
+ segment = graph_hermite(
153
+ x0=1, x1=1.5,
154
+ y0=4, y1=8 / 3,
155
+ m0=-8, m1=-32 / 9,
156
+ )
157
+
158
+ path = fit_graph(
159
+ lambda x: 4 / x**2,
160
+ lambda x: -8 / x**3,
161
+ x0=0.8, x1=3,
162
+ tolerance=1e-3,
163
+ )
164
+
165
+ contours = trace_implicit(
166
+ lambda x, y: x**2 * y,
167
+ levels=[1, 2, 4],
168
+ viewport=(0.5, 4, 0, 6),
169
+ resolution=(121, 121),
170
+ tolerance=0.01,
171
+ gradient=lambda x, y: (2 * x * y, x**2),
172
+ )
173
+ ```
174
+
175
+ Adaptive fitting reports a measured Euclidean error on every segment and
176
+ raises `ToleranceNotMet` if configured limits prevent the requested tolerance.
177
+ Implicit tracing preserves disconnected and closed components as independent
178
+ paths and uses parametric gradient tangents, including at vertical tangencies.
179
+ The derivations, error bound, continuity conditions, and sampling limitations
180
+ are documented in `docs/math/interpolation.md`.
181
+
182
+ ## Exporters and adapters
183
+
184
+ ```python
185
+ from bezierkit.export.json import dumps, loads
186
+ from bezierkit.export.svg import to_svg_path_data
187
+ from bezierkit.export.tikz import to_tikz
188
+
189
+ json_document = dumps(path, metadata={"name": "indifference-curve"})
190
+ same_path = loads(json_document).path
191
+ svg_path_data = to_svg_path_data(path, precision=5)
192
+ tikz = to_tikz(path, precision=5, options="thick")
193
+ ```
194
+
195
+ TikZ uses native `.. controls ... and ... ..` commands and SVG uses native
196
+ `C` commands; neither exporter flattens cubic geometry or chooses a theme.
197
+ The versioned JSON schema preserves all controls, subpaths, closure, dimension,
198
+ and caller metadata.
199
+
200
+ Matplotlib interoperability is optional:
201
+
202
+ ```bash
203
+ uv add "bezierkit[matplotlib]"
204
+ ```
205
+
206
+ ```python
207
+ from bezierkit.adapters.matplotlib import from_path, to_path
208
+
209
+ geometry = from_path(matplotlib_path, transform=affine_transform)
210
+ round_trip = to_path(geometry)
211
+ ```
212
+
213
+ Affine transforms preserve controls exactly. Non-affine transforms require
214
+ the explicit `approximate_path(..., tolerance=...)` API. Format guarantees and
215
+ round-trip tolerances are documented in `docs/exporters.md`.
216
+
217
+ ## Command-line interface
218
+
219
+ Run `bezierkit` directly if it's installed in your active environment, or
220
+ prefix every command with `uv run` (e.g. `uv run bezierkit --help`) when
221
+ working inside a uv project without activating the venv.
222
+
223
+ Evaluate a curve or derivative:
224
+
225
+ ```bash
226
+ bezierkit evaluate \
227
+ --points "0,0" --points "1,2" --points "3,2" --points "4,0" \
228
+ --t 0:1:0.25 --order 1 --json
229
+ ```
230
+
231
+ Sample as CSV:
232
+
233
+ ```bash
234
+ bezierkit sample \
235
+ --points "0,0" --points "1,2" --points "3,2" --points "4,0" \
236
+ --count 50 --format csv > curve.csv
237
+ ```
238
+
239
+ Construct a curve from planar slopes and pipe its control points directly into
240
+ another command:
241
+
242
+ ```bash
243
+ bezierkit construct slopes \
244
+ --start "0,5" --end "5,0" \
245
+ --start-slope -2 --end-slope -0.3 \
246
+ | bezierkit sample --count 20 --format json
247
+ ```
248
+
249
+ Run `bezierkit --help` for the complete command tree. Expected domain and input
250
+ errors are printed without a traceback; add the global `--debug` option before
251
+ the subcommand when diagnosing unexpected failures.
252
+
253
+ ## Scope
254
+
255
+ The 0.5 release adds stable JSON geometry, native TikZ and SVG cubic output,
256
+ and optional Matplotlib Path interoperability. Rendering style and diagram
257
+ semantics remain intentionally outside this package. Intersections, B-splines,
258
+ and NURBS are future work.
@@ -0,0 +1,229 @@
1
+ # bezierkit
2
+
3
+ <p align="center">
4
+ <img src="docs/assets/banner.svg" alt="bezierkit" width="480">
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://pypi.org/project/bezierkit/"><img alt="PyPI" src="https://img.shields.io/pypi/v/bezierkit?style=flat-square&color=181818&labelColor=f3f3f3&cacheSeconds=300"></a>
9
+ <a href="https://pypi.org/project/bezierkit/"><img alt="Python" src="https://img.shields.io/pypi/pyversions/bezierkit?style=flat-square&color=181818&labelColor=f3f3f3"></a>
10
+ <a href="https://opensource.org/licenses/MIT"><img alt="License" src="https://img.shields.io/badge/License-MIT-181818?style=flat-square&color=181818&labelColor=f3f3f3"></a>
11
+ <img alt="Tests" src="https://img.shields.io/badge/tests-176%20passed-181818?style=flat-square&color=181818&labelColor=f3f3f3">
12
+ </p>
13
+
14
+ A small mathematical toolkit for constructing and analyzing Bézier curves.
15
+
16
+ `bezierkit` is renderer-independent: it provides curve construction,
17
+ evaluation, subdivision, and sampling while leaving plotting to consumers such
18
+ as EconViz, Matplotlib, Plotly, SVG, or Typst adapters.
19
+
20
+ ## Installation
21
+
22
+ Add the library to your project with [uv](https://docs.astral.sh/uv/):
23
+
24
+ ```bash
25
+ uv add bezierkit
26
+ ```
27
+
28
+ Add the optional command-line interface:
29
+
30
+ ```bash
31
+ uv add "bezierkit[cli]"
32
+ ```
33
+
34
+ For development, clone the repo and sync all extras:
35
+
36
+ ```bash
37
+ uv sync --all-extras --dev
38
+ ```
39
+
40
+ Run anything inside the environment with `uv run`, e.g. `uv run pytest` or
41
+ `uv run bezierkit --help`. A plain `pip install bezierkit` /
42
+ `pip install "bezierkit[cli]"` also works if you are not using uv.
43
+
44
+ ## Python API
45
+
46
+ ```python
47
+ from bezierkit import BezierCurve, CubicBezierSegment, PiecewiseBezier, Point
48
+ from bezierkit.construction import PlanarSlopes
49
+ from bezierkit.sampling import UniformSampler
50
+
51
+ curve = BezierCurve.cubic(
52
+ Point(0, 0),
53
+ Point(1, 2),
54
+ Point(3, 2),
55
+ Point(4, 0),
56
+ )
57
+
58
+ segment = CubicBezierSegment(
59
+ Point(0, 0),
60
+ Point(1, 2),
61
+ Point(3, 2),
62
+ Point(4, 0),
63
+ )
64
+ segment.control_points # (P0, P1, P2, P3)
65
+
66
+ path = PiecewiseBezier([
67
+ CubicBezierSegment.from_line(Point(0, 0), Point(2, 0)),
68
+ CubicBezierSegment.from_line(Point(2, 0), Point(2, 4)),
69
+ ])
70
+ path.at(0.75) # Point(2.0, 2.0)
71
+
72
+ curve.at(0.5) # Point(2.0, 1.5)
73
+ curve.derivative().at(0.5) # first derivative
74
+ left, right = curve.split(0.3)
75
+ sample = UniformSampler(200).sample(curve)
76
+
77
+ demand = PlanarSlopes(
78
+ start=Point(0, 5),
79
+ end=Point(5, 0),
80
+ start_slope=-2,
81
+ end_slope=-0.3,
82
+ ).build()
83
+ ```
84
+
85
+ The engine supports arbitrary degree and dimension. `Point`, `Vector`,
86
+ `PointSet`, control polygons, and sampling results are immutable value objects.
87
+
88
+ `CubicBezierSegment` is the primary renderer-facing value object. It exposes
89
+ `p0`, `p1`, `p2`, `p3`, tight axis-aligned bounds, and cubic-preserving split
90
+ and reversal operations. Existing `BezierCurve.cubic(...)` code remains valid;
91
+ use `CubicBezierSegment.from_curve(curve)` to migrate without changing any
92
+ control point, or `segment.as_curve()` when a degree-generic API is required.
93
+
94
+ `PiecewiseBezier` divides `[0, 1]` uniformly across the segments of a single
95
+ subpath. Closure is metadata and never inserts a hidden closing segment.
96
+ `PiecewiseBezier.compound(...)` preserves independent subpaths for fills and
97
+ holes; evaluate and split each subpath independently because a compound path
98
+ has no single continuous parameterization. Reversal preserves each subpath;
99
+ splitting is defined only for a single open subpath.
100
+
101
+ Linear and quadratic inputs can be elevated without changing their geometry:
102
+
103
+ ```python
104
+ from bezierkit.bezier import to_cubic
105
+
106
+ cubic = to_cubic(BezierCurve.quadratic(
107
+ Point(0, 0), Point(3, 6), Point(9, 0)
108
+ ))
109
+ ```
110
+
111
+ Batch evaluation uses a vectorized de Casteljau implementation by default.
112
+ `BernsteinEvaluator` is also available from `bezierkit.bezier.evaluation` for
113
+ explicit low-degree throughput tradeoffs; see `benchmarks/` for the
114
+ reproducible 400, 10,000, and 100,000-value benchmark.
115
+
116
+ ## Interpolation, fitting, and level sets
117
+
118
+ ```python
119
+ from bezierkit.fitting import fit_graph
120
+ from bezierkit.implicit import trace_implicit
121
+ from bezierkit.interpolation import graph_hermite
122
+
123
+ segment = graph_hermite(
124
+ x0=1, x1=1.5,
125
+ y0=4, y1=8 / 3,
126
+ m0=-8, m1=-32 / 9,
127
+ )
128
+
129
+ path = fit_graph(
130
+ lambda x: 4 / x**2,
131
+ lambda x: -8 / x**3,
132
+ x0=0.8, x1=3,
133
+ tolerance=1e-3,
134
+ )
135
+
136
+ contours = trace_implicit(
137
+ lambda x, y: x**2 * y,
138
+ levels=[1, 2, 4],
139
+ viewport=(0.5, 4, 0, 6),
140
+ resolution=(121, 121),
141
+ tolerance=0.01,
142
+ gradient=lambda x, y: (2 * x * y, x**2),
143
+ )
144
+ ```
145
+
146
+ Adaptive fitting reports a measured Euclidean error on every segment and
147
+ raises `ToleranceNotMet` if configured limits prevent the requested tolerance.
148
+ Implicit tracing preserves disconnected and closed components as independent
149
+ paths and uses parametric gradient tangents, including at vertical tangencies.
150
+ The derivations, error bound, continuity conditions, and sampling limitations
151
+ are documented in `docs/math/interpolation.md`.
152
+
153
+ ## Exporters and adapters
154
+
155
+ ```python
156
+ from bezierkit.export.json import dumps, loads
157
+ from bezierkit.export.svg import to_svg_path_data
158
+ from bezierkit.export.tikz import to_tikz
159
+
160
+ json_document = dumps(path, metadata={"name": "indifference-curve"})
161
+ same_path = loads(json_document).path
162
+ svg_path_data = to_svg_path_data(path, precision=5)
163
+ tikz = to_tikz(path, precision=5, options="thick")
164
+ ```
165
+
166
+ TikZ uses native `.. controls ... and ... ..` commands and SVG uses native
167
+ `C` commands; neither exporter flattens cubic geometry or chooses a theme.
168
+ The versioned JSON schema preserves all controls, subpaths, closure, dimension,
169
+ and caller metadata.
170
+
171
+ Matplotlib interoperability is optional:
172
+
173
+ ```bash
174
+ uv add "bezierkit[matplotlib]"
175
+ ```
176
+
177
+ ```python
178
+ from bezierkit.adapters.matplotlib import from_path, to_path
179
+
180
+ geometry = from_path(matplotlib_path, transform=affine_transform)
181
+ round_trip = to_path(geometry)
182
+ ```
183
+
184
+ Affine transforms preserve controls exactly. Non-affine transforms require
185
+ the explicit `approximate_path(..., tolerance=...)` API. Format guarantees and
186
+ round-trip tolerances are documented in `docs/exporters.md`.
187
+
188
+ ## Command-line interface
189
+
190
+ Run `bezierkit` directly if it's installed in your active environment, or
191
+ prefix every command with `uv run` (e.g. `uv run bezierkit --help`) when
192
+ working inside a uv project without activating the venv.
193
+
194
+ Evaluate a curve or derivative:
195
+
196
+ ```bash
197
+ bezierkit evaluate \
198
+ --points "0,0" --points "1,2" --points "3,2" --points "4,0" \
199
+ --t 0:1:0.25 --order 1 --json
200
+ ```
201
+
202
+ Sample as CSV:
203
+
204
+ ```bash
205
+ bezierkit sample \
206
+ --points "0,0" --points "1,2" --points "3,2" --points "4,0" \
207
+ --count 50 --format csv > curve.csv
208
+ ```
209
+
210
+ Construct a curve from planar slopes and pipe its control points directly into
211
+ another command:
212
+
213
+ ```bash
214
+ bezierkit construct slopes \
215
+ --start "0,5" --end "5,0" \
216
+ --start-slope -2 --end-slope -0.3 \
217
+ | bezierkit sample --count 20 --format json
218
+ ```
219
+
220
+ Run `bezierkit --help` for the complete command tree. Expected domain and input
221
+ errors are printed without a traceback; add the global `--debug` option before
222
+ the subcommand when diagnosing unexpected failures.
223
+
224
+ ## Scope
225
+
226
+ The 0.5 release adds stable JSON geometry, native TikZ and SVG cubic output,
227
+ and optional Matplotlib Path interoperability. Rendering style and diagram
228
+ semantics remain intentionally outside this package. Intersections, B-splines,
229
+ and NURBS are future work.