Skip to content

alvinr/tbs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2,558 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

The Big Shoebox Project (TBS-001)

The Big Shoebox Project (TBS-001) — Repository & Build Guide

This repository is the source for the TBS-001 engineering documentation: a giant pinhole camera built inside a 20 ft shipping container. Everything published is generated from code and text in this repo — the 2D engineering diagrams (matplotlib), the 3D SketchUp models, the cost / shopping / weight figures, and the MkDocs website.

Looking for the camera itself? This README is about running the repo (regenerating drawings, building the site, the tooling). For the project overview see project-summary.md or the live site: https://alvinr.github.io/tbs/.


Repository layout

/                      project root — engineering reports (*.md), config, this README
src/generators/        all 2D diagram generators + the tooling (lint.py, costing.py, …)
src/models/            SketchUp 3D model generators (emit Ruby, drive SketchUp)
diagrams/              generated PNGs/SVGs (output of src/generators/)
skills/                codified drawing/labeling conventions
published/             MkDocs input — auto-populated by publish.sh (gitignored, never edit)
site/                  built site output — gitignored, never commit
.githooks/             versioned git hooks (the pre-commit drift gate)
dependencies.yml       machine source: script → output-file graph (see "Tooling")
mkdocs.yml             site nav + theme           publish.sh   sync + build + deploy

CLAUDE.md holds the working conventions (drawing style, report style, the add-a-document workflow) in more depth — it's worth a read before adding content.


Prerequisites

  • Interpreter: CPython 3.9 — developed and run on 3.9.6 (the macOS system python3).

  • Packages: requirements.txt — the single, pinned dependency list (for the 2D diagram generators and the documentation site). Set up a virtual environment from it:

    python3 -m venv .venv
    .venv/bin/pip install -r requirements.txt
    # then activate it, or prefix commands with .venv/bin/
  • 3D models additionally need SketchUp + the live-send bridge — not a pip dependency (see "The 3D SketchUp models").

The drift tooling (lint.py, costing.py, facts.py, deps.py) and the SketchUp model generators use only the Python standard library, so the pre-commit hook runs on any python3 with nothing installed. Only the diagram generators and the site build need requirements.txt.


Regenerating the 2D diagrams

Each generator writes one or more PNGs into diagrams/:

python3 src/generators/generate_<name>.py        # e.g. generate_walkway_diagram.py

Spatial values come from src/generators/tbs_constants.py (the single source of truth for geometry). Don't hardcode a dimension — import the constant. After changing a constant, see "What a change cascades to" below for exactly which generators to re-run.


The 3D SketchUp models

Models live in src/models/ and emit Ruby that builds the model in a running SketchUp:

python3 src/models/generate_sketchup_model.py --save    # write the .rb to src/models/<name>.rb
python3 src/models/generate_sketchup_model.py --send    # build into the OPEN SketchUp document
  • --save is non-interactive (just writes the .rb); --send requires SketchUp open on the correct document with the live-send bridge listening — it clears and rebuilds the active model.
  • Open a blank document before --send for a new model so you don't clobber an existing .skp; then "Save As" to models/<name>.skp.
  • Commit the generator .py + the regenerated .rb + the .skp together. (Sketchfab re-uploads are a manual step.)

Models share geometry helpers and constants from generate_sketchup_model.py (imported as ov), so they stay in sync. See dependencies.yml for the model → output mapping.


Single source of truth

Numbers are computed, never transcribed — each kind of value has exactly one home:

Store Owns Consumed by
src/generators/tbs_constants.py geometry & engineering values generators, models, weight/cost scripts
src/generators/parts.py the parts registry — every purchasable item: qty, type, supplier, low/high cost, verified size, cyanotype chemistry the master BOM, every report's parts list, the dimension audit + cyanotype shopping list (block-injected); costing.py derives section totals from it
src/generators/costing.py the scenario cost model (mid + low/high budgeting bands) on top of the registry the cost tables in the docs (block-injected)
src/generators/facts.yml cross-referenced prose numbers + the regex to police them the linter
dependencies.yml the script → output-file graph the linter (cascade + validation)

parts.py is the procurement source of record; costing.py is a reconciled scenario layer. The registry generates master-shopping-list.md (the by-type BOM + supplier consolidation), every report's §Parts-List, component-dimension-audit.md §1, and the cyanotype chemistry-shopping-list.md — all wrapped in <!-- BEGIN parts:KEY --> blocks:

python3 src/generators/parts.py --inject         # rewrite the parts: doc blocks from the registry
python3 src/generators/parts.py --check          # assert each system reconciles with costing
python3 src/generators/parts.py --master         # print the by-type BOM to stdout

The invariant is lint-gated both ways: parts.py --check-blocks (docs match the registry) and costing.py --check-registry (each section total == the sum of its registry systems), so a cost can't drift between the BOM and the budget.

Cost tables in the markdown are wrapped in <!-- BEGIN costing:KEY --> / <!-- END … --> blocks and regenerated by costing.py:

