Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,160 @@
__pycache__
.venv
dist
uv.lock

# ====================================
# IDEs & Editors
# ====================================
# IntelliJ, PyCharm, etc.
.idea/
# VS Code workspace settings
.vscode/
*.code-workspace

# ====================================
# Python bytecode & caches
# ====================================
# __pycache__: compiled .pyc files at any depth
# *.py[cod]: catches .pyc, .pyo, .pyd
__pycache__/
*.py[cod]
*$py.class # other compiled artifacts

# pytest, coverage, MyPy, Hypothesis
.pytest_cache/
.mypy_cache/
.pyright/
.coverage*
htmlcov/
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.tox/
.nox/

# general cache directories (any project-level caching)
.cache/
.run/
.import_linter_cache/

# ====================================
# Build & distribution artifacts
# ====================================
# source/package builds
build/
dist/
*.egg-info/ # metadata for pip installs
*.egg
*.whl
pip-wheel-metadata/
*.manifest # PyInstaller metadata
*.spec # PyInstaller spec files

# Eggs, parts, wheels, sdist, etc.
develop-eggs/
.eggs/
parts/
sdist/
wheels/
downloads/
var/

# pip logs & installer leftovers
pip-log.txt
pip-delete-this-directory.txt
.installed.cfg

# ====================================
# Virtual environments & secrets
# ====================================
# common venv folder names
env/
venv/
ENV/
.venv/
# backups, alternate names
env.bak/
venv.bak/
# dotenv files with credentials
.env

# ====================================
# Project-specific & framework files
# ====================================
# Django logs & local overrides
*.log
local_settings.py

# Flask instance folder
instance/
.webassets-cache

# Scrapy cache
.scrapy

# Sphinx docs
docs/_build/

# PyBuilder output
target/

# Jupyter notebooks checkpoints
.ipynb_checkpoints

# pyenv Python version file
.python-version

# Celery beat schedule
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Spyder IDE
.spyderproject
.spyproject

# Rope IDE
.ropeproject

# MkDocs static site folder
/site

# ====================================
# OS-specific files
# ====================================
# macOS
.DS_Store
# Windows
Thumbs.db
ehthumbs.db

# Archives
*.zip

# Thunderbird build artifact (created temporarily by pack script)
sources/manifest.json

# ====================================
# Tool-specific extras
# ====================================
# Codecov uploader script (downloaded during local uploads)
codecov.sh
codecov.xml

# Nix build result symlink
result

# Optional lockfiles (for alternative managers)
poetry.lock
pdm.lock
uv.lock

# div
# .devcontainer/ # needed for github codespaces
.claude/
CLAUDE.md
old_code/
prompts.md
121 changes: 111 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,121 @@ Gridfinity is an open source storage system best introduced
Look on [thangs](thangs.com) and
[printables](printables.com) for more compatible parts.

Setup, You can install with pipx.
Install uv
----------

Linux / macOS:

curl -LsSf https://astral.sh/uv/install.sh | sh

Windows (PowerShell):

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

