Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

How a click becomes a document edit

This page explains the shape of the system from the outside in. It is the answer to a question the how-to guides skip: why does the code look the way it does?

The facade is the contract, and it keeps no state

import FreeCAD is the product surface. Everything else — the shell, a workbench module, a test — is a client of it.

The Python package is a thin facade over a Rust core, and the facade deliberately owns nothing. Documents, objects, properties and undo all live in the core; the facade forwards. That is why you can close a document from one script and read it from another: there is no per-script model to fall out of sync.

The same property is what makes the workflow pages in this guide possible. They create documents, edit them and delete them without a window, because there is nothing window-shaped in the model.

The shell is a client, not a layer

The application shell does not link a workbench and does not reach into the document model directly. It calls a small set of Python functions (ferrocad_shell), each of which returns JSON:

The shell asks forThe functionWhat comes back
the document treemodel_tree()names, labels, type names
one object’s editorsproperties(name)rows for the property pane
what the viewport drawsstage_meshes()tessellated shapes
the actionscommands()ids, groups, enablement, check state
to run onerun_command(id)a status, or a dialog and a token

Every one of those is an ordinary call into FreeCAD or FreeCADGui. Nothing in ferrocad_shell names a workbench. That is a rule, not a style preference: it is what lets the same shell host the toy edition, a Part edition, or a future workbench without a rebuild. See docs/workbenches/actions-and-tools.md.

A click, step by step

  1. The viewport reports a pick. The shell turns it into a selection snapshot and pushes it into FreeCADGui.Selection.
  2. A command’s IsActive is evaluated against that snapshot, so the button enables or disables without anyone telling it to.
  3. Running the command returns one of a small number of things: a status, a {"view": …} request, a {"tool": …} request, or a dialog description plus a token.
  4. A dialog is data, not a callback. The shell renders the fields natively and sends the typed values back to the token. The command’s on_accept then opens the transaction.

That last point is the one people find surprising. Workbenches do not build Qt widgets in FerroCAD; they describe a form. The description is ordinary Python data, so it crosses the JSON boundary, and the window decides how it looks. This is also why a dialog can be tested with no window at all.

  1. The edit itself is one document transaction, so undo reverses the click, not a half-applied intermediate state.

Several providers, one feature

Where two modules can supply the same capability — a grid, for instance — they register as providers of a feature rather than competing silently. The registry picks one (priority, then a saved choice), and only the active backend draws. Mods_Settings switches them.

This is why the grid has more than one implementation and still one UI: the grid feature owns the command and the appearance settings, and the provider owns the drawing. docs/workbenches/mod-providers.md specifies the contract.

The consequence is a rule for anything that adds a provider: the switch that turns a feature on and off belongs to the feature, not to the provider. When two stores disagree about “on”, a toggle moves one of them and the other keeps drawing — the defect recorded in docs/tasks/grid-visibility.md.

What this buys

  • Workflows are testable without a GPU or a display. The model path is the same one a window uses; only the pixels differ.
  • The window is replaceable. A different shell can drive the same facade.
  • Documentation can be executed. The pages in this guide are generated by running the code they show, which is only possible because the model is reachable without the UI.

Where to read further