A tool for turning a repetitive dimensional-inspection workflow into a program you can re-run.
Ingest spatial coordinates, construct geometry on them — best-fit circles, datum alignments, intersections, dimensions — and the construction is saved as a recipe that replays against any other dataset. Recipes produce measurements checked two ways at once: against explicit tolerances, and against the statistical history of every comparable part measured before. Results export as a text report and as a DXF drawing any CAD package can open.
Built as a portfolio project. It is a general-purpose geometry/metrology engine — it contains no proprietary data or processes, and the demo part is entirely synthetic.
A lot of coordinate work is repetitive by nature: the same construction, done the same way, on dataset after dataset. Fit a feature to these points, measure it, check it against the drawing. The method is identical every time; only the numbers change. Done by hand in general CAD, it is slow, and each repetition is a fresh chance to make a different mistake.
Two things make it worse in practice. The datasets are rarely named consistently — the same
physical feature arrives as BOLT_1, bolt-1, or Bolt 1 depending on who exported it —
so even a saved routine breaks on the next file. And a check that only looks at size will
pass a part whose form is wrong.
DimeNgin addresses both. You do the construction once, save it as a recipe, and run it against every subsequent dataset — with label matching that tolerates those naming differences and refuses to guess when a match is genuinely ambiguous, and with form error (roundness, flatness, straightness) treated as a first-class measurement alongside size.
Most tools would record what you did as a list of clicks — a diary: "clicked point 47, clicked point 92, made a midpoint." A diary is useless on the next part, because point 47 means nothing there.
DimeNgin records it the way a cookbook records a meal: as steps with blanks where the ingredients go. "Midpoint of [a] and [b]", plus a separate note of what fills the blanks right now. Because the steps and the ingredients are stored separately, running the same work on new data is just refilling the blanks.
That one decision collapses five features into one mechanism: recording, replay, editing, batch processing, and tracing where a number came from are all the same thing. The architecture decision record explains why in full.
flowchart LR
CSV["Coordinate file<br/><small>CSV · PNEZD · whatever</small>"]
subgraph ING [" 1 · Import "]
MAP["Map the columns<br/>validate · preview in 3D"]
end
DATA[("Dataset<br/><small>stored in metres</small>")]
subgraph BUILD [" 2 · Construct "]
EDIT["Add steps<br/>from a menu"]
REC["or pick points<br/>and record"]
end
RECIPE[["Recipe<br/><small>versioned · replayable</small>"]]
subgraph RUN [" 3 · Run "]
BIND["Bind inputs<br/>to this dataset"]
EVAL["Evaluate<br/>the graph"]
end
DIMS["Dimensions<br/><small>measured + judged</small>"]
SPEC["vs tolerance<br/><small>pass · marginal · fail</small>"]
HIST["vs history<br/><small>typical · unusual</small>"]
TXT["Text report"]
DXF["DXF drawing"]
HEAT["Deviation heat map"]
CSV --> MAP --> DATA
DATA --> EDIT & REC
EDIT & REC --> RECIPE
RECIPE --> BIND --> EVAL --> DIMS
DATA -.->|"any other dataset"| BIND
DIMS --> SPEC & HIST
SPEC --> TXT & DXF
HIST --> HEAT
classDef store fill:#0f766e,stroke:#14b8a6,color:#f0fdfa
classDef recipe fill:#4338ca,stroke:#818cf8,color:#eef2ff
classDef output fill:#7c2d12,stroke:#fb923c,color:#fff7ed
class DATA store
class RECIPE recipe
class TXT,DXF,HEAT output
The dotted arrow is the whole point: the recipe is written once and bound to a different dataset every time it runs.
- Import a coordinate file (CSV, PNEZD survey format, whatever) — with a live 3D preview and validation that catches mirrored axes, mistyped coordinates, and duplicate labels before anything is saved.
- Construct geometry — either by adding steps from a menu, or by picking points in the 3D view and letting it record the steps for you (each picked point is automatically promoted to a reusable recipe input).
- Save the construction as a versioned recipe in a "cookbook".
- Run it against one dataset or a whole batch. It binds the recipe's inputs to the new data, refuses to guess when it cannot, and evaluates everything it can.
- Judge each measurement against its tolerance and against the history of that part type across every project — so an in-tolerance-but-abnormal part gets flagged, not waved through.
- Export a text report and a DXF drawing, the latter layered so a reviewer can see which points were measured versus entered by hand.
examples/demo/ contains six synthetic inspections of an invented mounting plate. Two
carry a deliberate defect, so the tool is seen catching something rather than only passing
everything:
| Plate | Result | Why |
|---|---|---|
| plate-01…04 | Pass | Radius ≈ 30.00 mm, roundness ≈ 0.003 mm — clean parts |
| plate-05 | Fail | Bolt circle machined oversized — radius 30.055 mm, past the ±0.05 tolerance |
| plate-06 | Fail | One hole out of position — radius passes, but roundness fails at 0.071 mm, and the deviation heat map points straight at the bad hole |
plate-06 is the one worth dwelling on: its size is in tolerance but its form is not. A
size-only check passes it; the form-error check catches it. That is exactly the kind of
subtle defect a repetitive manual routine tends to miss.
To see it: start the app (below), then
python examples/demo/generate.py # writes the six CSVs (already committed)
python examples/demo/seed.py # loads them and runs the inspection recipeand open the app. The recipe, the runs, the failing dimensions, the heat map, and the part-type history are all populated.
You need: Python 3.12+ and Node 18+. Nothing else — no database to install, no Docker, no cloud account. It runs entirely on your machine.
It is two processes: the Python API, and the Vite dev server that serves the UI and proxies
/api to the backend. Both need to be running.
macOS / Linux
cd backend
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/python -m uvicorn dimengin.main:app --reloadWindows
cd backend
python -m venv .venv
.venv\Scripts\pip install -e ".[dev]"
.venv\Scripts\python -m uvicorn dimengin.main:app --reloadLeave it running. You should see Uvicorn running on http://127.0.0.1:8000.
Using uv?
uv venv && uv pip install -e ".[dev]"replaces the first two lines.
cd frontend
npm install
npm run devOpen http://localhost:5173. You should see the DimeNgin shell with an empty sidebar.
With the backend running:
python examples/demo/seed.pyThis imports six synthetic inspections, saves the recipe, and runs it against each — so the app has something in it immediately. Then look at:
- Cookbook → Mounting plate inspection — open the recipe, pick a plate, press Run
- plate-06's run — a failing dimension, and the Deviation heat map tab showing exactly which hole is out of position
- Part types → Mounting plate A — the historic distribution across all six plates
- Interactive API documentation is at http://127.0.0.1:8000/docs — every endpoint, browsable and callable.
- Data lives in
~/.dimengin(Windows:C:\Users\<you>\.dimengin). Delete that folder to start completely fresh. Override the location with theDIMENGIN_DATA_DIRenvironment variable. - Port already in use? Something else is on 8000 or 5173. Stop it, or run the backend
with
--port 8001and update the proxy target infrontend/vite.config.ts. - Blank page or API errors in the browser? The backend is not running, or is on a different port. The frontend needs both.
cd backend && .venv/bin/python -m pytest # ~400 tests, a few seconds
cd frontend && npm run build # typecheck + production build| Layer | |
|---|---|
geom/ |
Pure numpy geometry — primitives, best-fits, intersections, 3-2-1/Kabsch alignment. Imports nothing else, so every geometric claim is testable with numbers alone. |
graph/ |
The parametric engine: a recipe is a directed acyclic graph of operations, evaluated in dependency order with content-hash caching and structured failure propagation. |
cookbook/ |
Binding recipes to datasets, running them, recording results. The only layer spanning both the engine and the database. |
dimensions/ stats/ report/ export/ |
Tolerance verdicts, historic statistics, templated reports, DXF. |
store/ |
SQLAlchemy over SQLite, behind repositories — the only layer that knows a database exists. |
frontend/ |
React + TypeScript + three.js. Renders and picks; computes nothing that reaches a report. |
The single most important structural rule: layers point downward only. The geometry at the bottom knows nothing about databases, the web, or recipes, which is why it is covered by hundreds of fast, dependency-free tests.
Written first, not last — the reasoning is as much the deliverable as the code.
| Document | What it covers |
|---|---|
| docs/CODE_TOUR.md | Start here. Plain-English walkthrough following one file from import to finished measurement. |
| docs/ARCHITECTURE.md | System shape, layer rules, evaluation model. |
| docs/DECISIONS.md | 20 architecture decision records — every significant choice, the alternatives rejected, and the cost. |
| docs/FAILURES.md | Every real bug in this project, how it hid, and the rule it produced. |
| docs/GLOSSARY.md | Domain and code vocabulary. |
DECISIONS argues, ARCHITECTURE maps, CODE_TOUR narrates, FAILURES confesses.
Correct number > no number with a clear reason > confident wrong number. Every over-cautious-looking decision follows from this ordering. The importer refuses to guess axis conventions. Recipe binding halts and asks rather than picking a plausible point. Two lines that don't quite meet return the gap between them, not an invented intersection.
Historic "normal" is defined robustly. A part is judged abnormal against the median and a robust spread of its history, never the mean and standard deviation — because defining "normal" with the mean lets one bad past measurement redefine it. The outlier must never help define what an outlier is.
Provenance survives everything. Measured, hand-entered, and hand-edited points stay distinguishable forever, and the distinction flows all the way to the report. When a number is disputed months later, the software already knows where it came from.
The idea came from working with machine control systems, where I ran the same workflow over the same kinds of point sets, day after day. The points were named differently every time. Had they been standardised, the work would have been trivial — and even without standardisation, it was clearly a routine a machine should be doing rather than a person.
That experience is why two features exist that might otherwise look like over-engineering: the three-tier label binding (exact, then case-insensitive, then punctuation-normalised, then a rule — and a refusal to guess when two candidates match equally well), and the insistence that a recipe declares its inputs up front so a batch run can verify every dataset before computing anything.
This project was built with heavy use of an AI coding assistant (Claude), used as a pair-programmer and implementation accelerator. The architecture, the design decisions, and the trade-offs were mine to make and to defend — which is precisely why every non-obvious one is written down in docs/DECISIONS.md, with the alternatives that were considered and rejected. The AI wrote a great deal of the code; the decisions about what to build and why are the part that is worth reviewing, and they are documented so they can be.
Python 3.13 · FastAPI · SQLAlchemy · SQLite · numpy / scipy · ezdxf — React · TypeScript · three.js · Vite.
Feature-complete for its intended scope: import, construction (menu editor and interactive recording), the cookbook, replay and batch, dimensions with spec and historic verdicts, reports, DXF, deviation heat maps, and historic statistics. The one deferred area is native point-cloud support (millions of points), which would require a different storage and rendering strategy and is a project in its own right.