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.
16 KiB
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 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
-
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.
git clone https://github.com/will123song/pyrevit-dev.git cd pyrevit-dev -
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 runspanelliftagainst a published worked example and checks the computed stress against the number the article prints.python tools/setup_machine.pyRead the output top to bottom.
PASSandSKIPlines need nothing from you; aSKIPis a deliberate non-answer (this distribution ships no knowledge layers, so the checks that need one stand down cleanly — seeknowledge/README.md), not a problem. AFAILline 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--checkto just re-verify without changing anything:python tools/setup_machine.py --check -
Register the folder with pyRevit. pyRevit tab → Settings → Custom Extension Directories → add the folder that contains
WillTools.extension(the cloned folder itself, not the.extensionfolder inside it) → Save Settings.Equivalent to this in
%APPDATA%\pyRevit\pyRevit_config.ini:[core] userextensions = ["C:\\path\\to\\pyrevit-dev"] -
Turn on the routes server — only if you want the Claude Code bridge (GOD_MOD). pyRevit → Settings → Routes → enable, port
48884. Or:[routes] enabled = true port = 48884 host = "127.0.0.1" -
Restart Revit. Not "pyRevit → Reload" — see §7.
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
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:
- Extract Design — pick the design summaries, one extraction terminal
opens per design, each writes
specs/<DESIGN>.json. - Read the spec. It is JSON on purpose. Fix anything wrong and fill in
source.reviewed_by. Nothing downstream questions the spec again. - 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.
- Edit the
knowledge.yamlfiles 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 and
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 aDisplayUnitTypefallback 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."