See [the uv install docs](https://docs.astral.sh/uv/getting-started/installation/)
for package-manager alternatives (Homebrew, winget, scoop, pipx, etc.).

Install gfthings as a CLI tool
------------------------------

Install gfthings into an isolated, uv-managed environment and put its
commands (`gfbin`, `gfbase`, `gfedge`, `gfpin`) on your `PATH`:

uv tool install gfthings

Upgrade or remove later with:

uv tool upgrade gfthings
uv tool uninstall gfthings

Run without installing (uvx)
----------------------------

`uvx` runs a tool in a one-shot, ephemeral environment — handy for trying
things out or for CI:

uvx --from gfthings gfbin -h
uvx --from gfthings gfbin -x 2 -y 3 -o bin.step
uvx --from gfthings gfbase -x 4 -y 3 -o base.step

The `--from gfthings` is required because the command names (`gfbin`,
etc.) don't match the package name.

Recipe scripts (gfbin.sh / .ps1, gfbase.sh / .ps1, gfedge.sh / .ps1, gfpin.sh / .ps1)
-------------------------------------------------------------------------------------

For day-to-day printing it's awkward to remember the exact CLI flags
that produced a particular part. The repo ships a pair of *recipe*
launcher scripts for every tool — one Bash version (`*.sh`, for Linux
and macOS) and one PowerShell version (`*.ps1`, for Windows) — that
each declare the parameters as variables at the top, auto-derive the
output filename from those variables, and then call `uvx --from <git
URL> <tool>` for you.

Workflow:

1. Copy the recipe for the tool you want, renaming it to whatever
describes the part — e.g.

cp gfbin.sh screws-bin-2x2x4.sh # Linux / macOS
Copy-Item gfbin.ps1 .\screws-bin-2x2x4.ps1 # Windows

2. Edit the variables at the top (dimensions, scoop, magnet flags,
output format, etc.) to taste.

3. Run it:

./screws-bin-2x2x4.sh # Linux / macOS
.\screws-bin-2x2x4.ps1 # Windows

The script prints the equivalent `uvx ... gfbin ...` invocation it's
about to run, then writes a `.step` (or `.stl`) file next to the
script. The filename is built from the parameters — toggling
`NoMagnet=true` adds `_nomagnet` to the name, switching to `Format=stl`
flips the extension, etc. — so the same recipe always regenerates the
same file, and small tweaks produce uniquely-named siblings without
clobbering anything.

The Bash and PowerShell versions take exactly the same variables, so
recipes are portable: a `.sh` and `.ps1` with the same values produce
identical CAD output. Both pin `GfthingsSource` to the
`bitranox/gfthings@py314compat` branch so 3.13 / 3.14 users get the
upstream `build123d` `dev` branch automatically (see the Python 3.13 /
3.14 note below); change that line if you want to track a different
fork or branch.

Tools covered: `gfbin` (bins), `gfbase` (bases), `gfedge` (drawer-edge
fillers), `gfpin` (the small attaching pin).

Develop gfthings
----------------

Clone the repo and let uv manage the environment:

uv sync # creates .venv and installs deps + project
uv add ocp-vscode # optional: add a dependency
uv run gfbin -h # run a script from the project

sudo apt install pipx
pipx install gfthings
`uv sync` also picks up the `test` dependency group; run the suite with:

If you want to modify gfthings then build it with poetry.
uv run --group test pytest

sudo apt install pipx
pipx install poetry
poetry add ocp-vscode
poetry install
Python 3.13 / 3.14 note: the released `build123d` on PyPI (0.10.0)
transitively pulls `vtk`, which has no Python 3.13 or 3.14 wheels, so a
plain install fails on those interpreters. On 3.13 and 3.14 this project
resolves `build123d` from its upstream `dev` branch (which depends on
`cadquery-ocp-novtk` and skips the `vtk` dependency entirely) via a
marker-conditional `[tool.uv.sources]` entry — `uv sync`, `uv tool
install`, and `uvx --from .` all work from a checkout. On 3.10–3.12 the
regular PyPI release is used.

If you choose this option then you must prefix your commands with
`poetry run`
The `[tool.uv.sources]` block is uv-specific metadata and is not baked
into a built wheel, so the project remains publishable to PyPI. Plain
`pip install gfthings` on 3.13 / 3.14 will still fail (pip ignores
`[tool.uv.sources]` and tries to resolve the released wheel, which then
hits the missing `vtk` wheels) until upstream `build123d` ships a release
that also targets 3.13+ — `uv` is the supported install path on those
versions for now.

Copyright (C) Paul Bone
Distributed under: CC BY-NC-SA 4.0
Expand Down
65 changes: 65 additions & 0 deletions gfbase.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# gfbase recipe -- edit the variables below, then run: .\gfbase.ps1
# Copy this file and rename it (e.g. drawer-base-4x3.ps1) to save a recipe.
# Re-running the same script regenerates the same object.

# NOTE: this URL points at the bitranox fork's `py314compat` branch, which
# carries the not-yet-upstreamed Python 3.13 / 3.14 compatibility patches
# (PaulBone/gfthings#9). Once that PR is merged upstream, replace the
# value below with one of:
# $GfthingsSource = 'git+https://github.com/PaulBone/gfthings.git' # or @main
# $GfthingsSource = 'gfthings' # once a PyPI release ships
$GfthingsSource = 'git+https://github.com/bitranox/gfthings.git@py314compat'

# ---- Dimensions ----
$X = 4 # gridfinity units across
$Y = 4 # gridfinity units deep

# ---- Holes ----
$ScrewDiameter = 4 # mm
$MagnetDiameter = 6.2 # mm (or countersink diameter)
$MagnetDepth = 2 # mm
$ScrewHoleCount = 2 # 0, 2, or 4 per grid square
$ScrewHolePatternDrawer = $false # add 4 extra screw holes in drawer pattern
$NoMagnet = $false # skip magnet pocket (screw holes kept as plain holes)

# ---- Variant ----
$Short = $false # short variant (no screw holes)

# ---- Output ----
$Format = 'step' # 'step' or 'stl'
$Loop = $false # batch-generate variants
$VsCode = 0 # >0 = run in vscode_ocp mode on that port (no file produced)

# ---------- output filename (auto-derived from params) ----------
$tags = @()
if ($ScrewHolePatternDrawer) { $tags += 'drawer' }
if ($NoMagnet) { $tags += 'nomagnet' }
if ($Short) { $tags += 'short' }
$tagSuffix = if ($tags) { '_' + ($tags -join '_') } else { '' }
$basename = "base_${X}x${Y}_screw${ScrewDiameter}_mag${MagnetDiameter}x${MagnetDepth}_holes${ScrewHoleCount}${tagSuffix}.${Format}"
$Output = Join-Path $PSScriptRoot $basename
# To override, set $Output = 'mycustomname.step' here.

# ---------- build args ----------
$cliArgs = @(
'-x', $X, '-y', $Y,
'--screw-diameter', $ScrewDiameter,
'--magnet-diameter', $MagnetDiameter,
'--magnet-depth', $MagnetDepth,
'--screw-hole-count', $ScrewHoleCount,
'-o', $Output
)
if ($ScrewHolePatternDrawer) { $cliArgs += '--screw-hole-pattern-drawer' }
if ($NoMagnet) { $cliArgs += '--no-magnet' }
if ($Short) { $cliArgs += '--short' }
if ($Loop) { $cliArgs += '--loop' }
if ($VsCode -gt 0) { $cliArgs += @('--vscode', $VsCode) }

# ---------- invoke ----------
$inv = [System.Globalization.CultureInfo]::InvariantCulture
$displayArgs = $cliArgs | ForEach-Object {
if ($_ -is [double] -or $_ -is [single] -or $_ -is [decimal]) { ([double]$_).ToString($inv) } else { "$_" }
}
Write-Host "uvx --from $GfthingsSource gfbase $($displayArgs -join ' ')"
& uvx --from $GfthingsSource gfbase @cliArgs
if ($LASTEXITCODE -eq 0) { Write-Host "Wrote: $Output" }
Loading