Skip to content

Switch packaging to uv; enable Python 3.14 via build123d dev branch#9

Open
bitranox wants to merge 8 commits into
PaulBone:mainfrom
bitranox:py314compat
Open

Switch packaging to uv; enable Python 3.14 via build123d dev branch#9
bitranox wants to merge 8 commits into
PaulBone:mainfrom
bitranox:py314compat

Conversation

@bitranox
Copy link
Copy Markdown

@bitranox bitranox commented Apr 26, 2026

Summary

This branch covers four loosely-related areas of work:

  1. Packaging — migrate from poetry/pipx to uv.
  2. Python version support — make the project work on Python 3.13 and 3.14, where released build123d 0.10.0 currently can't even install.
  3. Bug fixes — three latent bugs that were unrelated to the migration but blocked the test suite or the gfpin CLI entirely.
  4. New feature--no-magnet flag on gfbase and gfbin, plus paired Bash + PowerShell recipe launcher scripts for every tool.

Verified: 26/26 tests pass on Python 3.12 (released build123d 0.10.0), Python 3.13 (build123d git dev branch) and Python 3.14 (build123d git dev branch).


1. Packaging: poetry → uv

  • Replace the [tool.poetry.*] blocks with PEP 621 [project] metadata and switch the build backend to hatchling.
  • SPDX License-Expression: CC-BY-NC-SA-4.0 instead of the free-text License: field.
  • Test deps moved to a PEP 735 [dependency-groups] table (uv run --group test pytest).
  • [project.scripts] for gfpin / gfbin / gfbase / gfedge.
  • poetry.lock removed; uv.lock not committed (added to .gitignore).
  • README install section rewritten:
    • How to install uv on Linux/macOS and on Windows (one-liner each).
    • Run without installing: uvx --from gfthings gfbin -h — one-shot, no global state, no pipx install needed.
    • uv tool install gfthings for a persistent CLI install (the equivalent of pipx install).
    • uv sync / uv run developer workflow (incl. uv run --group test pytest).
  • Wheel-metadata-checked: the built wheel only declares Requires-Dist: build123d>=0.10.0; uv-only metadata is not baked in, so the project remains PyPI-publishable.

2. Python 3.13 / 3.14 support

The released build123d 0.10.0 transitively pulls vtk, which has no Python 3.13 or 3.14 wheels, so a plain install fails on those interpreters regardless of any of our other changes. To unblock 3.13 / 3.14 the project routes build123d through its upstream dev branch (which depends on cadquery-ocp-novtk and skips vtk entirely) using a marker-conditional [tool.uv.sources] entry:

[tool.uv.sources]
build123d = [
    { git = "https://github.com/gumyr/build123d.git", branch = "dev",
      marker = "python_version >= '3.13'" },
]

Python 3.10–3.12 keep using the released wheel from PyPI.

The dev branch has small API drifts from 0.10.0 that the existing gfthings code tripped on. Each one is fixed with comprehensive inline comments explaining the WHY:

  • GFProfile.py — sketch plane orientation. Plane(origin, z_dir) on the dev branch picks a different default x_dir when z_dir is exactly axis-aligned vs. only nearly so. For non-square paths (e.g. width=1, depth=2) path%0 returns exactly (-1, 0, 0) and the resulting y_dir flips, sending the swept profile along world Y instead of Z and collapsing GFProfile to volume 0. Fix: pin x_dir explicitly so the polyline's local +Y always lands on world +Z.
  • Bin.py — cavity-floor face selection. When the cavity carve depth equals the box height (1×1 bins), the cavity floor lands exactly on the BinBase top face. The dev-branch boolean leaves two coplanar faces at that Z (the actual cavity floor at ±19.55 and the BinBase top at ±20.75 with a near-degenerate inner edge ring). sort_by(Axis.Z)[-2] picks one or the other depending on internal sort stability, which varies with bin height (h=4/h=6 happen to pick the cavity floor; h=5/h=8 pick BinBase top). When BinBase top is picked, two things break: inner_front_centre lands at Y=−20.75 (bin outer wall) instead of Y=−19.55 (cavity inner wall) — the scoop is then positioned 1.2 mm too far back, with part of it embedded in the wall material — and the floor fillet fails outright because of the degenerate inner edge ring. Fix: select the smallest-area XY face at the cavity-floor Z; that's reliably the cavity floor in every case.
  • Bin.py — X-coordinate snap. The midpoint of the front-most X-axis edge can land at X = −2e-9 due to symmetric construction. That exact magnitude/sign, when fed into Locations(...) for the subsequent Scoop placement, triggers an OCCT boolean-ADD bug on the dev branch: the resulting solid is just the scoop alone (the entire bin gets dropped). Other tiny X values (0, +1e-9, −1e-12) all work fine. Fix: snap any sub-µm X value to zero.