python3 src/generators/costing.py --inject     # rewrite the doc cost tables from the source
python3 src/generators/costing.py --check      # assert the figures are internally consistent

facts.yml prose figures and the per-session energy budget are block-injected the same way:

python3 src/generators/facts.py --inject                    # fill the <!-- fact:KEY --> placeholders
python3 src/generators/calculate_energy_budget.py --inject  # fill the <!-- energy:KEY --> figures

The energy injector fills the <!-- BEGIN energy:KEY --> placeholders in electrical-report.md §3.1/§3.2 from calculate_energy_budget.py — the per-session, daily-roll-up, and autonomy model. It is lint-gated (the energy doc-blocks gate), so those figures cannot drift from the model. Rerun --inject after any spec change that affects power, and regenerate the electrical Sheet 1 summary panel (generate_electrical_diagram.py) to match. (daily-energy-report.md presents the same model in prose but is not yet block-injected.)


Tooling: the drift linter

src/generators/lint.py is a commit-time linter that stops document-side drift. It runs automatically before every commit via the versioned hook — enable it once per clone:

git config core.hooksPath .githooks       # one-time install; emergency bypass: git commit --no-verify

Run it by hand any time:

python3 src/generators/lint.py            # all checks (4 gates that BLOCK + 7 advisory warnings)

Gates (block the commit): costing reconciliation, costing doc-blocks match the source, fact placeholders match the registry, energy doc-blocks match calculate_energy_budget.py. Warnings (advisory): facts-registry agreement, dependencies.yml validity, table arithmetic, missing-cascade, hardwired-literal, duplicated-geometry equality.

On-demand sub-commands:

python3 src/generators/lint.py --cascade PROC_TRAY_X_R   # what re-runs if this constant changes
python3 src/generators/lint.py --literals                # full hardwired-literal backlog scan
python3 src/generators/lint.py --duplication             # compare the duplicated 3D emitters

What a change cascades to

After editing a constant, ask the tooling rather than a table:

python3 src/generators/lint.py --cascade <CONSTANT_NAME>

It greps every script that reads the constant and lists the exact PNGs / .skp / .rb each writes. If you git add a tbs_constants.py change without the regenerated outputs, the pre-commit missing-cascade check names the specific files you still owe.

component-dependency-map.md is the human rationale (component → constant registry, the subsystem × diagram matrix, per-model narratives); dependencies.yml is the machine source.


Tooling: the editorial validator

src/generators/editorial_lint.py enforces the report narrative house style codified in skills/skill_report_writing.md (the counterpart to lint.py, which guards the numbers). It scans the published reports (publish.sh MD_FILES + project-summary.md).

It is a manual step — not wired into the pre-commit hook. Run it by hand:

python3 src/generators/editorial_lint.py          # gates + advisory
python3 src/generators/editorial_lint.py --gates   # gates only (exit 1 on failure, for future CI)

Gates (deterministic, exit 1 on failure): American spelling (British → American), and every item in a Source References section carries a hyperlink. Advisory (review; never blocks): banned stale design terms (e.g. 55-gal, colonnade, Portacool Jetstream — extend _BANNED), a registry-known value restated raw that should be a <!-- fact: --> placeholder (the highest-leverage check — it auto-cascades once wrapped), mixed thousands-separator styles, and reports missing a Source References section.

Scope: it reads the report .md prose only — it can't see British spellings baked into diagram labels (inside the generator .py) or .skp models; those still need a manual sweep.


Building & deploying the site

bash publish.sh            # sync docs → published/, build, and gh-deploy to GitHub Pages
bash publish.sh --local    # sync + serve a local preview at http://127.0.0.1:8000
bash publish.sh --build    # sync + build to site/ only (no deploy)

publish.sh syncs the root .md files + diagrams/ into published/, copies project-summary.md → published/index.md (the site Home), builds with MkDocs, optionally writes the PDF brochure, and pushes the gh-pages branch. It auto-detects whether a git remote exists and falls back to --build if not. On some systems run it as PATH=/usr/bin:$PATH bash publish.sh.

Never edit published/ or site/ by hand — they are regenerated.


Adding a new document (short version)

  1. Write <name>.md in the repo root (+ a src/generators/generate_<name>.py if it needs drawings).
  2. Register it in publish.sh (MD_FILES / DIAG_FILES) and src/generators/setup_docs.py (MD_FILES / DIAG_IMAGE_FILES + the index table), and add a nav: entry to mkdocs.yml.
  3. If you added a generator/model, add its script → outputs entry to dependencies.yml.
  4. bash publish.sh to deploy; commit the script + .md + .png together.

The full checklist is in CLAUDE.md.


Commit conventions

  • Commit the related source together (script + .md + regenerated .png/.skp/.rb).
  • Stage only the files you actually changed (no git add -A).
  • The pre-commit gate must pass (or --no-verify for an explicit emergency bypass).
  • published/, site/, and .claude/ are never committed.

© 2026 Alvin Richards — released under the GNU AGPLv3. Live site: https://alvinr.github.io/tbs/

About

The Big Shoebox - A Humungous Pinhole Camera

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors