Checked 2026-09-15 against AIOrchestrator/API/TOOL_CHECKLIST.md (current version incl. the
"Completion — compliance file" point). FreeCADTool is a pure-managed .NET agent tool that drives
an external FreeCAD instance over a local RPC bridge; it has no native or non-.NET dependencies of
its own. It sets itself up: on first use it auto-starts a headless FreeCAD + the bundled bridge, and
auto-installs the FCGear add-on on the first gear request, surfacing setup progress/failures as
localized OS notifications via the shared AIOrchestrator.SystemNotifier. Verified end-to-end
against a live headless FreeCAD 1.1.3 (41/41 harness checks pass, plus 10/10 field-test scenarios;
the FCGear install→Mod-dir→load→gear path verified with a live freecadcmd run).
- OK — csproj
<Version>$([System.DateTime]::Now.ToString("1.yy.MM.dd"))</Version>: date auto-version. - OK — both channels on
v*tags:.github/workflows/plugin-release.yml(GitHub Release zip for hosts) +.github/workflows/publish.yml(NuGetGraphene.FreeCADTool). - OK — AIOrchestrator referenced as sibling (
..\AIOrchestrator\AIOrchestrator.csprojProjectReference when present,Graphene.AIOrchestrator1.* package otherwise); never copied into the plugin tree. - OK — never ships
AIOrchestrator.dll/dependency graph: plugin-release.yml strips the AIOrchestrator closure; the tool carries no unique native dlls (FreeCAD is external, not a packaged dependency). - OK — the headless bridge launcher (
bridge_headless.py+ thebridge/package) is packed into the nupkg underlib/net10.0/on purpose, so the plugin ships the scripts it needs to auto-start the bridge; these are the tool's own files, not a foreign dependency, and they are plain Python (no native binaries). - OK — the AgentBridge Chat workbench payload (
AgentBridgeChat/→lib/net10.0/chat_mod/) is likewise the tool's own plain-Python/PySide files (no native binaries), packed on purpose soInstallChatModcan drop it into the user's FreeCADModdir. It is not a foreign dependency and adds no native dll to the plugin. - OK — writes no state next to the host or in
Tools/<FreeCADTool>/: exports/saves go to the sandbox workspace, versioned viaGitSupport; the bridge connection is runtime-only. The chat auto-install writes only into the user's own FreeCADMod/AgentBridgeChat/(the standard add-on location), never next to the host. - OK — independent of the host launch directory: file paths resolve via
SandboxPath/host base; the bridge endpoint comes fromFREECAD_HOST/FREECAD_PORTenv (with defaults), never the process CWD.
- OK — docs state outcomes, not internals (no mention of the RPC wire format, Python code generation, or the bridge transport in method docs).
- OK — minimal text; class summary is three short lines (competency + document precondition + path convention).
- OK — methods state what they do, not how.
- OK — nothing says "sandbox" or "virtual"; paths are described as relative to the "workspace root".
- OK — path-bearing methods (
export,import_file,documentopen/save,viewscreenshot) use workspace-relative paths (leading/). - OK — every public method has
<summary>,<param>,<returns>covering parameters and theError:format. - OK — class summary: one-line competency + cross-method rules (active-document default, start-with-
document(create), path convention); it does not explain individual methods. - OK — the cross-method rule that drives tool choice ("for a complex real-world object search the public libraries first with
get_complex_part") is on the FIRST summary line: with AgentBridge's lean orchestrator the planner sees only that line (the plugin's full definitions stay in the subagent), so a rule on line two is invisible where the work is planned. Verified in the live agent test described under the parts-library limitation. - OK — no summary/param redundancy; per-kind property keys live only in
<param>. - OK — one instruction per line; each
///line is one continuous source line. - OK — formats specified: lengths in mm, angles in degrees, JSON property keys and allowed enum values listed per method.
- OK — cross-references: object/sketch/feature params say they come from
list_objects/ must be created first ("sketch must exist before adding geometry", "start with document(create)"). - OK — errors are actionable (cause + detail) and prefixed
Error:; no raw exceptions reach the agent. - N/A — no
[[name]]dynamic placeholders.
- OK — only agent operations are public (21 modeling/lifecycle methods, incl. the
create_gearFCGear call-through and theget_complex_partpublic-library search); the RPC client (FreecadBridge) and helpers (Run/Err/Py/PyJson/N/Field) are private/internal. - OK — every file-handling method resolves with
SandboxPath.TryResolve/Resolveand converts host paths shown to the agent withSandboxPath.ToAgent(export,import_file,documentopen/save,viewscreenshot). - OK — no public method escapes the sandbox or exposes credentials, configuration or host paths; results render workspace-relative only.
- OK —
get_complex_partdownloads only into the sandbox workspace (the single winning library file plus its.freecad-library-index.jsoncache) and reports workspace-relative paths; it never writes into the FreeCAD install, and the archive it reads is public (no credential, no token).
- OK — class name ends with
Tool; packageGraphene.FreeCADTool. - OK — public methods declared directly on the class; the only inherited public member is
BaseAgentTool.LoadSkill(the bridge is a private field; no extra public members added). - OK —
Log.LogStep()at the entry of every public method, plus a rich outcome log at theRun()RPC choke point (every modeling operation logged with success/failure). - OK — derives from
BaseAgentTooland implementsIFileTool(its tasks create/modify files, so the done-without-tool guard applies). - OK —
GenerateDocumentationFile=True(tool definitions come from the.xmlnext to the dll).
- N/A — the tool returns shape metrics and paths, not file-content previews:
FileManager.GetFileInfo/GetFilesInfonot applicable. - OK — every file the tool writes is versioned with
GitSupport.Snapshotright after the write (export,documentsave,viewscreenshot); rollback stays centralized inGitTool. - OK — every method that creates/modifies a file returns the sandbox-relative path in its result (
export,save,screenshot), never a bare name or host path. - N/A — the tool parses no LLM text:
Utility.RemoveFencesEncapsulationAndFixTrimnot applicable. - N/A — no HTML/SVG output:
Utility.EmbedSvgIconsnot applicable. - N/A — no language detection:
Utility.DetectLanguagenot applicable. - OK — all path resolution/conversion uses
SandboxPath.Resolve/TryResolve/ToAgent; noPath.GetFullPathor host paths. - OK — OS desktop notifications use the shared
AIOrchestrator.SystemNotifier.Notify(title, body, seconds)(Windows balloon / macOS osascript / Linux notify-send), not a hand-rolled per-OS path; the tool never duplicates the notifier. - OK — user-facing setup strings are localized to the OS UI language (
CultureInfo.CurrentUICulture, English fallback) viaFreeCADStrings, matching AgentBridge's language rule; no hardcoded single-language user messages.
- OK — this file is shipped at the repository root of the plugin and states every checklist point above.
- GUI-only
viewactions (screenshot, visibility, display_mode, color) require a FreeCAD GUI session; in headless mode they return a clearError:rather than a wrong result. Not exercised by the headless harness. - GUI-only
undo_redo(Undo)/undo_redo(Redo): FreeCAD's undo stack is GUI-driven, and headlessdoc.undo()is a silent no-op, so the tool raises a clearError:in headless mode rather than falsely reporting a revert.undo_redo(Status)works headless and reports theguiflag. The harness asserts the GUI-required error; the GUI revert itself was confirmed manually in a GUI session (5000→1000→5000). create_gearis a call-through to the FCGear workbench (a runtime add-on, not a build dependency). The plugin bundles no FCGear code, so its GPL-3.0 license does not enter the plugin's distribution. On the first gear request, if FCGear is absent the tool installs it automatically in the background into the user's FreeCADModdir (download + extract, one-shot, guarded by an interlocked state) and returns a localized "installing" notice; the next request builds the gear.FREECAD_DISABLE_AUTOINSTALL=1skips the download (used by the harness), in which case the method returns a localized notice instead. The gear-build path (Mod-dir fallback load +setActiveDocument+CreateInvoluteGear.create()+ properties + recompute) was verified with a livefreecadcmd+ FCGear run on FreeCAD 1.1.3 and 0.20.2 (valid solid, volume 9798.73), and the full install→load→gear path was verified end-to-end with a livefreecadcmdsimulation of the C# installer; the harness check accepts either a valid gear or the installing notice.get_complex_partrequires network access toapi.github.com/raw.githubusercontent.com(the anonymous limit is 60 requests/hour; the index is one request and is cached for a week in the sandbox, so a normal run costs 2). Its verified surface: the real 8 649-file index ofFreeCAD/FreeCAD-library(GitHub tree API) driving live imports throughfreecadcmd—"spur gear","ball bearing","dc motor"(6.9 MB FCStd, 847 objects),"robot"/"robots"/"robotics"(all three resolving toRobots/Quadruped robot/Spider robot/…, i.e. the plural and prefix tolerance),"servo horn","robot arm"(no robot in the library → the result states which word is missing and lists the alternatives) — plus a white-box pass over the private index fetcher with a stub HTTP handler for thetruncated-tree fallback and the 403/X-RateLimit-Remaining: 0branch, and aAnalyzer.GetToolDefinitionscheck that the method and its class summary reach the agent. The STL branch (Mesh.read→Mesh::Feature, becauseImport.insertcannot read STL headless) was found and fixed by that live testing; the STEP branch (Import.insert) uses the same code path asimport_file. Agent-level verification (live AgentBridge + the SUPERFAST device model,get_complex_partreached through the real tool chain): asked in Italian to "aggiungi al progetto un robot quadrupede", the planner wrote the search-first rule into the subagent prompt and the subagent calledget_complex_part("robot dog")— English, as the parameter doc requires — which fetched the index, downloadedRobots/Quadruped robot/Spider robot/01 FCStd/quad_robot_body_bottom.FCStd(163 KB) and merged it into a new document, then exported/robot_quadrupede.step. This path only works because the rule is on the first summary line (see Agent-facing descriptions).- Cross-platform: the plugin is OS-neutral (
AnyCPU, no RID) and the transport is plain TCP/JSON with no platform-specific code. Verified end-to-end (41/41 harness + 10/10 scenarios) on Windows (FreeCAD 1.1.3) and Linux/WSL Debian (FreeCAD 0.20.2), confirming the Python snippets are robust across FreeCAD versions (sketch-to-plane attachment handles both the 1.xAttachmentSupport/getObjectAPI and the 0.20Support/OriginFeaturesAPI; PartDesign Revolution/GrooveReferenceAxisand PolarPatternAxisresolve to the profile-sketch axes on both). macOS uses the same OS-neutral plugin and the same FreeCAD engine; not executed here (no macOS host available), but no platform-specific code is involved. - AgentBridge Chat panel: the C# auto-install (
EnsureChatMod/InstallChatMod), the nupkgchat_modpacking, the Qt import shim (_qt.py), the headless import safety ofInitGui.py(all three registration calls arehasattr-guarded), the Python syntax of the add-on, and the SSE chat-client contract were all verified (import smoke-tests pass underfreecadcmdon FreeCAD 0.20.2 and 1.1.3; the SSE client was checked against a mock streaming server). The command's entry points use FreeCAD's own extension APIs —Gui.addWorkbenchManipulatorfor the Tools menu and the File toolbar,FreeCAD.addDocumentObserverfor the bridge/document gate — which the bundled BIM workbench also exercises, and the discovery offreecadcmdis verified on real Linux (WSL: PATH,FREECADnot found,/opt/freecad-1.1,/snap/bin, Fedora'sFreeCADCmd) and on Windows (a portable install onD:found with no environment variable). The full GUI round-trip — the dock actually rendering in a desktop FreeCAD, the bridge starting inside the visible GUI instance, the entry points being greyed out on the start page, and the agent editing that window live — requires a real desktop FreeCAD GUI plus a running AgentBridge and was not executed in this headless environment. It should be confirmed manually on a desktop before relying on the chat in production. Thecommands.pycross-process-detection gap (see MAINTENANCE.md) is handled by the in-process_bridge_startedguard, so the document observer and the menu command cannot double-bind the ports.