3. Latent bug fixes (independent of the dev-branch work)

  • gfthings/gfbin.py:25SyntaxWarning: invalid escape sequence '\*'. Python 3.14 raises this every run. The literal asterisk in the --loop help string didn't need escaping. One-character fix.
  • gfthings/gfpin.pybin vs pin typo. Lines 37/39 called export_step(bin, ...) and export_stl(bin, ...) with bin (Python's built-in) instead of the local pin. The --vscode path worked, but gfpin -o file.step had never produced output.
  • gfthings/Pin.py:16TypeError: unsupported operand type(s) for +: 'float' and 'function'. plate_base_height was a constant (2.9) at the time Pin was written; commit e7296b9 turned it into a function (short: bool) -> float for the --short base feature, but Pin was not migrated. Pass False to preserve the original 2.9 mm geometry.

4. New feature: --no-magnet

Adds a --no-magnet flag to both gfbase and gfbin with the obvious meaning: keep screw functionality, skip the magnet pocket.

  • gfbase --no-magnet replaces the CounterBoreHole(screw_rad, magnet_rad, magnet_depth) (or CounterSinkHole) inside ScrewSupport with a plain Hole(screw_rad), so the base can still be screwed down but no magnet pocket is drilled.
  • gfbin --no-magnet skips both the refined side slots and the unrefined bottom holes inside BinBase. Bins have no screw holes, so the result is a plain base profile.
  • Plumbed through ScrewSupport/BaseSquare/BaseGrid (Base.py) and BinBase/Bin/FunkyBin/HalfWallBin (Bin.py) via a new magnet : bool = True parameter on each. Default is True, preserving existing behavior.
  • Tests added: test_2x2_base_no_magnet, test_1x1_base_no_magnet, test_4_holes_no_magnet, test_simple_bin_no_magnet, test_simple_bin_no_magnet_unrefined.

5. Recipe launcher scripts (.sh + .ps1)

Each tool gets a paired Bash and PowerShell launcher (gfbin.sh / gfbin.ps1, gfbase.sh / gfbase.ps1, gfedge.sh / gfedge.ps1, gfpin.sh / gfpin.ps1):

  • The CLI parameters are declared as variables at the top of the script.
  • The output filename is auto-derived from the parameters, so a tweak like NoMagnet=true adds _nomagnet to the name and produces a uniquely-named sibling without clobbering anything.
  • Each script just prints the equivalent uvx --from <git URL> <tool> ... command and runs it.

The Bash and PowerShell versions take exactly the same variable names and produce identical CAD output, so a recipe is portable between Linux/macOS and Windows.

Workflow (from the new README section):

cp gfbin.sh   screws-bin-2x2x4.sh        # Linux / macOS  (or Copy-Item .ps1 on Windows)
$EDITOR screws-bin-2x2x4.sh              # tweak the variables
./screws-bin-2x2x4.sh                    # writes screws-bin-2x2x4-derived-name.step

Each script also has a NOTE: block above the GfthingsSource URL pointing out that the URL is currently pinned to the fork's py314compat branch because the 3.13 / 3.14 work is not yet upstream, and listing the two replacements to use after this PR merges (git+https://github.com/PaulBone/gfthings.git while waiting for a release, or just gfthings once a 3.13/3.14-compatible release is on PyPI).

6. Test infrastructure

tests/Utils.py's float_eq epsilon is loosened from 0.001 to 0.05 mm³ to accommodate sub-cubic-millimeter OCCT variance between build123d versions for the same input geometry. The comment on the helper now explains where the variance comes from.

Verified test matrix

Python build123d source tests
3.12 PyPI 0.10.0 (released) 26/26 ✓
3.13 git dev branch (via [tool.uv.sources]) 26/26 ✓
3.14 git dev branch (via [tool.uv.sources]) 26/26 ✓

3.10 / 3.11 use the same resolution path as 3.12 (released build123d 0.10.0); not exercised in CI here but should behave identically.

Note on plain pip install for 3.13 / 3.14

pip ignores [tool.uv.sources], so pip install gfthings on Python 3.13 or 3.14 will still fail to resolve build123d (it tries the released 0.10.0 wheel and hits the missing vtk wheels). uv (uv tool install, uvx --from <git URL>, or uv sync from a checkout) is the supported install path on those interpreters until upstream build123d ships a release that targets 3.13+.

bitranox and others added 8 commits April 26, 2026 10:38
…dev branch

Replace poetry/pipx setup with PEP 621 metadata + hatchling backend.
Add SPDX license expression, dependency-groups for tests, and project
scripts. Update README with uv install (Linux/Windows), uv tool install,
uvx usage, and dev workflow.

For Python 3.14, depend on build123d's upstream dev branch via a
marker-conditional [tool.uv.sources] entry; 3.10-3.13 keep the PyPI
release. Drop poetry.lock and ignore uv.lock.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Python 3.14 raises SyntaxWarning on every run for invalid escape
sequences in string literals; the literal asterisk does not need
escaping.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Pin.__init__: plate_base_height was used as a number, but became a
  function (short: bool) -> float when --short support was added in
  e7296b9. Pass False to preserve the original 2.9mm constant.
* gfpin.main: export_step/export_stl were called with `bin` (Python's
  built-in) instead of the local `pin` object.

Together these prevented `gfpin -o file.step|.stl` from ever producing
output; only the --vscode path worked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The released build123d 0.10.0 transitively pulls vtk, which has no Python
3.13 or 3.14 wheel, so a plain install fails on those interpreters. Route
3.13 and 3.14 to build123d's upstream `dev` branch (which uses
cadquery-ocp-novtk and skips vtk) via a marker-conditional [tool.uv.sources]
entry. 3.10-3.12 keep the released wheel from PyPI.

