Files
cadcraft/docs/control-protocol.md
1e075f76c5 Arabic (العربية) UI translation: language switching + font fallback (#798)
* Show Thai file names and text in the interface with an installed Thai font

A drawing whose file name is Thai showed boxes in its file tab and in the
window title: the interface loaded one Latin system font (plus a CJK font for
the Chinese interface), and neither has Thai.

install_fonts now also adds an installed Thai font (Thonburi, Leelawadee UI,
Noto Sans Thai, Loma...) after the Latin one, in any interface language, since
file names and drawing text are Thai whatever the interface is in. No font is
bundled; without one installed nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add Korean interface localization and Hangul font support

* Add Arabic (العربية) interface translation

A full ar.tsv catalog for the CAD interface — every menu label, tool,
palette, dialog and status message in the repo's three-column TSV
format, written from the plain meaning of the English with the
Arabic AutoCAD vocabulary its users know (أمر، إزاحة، تدوير، قص،
استدارة، طبقة…), with the six CLDR plural forms for the counts.
Registers ar in LANGUAGES with plural_arabic and the font chain
(Noto Sans Arabic first on Linux, Segoe UI/Tahoma on Windows, Geeza
Pro on macOS) so the UI families paint Arabic.

The shell itself still lays out left-to-right: egui 0.36 has no
global direction switch and no bidi, so mirroring is left for a
later pass; this PR is the catalog, the registry entry and the
fonts.

* i18n: start the Swedish (sv) interface catalog

Header only, not registered yet: the catalog is filled and the language registered in this
branch, translated from the meaning of CADCraft's English strings and reviewed by a native
Swedish speaker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n: add Swedish (sv) interface translation

A complete 1,235-entry Swedish catalog: every key of the Spanish catalog, Run Script... and the
four menu headings (Visa, Rita, Måttsättning, Ändra). Written from the meaning of CADCraft's
English strings; no other product's localisation and no other catalog was used. AI-assisted; the
terminology and the running app were reviewed by a native speaker. Command-line keywords, system
variables and osnap tokens stay English.

- Preference::Swedish (sv); sv-SE, sv-FI and sv_SE.UTF-8 select it; one|other plurals (the
  English rule).
- sv in REPORTED; tests for locales, saved preference and plurals, menu headings, a live switch,
  dialogs and the Swedish letters in the bundled fonts. No gaps are reported for Swedish.
- ATTRIBUTION, README, docs/localization.md, docs/localization-parity.md,
  docs/control-protocol.md, the options doc comment and a contributors/people.toml entry for
  @holmbom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Chanthawat Kiriyadee <enwuft@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: huk082 <89136256+huk082@users.noreply.github.com>
Co-authored-by: hasan-sdaif <306196098+hasan-sdaif@users.noreply.github.com>
Co-authored-by: Erik Holmbom <erik.holmbom@icloud.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 06:54:40 -04:00

12 KiB

CADCraft control protocol

Start the desktop app with --control PORT (or CADCRAFT_CONTROL_PORT=PORT). It listens on 127.0.0.1:PORT for JSON lines: one request per line, one reply per line. A port given with --control wins over the variable; a bare --control keeps the variable's port.

{"id": 1, "method": "cmdline.input", "params": {"text": "circle 0,0 5"}}
{"id": 1, "ok": true, "result": {"prompt": "Command:", "output": ["Command: CIRCLE"], ...}}

Errors come back as {"id": …, "ok": false, "error": "message"}. An unknown method's error points to control.methods, which lists the methods the server answers.

The server waits up to 60 s for the app to answer a request. After that it replies with a timeout, and state tells whether sending the request again is safe:

  • {"id": …, "ok": false, "error": "timeout", "state": "not-run"}: the app had not started the request. It is cancelled and will never run, so sending it again is safe.
  • {"id": …, "ok": false, "error": "timeout", "state": "may-have-run"}: the app had already started it. It may still finish and change the drawing, and its late result is discarded. Inspect the drawing (drawing.inspect) before sending it again.

The table below describes top-level control-protocol methods: names for the request's method field. engine.commands returns a separate command catalog, not a list of additional methods.

Method Params What it does
cmdline.input {text} Type a line at the command line exactly like a user. Starts commands by name/alias and answers prompts (points x,y, @dx,dy, @d<a, distances, keywords, empty = Enter). Spaces act as Enter except at text prompts.
cmdline.script {text} Run a multi-line script (like .scr files).
cmdline.key {key: "enter"|"escape"} Press Enter or Escape. Replies like cmdline.input: the state, output, and error when Enter was refused.
cmdline.state Current prompt, keywords, accepted input kinds, history tail.
engine.execute {command, params} Run any command with JSON parameters. Never opens a dialog. command is an alias of this method, with the same parameters and result.
engine.commands Every command: id, label, menu path, shortcut, aliases, params doc, enabled.
drawing.inspect {entities?, limit?} Drawing summary (counts, extents, layers, styles, blocks, layouts, selection, undo) and entities with handles and geometry.
ui.inspect UI state, canvas rect, view, cursor and snap, performance counters; closePrompt: the drawing a "Save changes?" prompt is asking about (null when none); and actual egui keyboard focus (focus.id, focus.label, focus.role).
ui.elements {label?, role?, focused?} Observed toolbar, tab, drafting and layer-palette controls from the latest UI frame. Filters match exactly. Each entry has its real egui id, label, role, rectangle [x,y,width,height], enabled/visible/selected state and current focus.
ui.menu.list / ui.menu.invoke {command} The menu tree; invoke an item like a click (interactive).
ui.pointer {x, y, space?: "world"|"screen", button?, action?: "click"|"move"} Click in the drawing area (world coordinates by default). Right button = Enter.
ui.click / ui.move {x, y, button?, shift?, ctrl?} Real egui pointer events in screen points (reach every widget).
ui.drag {x, y, to: [x, y], button?} Real press, move and release (drag the command line's top edge, the ViewCube ring…).
ui.key / ui.text {key, cmd?, shift?, alt?} / {text} Synthetic keyboard input.
ui.set UiState fields Show/hide palettes, toolbars, command line, ViewCube…; theme: "system"|"light"|"dark" (also engine.execute with ui.theme {theme}); ui.inspect reports the theme shown (theme: light/dark). animations: "system"|"on"|"off" (also ui.animations {animations}) shows or hides animation effects (dialog and menu fades, smoothed scrolling), system following the operating system's setting; ui.inspect reports whether they show (animations: true/false). saveFormat: "dxf"|"dwg" (also ui.saveformat {format}) is the format Save As suggests for new drawings; saveVersion: "2000"…"2018" (also ui.saveversion {version}) the version it saves them in (SAVEAS also takes version and binary directly).
ui.resize {width, height} Resize the window.
ui.focus Activate the native window. Does not assign focus to a widget.
ui.screenshot {path?} PNG of the window (needs a presented frame).
ui.render {path?, width?, height?, fit?} Headless render. fit (default true) frames the whole drawing; false uses the current view. Replies {pngBase64, width, height, bytes, path?}.
app.open / app.save / app.quit {path} File operations.
control.methods Every method this server answers: {method, params, description}. Headless (cadcraft-cli mcp) lists only the methods it serves.
ping Replies "pong". A cheap health check.

To run a command listed by engine.commands, put its id in params.command and its arguments in the nested params.params object, using engine.execute as the top-level method. This includes open and document.bytes, and the UI commands in the desktop app's catalog. drawing.inspect is also an engine command, but has an explicit top-level method; a dotted command id alone does not make it a protocol method.

For example, inspect the drawing and list commands directly, then open a drawing and get its DXF bytes through engine.execute (one request per line):

{"id":2,"method":"drawing.inspect","params":{}}
{"id":3,"method":"engine.commands","params":{}}
{"id":4,"method":"engine.execute","params":{"command":"open","params":{"path":"examples/apartment.dxf"}}}
{"id":5,"method":"engine.execute","params":{"command":"document.bytes","params":{}}}

The open example assumes the app was started from the repository root; otherwise use a path to an existing drawing accessible to the app. document.bytes defaults to DXF and returns base64-encoded bytes in the result's data field. The command alias can be used identically:

{"id":6,"method":"command","params":{"command":"document.bytes","params":{}}}

Sending open or document.bytes as the top-level method returns an unknown method error: the protocol dispatches methods explicitly and does not fall through to the command registry. app.open is a top-level convenience method for opening a drawing with {path}.

The MCP server (cadcraft-cli mcp --connect 127.0.0.1:PORT) wraps this protocol; headless (cadcraft-cli mcp) it serves the same methods from an in-process session. MCP tool names are another interface: execute maps to engine.execute, list_commands to engine.commands, and the MCP open tool to app.open. See mcp.md.

ui.key queues real egui key events; its reply acknowledges the queue. Poll ui.inspect or ui.elements for the resulting focus or state before sending the next action. Use Tab and Shift-Tab to traverse controls and Enter/Space to activate them. The observed registry is not the operating system's accessibility tree and does not establish physical input or screen-reader acceptance. Unregistered controls still report their actual focus ID, with a null label/role.

The native host restores layout choices from workspace.json and saves changes atomically. It stores palette/toolset visibility, selected toolset, collapsed groups, command-line history height and related chrome choices. It does not store drawings, command buffers, dialogs or the current Start tab. The default directory is ~/Library/Application Support/CADCraft on macOS, %APPDATA%/CADCraft on Windows, and $XDG_CONFIG_HOME/cadcraft or ~/.config/cadcraft elsewhere. Set CADCRAFT_CONFIG_DIR to a private directory for an isolated profile; an explicitly empty value disables workspace reads and writes. An invalid, oversized or newer-version file is reported and left untouched, with persistence disabled for that launch. Repair or move that file before restarting to resume saving workspace choices.

Panel docking

Tool Sets, Layers, and Properties can be regrouped, split, floated within the app window, closed, and reopened. The drawing canvas stays visible and cannot be closed or turned into a tab. The document/session view is protected. Layouts, active tabs, panel visibility, and floating rectangles are saved with the app's existing UI preferences. These operations do not edit document contents.

Panel IDs: toolSets, layers, properties, externalReferences. The protected center is canvas.

ui.dock accepts operation; basic per-panel operations also require panel. The advanced resizeSplit operation below uses a split path instead of a panel. Basic operations are open, close, activate, float (with finite positive rect: [x, y, width, height]), or move (with target and zone: center|left|right|top|bottom). Center groups with the target; side zones split beside it. ui.dock.reset restores the default arrangement. Invalid moves return an error without changing the layout.

The QuickCalc palette (quickCalc) is not part of the default arrangement: QUICKCALC (ui.panel.quickcalc, Cmd+8) opens it, floating the first time.

Invoke these UI commands through engine.execute, for example {"command":"ui.dock","params":{"operation":"float","panel":"layers","rect":[100,100,320,440]}}.

Advanced ui.dock operations use the same validated transaction as pointer gestures: moveFloating updates rect, resizeSplit accepts a boolean child path and size ({"Ratio":0.5}, {"FixedFirst":280}, or {"FixedSecond":280}), setStackOpen takes open, and resizeStack takes a point height or null. A center move can include before (panel ID or null to append) for tab order. The equivalent {"action": ...} form accepts a serialized shared docking action; this is also the form emitted by the renderer. Protected-canvas and app-specific panel rules apply equally to both forms.

Command-line replies

The app and the headless session reply with the same fields.

cmdline.state, cmdline.key and cmdline.input return the command-line state:

Field Value
prompt The prompt text ("Command:" when idle).
running The id of the running command, or null.
keywords The current prompt's options.
accept The kinds of input the current prompt accepts, or null when idle.
buffer Text typed in the command line but not yet submitted (always "" headless).
history The last 20 history lines.
historyExpanded Whether the history is expanded (always false headless).

cmdline.input adds:

  • output: the history lines the input added. This includes the error message when the line was refused.
  • error: the error message, only when the line was refused (unknown command, invalid input…).

A refused line still replies "ok": true, because the text was typed. Check error.

app.open without a string path fails with "missing path" before any command runs.

Interface language

Select a language with engine.execute and the UI command ui.language:

{"id":1,"method":"engine.execute","params":{"command":"ui.language","params":{"lang":"uk"}}}

Supported values are auto, en, uk, pt-BR, de, es, cs, ru, fr, zh, it, ko, sv and ar. Chinese needs an installed CJK font, Arabic an installed Arabic font; Korean needs a font covering the Korean interface from the optional craft-fonts input or the operating system. Without a suitable font, the saved choice is retained and the result's shown is en. ui.set also accepts {"interfaceLanguage":"uk"}; ui.inspect reports the saved choice in its UI state. Invalid choices leave the current language unchanged. Menus, command labels and prompt text returned by the control channel retain their English source text. See localization.md.