anvil.structure
system map

You give it a 3D model of a metal part. It works out how to carve that part out of a solid block, and writes the instructions the milling machine reads. This page explains the machinery behind that, in order, without assuming you know either CAD or code.

the short version

Imagine handing someone a photograph of a finished sculpture and a rectangular block of aluminium, and asking them to write down every cut needed to get from one to the other — which cutter, in what order, from which side, how deep. That is the whole job.

Anvil does it automatically. The reason that takes ~53,000 lines is that each of those words — which, what order, which side, how deep — is a separate hard problem.

96modules
52,866lines
80on live path
779tests
159corpus parts
the spine · six stages

In the order material actually moves

Everything else hangs off this sequence. Each stage hands its output to the next, and the numbering is real — you cannot plan a cut before you know what you are cutting.

  1. 1

    Read the shape

    A CAD file is just a bag of surfaces — it says "there is a cylinder here" but not "that is a bolt hole". This stage looks at every face and decides what it is: a pocket, a hole, a boss, a chamfer. Everything downstream depends on that translation being right.

    feature_extractor.py 3,478 rule_engine_yaml.py 1,025 labeler/rules.yaml 2,812
  2. 2

    Work out how the features relate

    A hole inside a pocket must be drilled after the pocket is opened. A raised island has to survive the clearing pass around it. This stage builds the map of what depends on what, and decides how many times the part must be turned over in the vice — each turn is a "setup", and each one costs real time on a real machine.

    constraint_graph.py 1,463 setup_engine.py 545 workholding.py 772
  3. 3

    Plan the operations

    The brain. For each feature it picks the cutting tool, the speeds, how deep each pass goes, how far sideways the tool steps between passes, and what order everything happens in. This is where machining knowledge lives, and it is the largest single file in the project by a wide margin.

    strategy_solver.py 6,039 machining_rules.py 687
  4. 4

    Draw the cutter path

    The plan says "rough this pocket with a 10 mm cutter". This stage turns that into the actual line the cutter tip travels through space — thousands of coordinates, spiralling inward, stepping down, ramping in at an angle so the tool never drops straight into solid metal.

    toolpath_generator.py 4,606 polygon_utils.py 1,166
  5. 5

    Check the result

    Before anything reaches a machine, the path is simulated: material is subtracted from a virtual block and the result compared against the original model. Anything left uncut, or cut away that shouldn't be, is reported and attributed to the feature responsible. Separately, every move is graded against the machining rulebook.

    machining_grader.py 1,632 verifier.py 958 manifold_diff.py 648 deviation_attribution.py
  6. 6

    Write the machine's instructions

    Finally the path becomes G-code — the plain-text language every CNC machine speaks, a list of coordinates and feed rates. If any check in stage 5 failed hard, nothing is written and the job reports why instead.

    post_processor.py 686
two rulebooks

The difference that mattered

Anvil carries a distilled body of machining knowledge — feeds and speeds, how deep a cut may be, when to switch cutting direction, how small a tool may follow a bigger one. It lives in MACHINING_KNOWLEDGE.md, and every number in the code cites the line it came from.

Until recently that knowledge only ever ran in one direction: as an inspector, grading a finished toolpath. The planner that produced the toolpath had never read it. Both had opinions about how deep a cut should be; they agreed only where somebody had typed the same number into both places by hand.

The clearest symptom. The rulebook caps a roughing cut at one tool diameter deep. The inspector flags anything deeper. The planner asked for one and a half diameters on a particular kind of pass — so the system marked itself down, on every part, for a number it had chosen itself. Nobody could act on the complaint, because nothing connected the two ends.

RulebookRunsAnswersCount
MQ001–MQ035after a path exists what is wrong with this toolpath?31
MP001–MP015while planning what should this decision be?15

Each planning rule now names the function that must obey it, and scripts/machining_rule_consumption_audit.py fails the build if that function stops reading it, gets renamed, or is never actually called. A cited threshold has to be load-bearing, or it quietly decays into a comment — which is exactly what had happened.

One rule is still open, on purpose. "Never let a finishing cut drop into material that was never roughed out" needs a running simulation of how much metal is left at every moment, which the planner does not yet carry. It is recorded as a gap and counted, so a second one cannot appear unnoticed.

deliberately off to one side

The learning loop never touches your upload

There is a language model in this project, and it is kept out of the path your file travels. When you upload a part the pipeline is rule-based and deterministic: it either produces G-code, or it stops and tells you which piece of geometry defeated it — in seconds to minutes, not a quarter of an hour.

The model works on the other side of the wall, on its own schedule, against the pile of parts that have previously failed. What it proposes arrives as a code change for a human to review. The algorithm improves in discrete, audited steps, and nobody's upload pays for that improvement.

Your uploada STEP file
→
Rule-based pipelinedeterministic · fast
→
G-codeor a clear failure,
with full diagnostics
Failures on diskevery artifact kept
⇢
Offline learning loopits own clock,
never your request
⇢
Code changereviewed by a human,
then merged
where it runs

Three machines, one request

The browser never talks to the compute server directly — a secure page cannot load an insecure one, so requests are relayed. Merging an approved change redeploys the server automatically.

Browserthe uploader page
→
Vercelstatic host + relay
→
VPSFlask + gunicorn,
runs the pipeline
Merge to mainafter the gate passes
⇢
WebhookPOST /admin/pull
⇢
VPS redeployspulls, rebuilds, restarts
honest inventory

What is alive, and what is not

The structure holds up better than its size suggests: 80 of 96 modules are reachable from a real request. This is not a scattered collection of disconnected pieces. But nine modules — about 2,700 lines — are imported by nothing at all, including the tests.

ModuleLinesStatusWhat it is
tool_library.py201 duplicate A carefully sourced catalogue of real cutting tools — while the planner carries its own separate tool list inline. The same disease as the rulebook: good work nothing reads.
setup_orientation.py480 deleted Superseded by setup_engine.py, which declares itself the single home for this. Shared not one function with it.
approach.py7 deleted A compatibility alias for callers that no longer exist.
assistant.py543 unused—
encoder/probe.py + machining_probe.py760 experimental Neural-CAM research, not wired in.
feature_net/train.py269 experimental Model training, run by hand.
visualize.py + render_stl.py407 unused Older rendering helpers.

The one worth acting on is the tool catalogue. The others are either clearly experimental or clearly superseded — harmless once labelled. But a duplicated tool list is the kind of split that silently produces two different answers to "what cutters do we own", and that is a question the planner asks constantly.

where the code sits
DirectoryFilesLinesRole
src/7944,289 The pipeline itself — all six stages above
server/178,577 Web service, upload handling, the build gate
tests/7314,028 779 checks that run before anything ships
scripts/5612,226 Audits, corpus tools, one-off analyses
labeler/12,812 The shape-recognition rules, as data not code
docs/9— The uploader, the 3D viewer, the annotator, this page
if you only open four files
FileWhy
MACHINING_KNOWLEDGE.md The machining knowledge in plain English, with sources. Everything numeric in the code cites it.
src/machining_rules.py That knowledge as code — the only place those numbers are allowed to live.
src/strategy_solver.py Every decision about how the part gets cut.
CLAUDE.md The working rules for changing any of it.

The four automated gates — feature coverage, operation-geometry contract, recognizer signal, and rulebook consumption — each exist because something once passed every test while being quietly wrong. They are worth reading as a list of the mistakes this project has already made.