The dev branch has small API drifts from 0.10.0 that the existing gfthings
code tripped on; fix them so the test suite passes on 3.12 (released),
3.13 (dev), and 3.14 (dev):

* GFProfile: Plane(origin, z_dir) on the dev branch picks a different
  default x_dir when z_dir is exactly axis-aligned vs nearly so. For
  non-square paths (depth != width) path%0 is exactly (-1, 0, 0) and the
  resulting y_dir flips, sending the swept profile along world Y instead
  of Z and collapsing GFProfile to volume 0. Pin x_dir explicitly.

* Bin: when the cavity carve depth equals the box height, the dev branch
  leaves two coplanar faces at the cavity-floor Z (the actual cavity
  floor at +/-19.55 and the BinBase top at +/-20.75 with a near-degenerate
  inner edge ring). `sort_by(Axis.Z)[-2]` picks the wrong one at certain
  heights, which 1) places inner_front_centre at the bin's outer wall so
  the scoop overlaps wall material, and 2) makes the floor fillet fail
  outright. Select the smallest-area XY face at the cavity-floor Z
  instead.

* Bin: a -2e-9 floating-point offset on inner_front_centre.X (from
  symmetric construction) triggers an OCCT boolean-ADD bug on the dev
  branch that drops the entire bin solid when the scoop is placed at
  that point. Snap sub-um X values to zero.

* Pin: plate_base_height became a function (short: bool) -> float when
  --short bases were added; Pin still treated it as a number, raising
  TypeError on every gfpin invocation. Pass False to preserve the
  original 2.9mm geometry.

* tests/Utils.py: loosen float_eq epsilon from 0.001 to 0.05 mm^3 to
  accommodate sub-mm^3 OCCT variance between build123d versions for the
  same input geometry.

* README: expand the Python 3.13/3.14 note to explain the vtk wheel gap
  and that uv is the supported install path on those versions for now.

Verified: 21/21 tests pass on Python 3.12 + released 0.10.0, on Python
3.13 + dev branch, and on Python 3.14 + dev branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Skips the magnet pocket without removing screw functionality. For gfbase,
the counterbore (screw + magnet recess) becomes a plain through-hole at
screw_rad so the base can still be screwed down. For gfbin, both refined
side slots and unrefined bottom holes are skipped (bins have no screw
holes, so the result is a plain base profile).

Plumbed through ScrewSupport/BaseSquare/BaseGrid in Base.py and BinBase/
Bin/FunkyBin/HalfWallBin in Bin.py via a new `magnet : bool = True`
parameter on each. Default is True, preserving existing behavior; the
test suite continues to pass on Python 3.12 + released build123d 0.10.0
and Python 3.13 / 3.14 + dev branch.

CLI:
  gfbase ... --no-magnet
  gfbin  ... --no-magnet

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cover the new `magnet=False` paths:

* test_base.py: 2x2 base, 1x1 base, 2x2 base with 4 holes (counterbore
  branch in ScrewSupport replaced by a plain through-hole at screw_rad).
* test_bins.py: 1x1x4 bin (refined slots skipped) plus an unrefined
  variant — both produce the same volume because both magnet branches
  are short-circuited when magnet=False.

Reference volumes verified to match within 1e-6 between Python 3.12 +
released build123d 0.10.0 and Python 3.13 / 3.14 + dev branch.

Total: 26/26 tests pass on all three Python+build123d combos.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each tool gets a paired Bash and PowerShell launcher (gfbin, gfbase,
gfedge, gfpin). The scripts declare the CLI parameters as variables at
the top, auto-derive an output filename from those parameters (so a
tweak like NoMagnet=true produces a uniquely-named sibling without
clobbering anything), and call `uvx --from <git URL> <tool>` for you.

The .sh and .ps1 versions take exactly the same variable names and
produce identical CAD output, so a recipe is portable between Linux /
macOS and Windows. Both pin GfthingsSource to bitranox/gfthings@py314compat
so 3.13 / 3.14 users get the build123d dev branch automatically.

The .ps1 versions were added in an earlier commit; this commit adds the
matching .sh versions and a "Recipe scripts" section to the README
explaining the workflow (copy + rename, edit variables, run).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* README: add the "Recipe scripts" section explaining the copy-and-edit
  workflow, that the .sh and .ps1 versions take the same variables and
  produce identical CAD output, and listing the four covered tools.
  (This section was prepared with the previous commit but didn't make
  it into the index — folding it in here.)

* All 8 launcher scripts: add a NOTE above the `GfthingsSource` line
  pointing out that the URL targets the bitranox fork's `py314compat`
  branch only because the Python 3.13 / 3.14 compatibility work is not
  yet upstream (PaulBone#9). Once that PR is merged, the URL
  should be replaced with `git+https://github.com/PaulBone/gfthings.git`
  or, after a PyPI release that supports 3.13/3.14, just `gfthings`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant