WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m14s

History-free snapshot of the private development repository at 77a82eb.
Code, docs and the public catalogue only: no plant or job knowledge
layers, no client, job or producer names (tools/scrub.py clean against
the widened term list; a second word-level grep clean). The intro deck is
left out because its slide images cannot be checked for job drawings.
This commit is contained in:
2026-09-09 08:35:15 -05:00
commit 25592fa6c2
406 changed files with 223435 additions and 0 deletions
+325
View File
@@ -0,0 +1,325 @@
# Installing WillTools
Precast engineering tooling: buttons across four ribbon panels — three
modelling/ticketing tools plus Snapshot behind **Tools**' `Modeling`
pulldown, five set-up commands behind the **Config** pulldown, two finished
and four unfinished analysis tools behind **DesignHub**, and GOD_MOD — plus a
live bridge that lets Claude Code drive a running Revit session. `README.md`
has the table of what each one does.
Developed and exercised on **Revit 2025.4 · pyRevit 6.5.3 · IronPython 2.7.12**.
Everything else in this file is about what to check when one of those differs.
---
## 1. Prerequisites
| | Needed for | Notes |
| --- | --- | --- |
| **Revit** 2021 or newer | everything | see [§6](#6-a-different-revit-version) before assuming a version works |
| **pyRevit** 4.8 or newer | everything | <https://github.com/pyrevitlabs/pyRevit/releases> |
| **Python 3.9+** on the host | `tools/*.py`, `panellift` | not needed to use the ribbon buttons themselves |
| **Claude Code CLI** | GOD_MOD button only | optional — every engineering button runs without it |
**No button that does engineering work needs the CLI, the network or the
bridge.** Design Extract reads a design packet in-process with a
regex/geometry parser and never leaves Revit. GOD_MOD is the only button
that launches the CLI, and it is the developer button.
### The Windows Store Python trap
If `python` at a terminal prints something like *"Python was not found; run
without arguments to install from the Microsoft Store"* and then exits
non-zero, that is not really Python — it is a stub Windows ships in
`WindowsApps` that only exists to point you at the Store. Every command in
this file, and `tools/setup_machine.py` itself, will fail in confusing ways
if that stub is what `python` resolves to.
Fix: install Python from <https://www.python.org/downloads/> (check "Add
python.exe to PATH" during setup), then open a **new** terminal and confirm
with `python --version` — it should print a real version number, not a
Store prompt. If both a real install and the Store stub are on `PATH`, the
Store one usually wins unless you reorder `PATH` or use the `py` launcher
instead (`py -3 tools/setup_machine.py`).
## 2. Install
1. **Clone or unzip anywhere you can write to.** A checkout under your user
folder is easiest. The path is not hardcoded anywhere; the buttons find
the folder relative to themselves.
```bash
git clone https://github.com/will123song/pyrevit-dev.git
cd pyrevit-dev
```
2. **Run the setup script.** It installs the host-side Python dependencies
(`requirements.txt`: PyYAML, pytest, pymupdf) and then *proves* the
install rather than just reporting exit codes — it runs `panellift`
against a published worked example and checks the computed stress
against the number the article prints.
```bash
python tools/setup_machine.py
```
Read the output top to bottom. `PASS` and `SKIP` lines need nothing from
you; a `SKIP` is a deliberate non-answer (this distribution ships no
knowledge layers, so the checks that need one stand down cleanly — see
`knowledge/README.md`), not a problem. A `FAIL` line names what broke and
what to try — fix the first one and re-run; failures often cascade from
one root cause. Re-run any time with `--check` to just re-verify without
changing anything:
```bash
python tools/setup_machine.py --check
```
3. **Register the folder with pyRevit.** pyRevit tab → *Settings* →
*Custom Extension Directories* → add the folder that **contains**
`WillTools.extension` (the cloned folder itself, not the `.extension`
folder inside it) → *Save Settings*.
Equivalent to this in `%APPDATA%\pyRevit\pyRevit_config.ini`:
```ini
[core]
userextensions = ["C:\\path\\to\\pyrevit-dev"]
```
4. **Turn on the routes server** — only if you want the Claude Code bridge
(GOD_MOD). pyRevit → *Settings* → *Routes* → enable, port `48884`. Or:
```ini
[routes]
enabled = true
port = 48884
host = "127.0.0.1"
```
5. **Restart Revit.** Not "pyRevit → Reload" — see [§7](#7-the-one-rule).
A **WillTools** tab appears with four panels, left to right:
| Panel | |
| --- | --- |
| **Tools** | one button, **Modeling**, with a dropdown arrow (Reinforce Suggestion, Reinforcing QC, Ticket QC), plus **Snapshot** as a large button beside it. |
| **Setup** | one button, **Config**, with a dropdown arrow: Project Config, Design Extract, Self Check, Job Recon, Takeoff. |
| **Design** | one button, **DesignHub**, with a dropdown arrow: Panel Lift Check and Panel Analysis (finished), then Beam Analysis, Connection Analysis, Gravity and Lateral, marked `(WIP)`. |
| **Lab** | **GOD_MOD**, the CLI button. |
## 3. Check the install
Open a model and press **Self Check**. It probes every assumption these tools
make — Revit version, pyRevit version, Python engine, routes, library imports,
unit conversion, whether the CLI is findable, where the data folder resolved
to, whether a published checklist is available, and whether this model
actually follows the naming conventions the checks depend on — and prints
each as OK / WARN / FAIL.
Read the FAILs top-down; they cascade. A WARN never blocks a run, and on a
knowledge-less install several rows are *expected* to read WARN — that is
the "checks stand down" state, not a broken install.
## 4. Try it with no Revit at all
```bash
python -m panellift examples/example-panel.job.json -o report.html
```
That runs the PCI Chapter 8 handling check against a published worked
example (the ASPIRE Winter 2019 article) and writes an HTML report. It needs
no Revit, no model, and no knowledge layer — a good first thing to run right
after `tools/setup_machine.py` to confirm the arithmetic reaches you intact.
## 5. Using it on a real job
Per model, in order:
1. **Extract Design** — pick the design summaries, one extraction terminal
opens per design, each writes `specs/<DESIGN>.json`.
2. **Read the spec.** It is JSON on purpose. Fix anything wrong and fill in
`source.reviewed_by`. Nothing downstream questions the spec again.
3. **Ticket QC** — checks every piece of those designs against the spec.
**Needs a knowledge layer with the published checklist** — see below;
without one it refuses to open with a one-line reason.
4. Edit the `knowledge.yaml` files as you learn the job — see the layers
below for which one. They are re-read on every run — **no restart needed
for knowledge or spec edits.**
Everything lands in `_ticketqc/` **beside the model file**:
```
<model folder>/_ticketqc/
evidence/<DESIGN>.json raw scrape, no interpretation
specs/<DESIGN>.json the reviewable spec <-- read this
knowledge.yaml your hand-written rules
log/<DESIGN>.txt what the extractor printed
```
If the model is not saved, or its folder is read-only, it falls back to
`%APPDATA%\pyrevit-ticketqc\<model title>\`. Self Check tells you which.
### The knowledge layers, and why this distribution has none of them
Knowledge is not one file. It is read in layers, each overriding the one
before it key by key, and the split is by **how long the fact stays true**:
| layer | where | what belongs there |
| --- | --- | --- |
| defaults | `lib/vibe/knowledge.py` — in this repo | what the code assumes when nobody has said |
| catalogue | `knowledge/catalogue.yaml` — **not shipped** | published hardware ratings, plus the master shop-ticket checklist |
| **plant** | `%APPDATA%\WillTools\knowledge.yaml` — **not shipped** | naming conventions, casting-bed rails, **parameter names**, which catalogue item each part number is, rigging mode and dunnage position |
| **job** | `<model folder>/_ticketqc/knowledge.yaml` — **not shipped** | tolerances, and anything where this job departs from the plant |
| **project config** | wherever you put it — see below | the whole configuration, pinned to one job |
This distribution ships **none of the last four**, deliberately — see
[`knowledge/README.md`](knowledge/README.md) and
[`knowledge/catalogue.yaml.template`](knowledge/catalogue.yaml.template) for
the shape of what a real layer looks like, with every value blanked out.
Everything on **Tools** and **Setup** except Ticket QC runs without one,
reading only `knowledge.DEFAULTS`; Self Check reports every plant-specific
parameter role as unmapped until real layers are wired in. **Ask the owner**
for real layers when you have a job to run for real — see the README's
"What to ask the owner for."
Copying a job file forward to the next job should be nearly empty work — if
you find yourself copying a lot, it belongs in the plant layer. Set
`WILLTOOLS_KNOWLEDGE` to a full path to put the plant layer on a share so a
whole office reads one file. (`tools/setup_machine.py` deliberately leaves
this variable unset on a knowledge-less install — see its own comments if
you are wiring one in by hand rather than through the script.)
Handling decisions are plant knowledge too: `handling.stages[].rig_mode`
(`rolling` or `static`, blank inherits the global mode) and
`handling.dunnage.mode` (`lifter_rows` or `fraction`, with `fraction` such as
`0.2` for supports at 0.2L from each end). A plant that has not set dunnage
still gets transportation as a lift, and the report labels it as approximate.
Self Check prints which layers it found, and lists any parameter role still
unmapped.
### Pinning a configuration to a job
The **Project Config** button writes every layer above, merged, into one file
wherever you choose — the job folder on the share, usually, next to the rest
of the job's paperwork. From then on the model runs on that file, and it is
the last word: edit it and the next run uses what you wrote.
A one-line pointer at `<model folder>/_ticketqc/active-config.txt` records
where the file went. Press the button again to refresh the file from the
layers underneath, or to drop back to them.
Two things to know before you use it:
- **It freezes the plant layer.** The merged copy in the file contains the
plant's settings as values of its own, so while the file is live, fixing
something in the plant file no longer reaches this job. That is the point of
pinning, and it is also how a plant-wide correction quietly fails to arrive.
- **Do not move or delete the file without clearing the pointer.** Every
button refuses to run rather than falling back, because a run on the layered
files when you meant the pinned ones looks entirely normal and is not the
configuration you set.
A new model has no pointer, so it starts from the layered files and gets its
own configuration the first time the button is pressed there.
### Nothing is hard-coded, including public facts
The obvious things stay out of the code: client names, job names, parameter
names. Less obviously, **so do facts that are perfectly public.** A lifting
insert's rated working load is printed in the manufacturer's catalogue and is
no secret — but written into a `.py` file it becomes a number nobody can
correct without a release, and it will be wrong eventually.
So a rating is a lookup, and it takes two hops, because a plant models its own
part number rather than the manufacturer's designation:
```
family name in the model
-> lifters.catalogue_alias plant layer: P000 -> SAMPLE_ANCHOR_4T
-> catalogue.inserts product data: SAMPLE_ANCHOR_4T -> 8000 lb
```
Each hop fails differently and is fixed in a different place, so the check
says which one is missing rather than reporting a bare "unknown". The same
shape applies to anything else with a published number behind a local name.
## 6. A different Revit version
The code is written against the stable parts of the API and guards the two
places Autodesk moved the floor:
- **`UnitTypeId`** (Revit 2021+) with a `DisplayUnitType` fallback for 2020 and
older, resolved once at import so an old host cannot break the whole library.
- **`ElementId.IntegerValue`** → **`.Value`** (Revit 2024+), read defensively
at every site.
Also version-proofed: the `/exec` snippet namespace skips API types that a
given release does not have, rather than failing to register.
**Not verified anywhere but 2025.** If you move to 2023 or 2026, press Self
Check first — it reports the host version as a WARN and exercises the imports
and a unit round-trip, which is where a version problem shows up first.
### A different Python engine
pyRevit chooses the interpreter. This extension is written in the intersection
of Python 2 and 3 — no f-strings, no `print` statement, no implicit integer
division, no `pathlib`, and `unicode`/`basestring` resolved in
`lib/vibe/compat.py` — so IronPython 2.7, IronPython 3 and CPython 3 should all
load it. Only IronPython 2.7 has actually been run in anger; Self Check warns
on anything else.
## 7. The one rule
**Never use pyRevit → Reload. Restart Revit.**
pyRevit's routes handler is owned by the engine that first loaded it. A reload
tears that engine down and leaves the handler pointing at the corpse; the next
bridge request runs against a dead engine and takes Revit down with it —
an unhandled exception off the main thread, which Revit cannot catch.
There is no reload endpoint, `rvt.py` refuses to run after it sees two loads in
one Revit process, and a restart picks up new buttons just as well.
Restart Revit after: installing, adding or renaming a button, or adding an icon.
No restart needed after: editing a `script.py`, a spec, or `knowledge.yaml`.
## 8. What is in this distribution
```
pyrevit-dev/
├── INSTALL.md this file
├── README.md what the tool is, what needs knowledge
├── CONTRIBUTING.md tests, the no-client-data rule, PRs
├── examples/example-panel.job.json a published worked example, try this first
├── docs/ reference material and design docs
├── knowledge/README.md why this folder is otherwise empty
├── knowledge/catalogue.yaml.template the shape of the layer that isn't here
├── tools/setup_machine.py run this first
├── tools/rvt.py bridge client (host CPython 3)
├── panellift/ the handling-check engine, its own test suite
└── WillTools.extension/
├── startup.py registers the bridge routes
├── lib/vibe/ helpers, compat, designspec, knowledge,
│ ticketqc, ticketchecks, aiextract
└── WillTools.tab/ the ribbon, four panels left to right:
├── Tools.panel/ Modeling.pulldown/ — Reinforce
│ Suggestion · Reinforcing QC · Ticket QC —
│ plus Snapshot as the one remaining large
│ button
├── Setup.panel/ Config.pulldown/ — Project Config ·
│ Design Extract · Self Check · Job Recon ·
│ Takeoff
├── Design.panel/ DesignHub.pulldown/ — Panel Lift Check ·
│ Panel Analysis (finished, grouped here on
│ the owner's own instruction), then Beam
│ Analysis (WIP) · Connection Analysis (WIP)
│ · Gravity (WIP) · Lateral (WIP)
└── Lab.panel/ GOD_MOD
```
No model data or knowledge ships with it. `_ticketqc/` folders stay with
their models; knowledge layers come from the owner, on request — see the
README's "What to ask the owner for."