AnkusDrive

io.github.gchen19由 GitHub 帳號 gchen19 發布v0.5.7更新於 Oct 30, 2026

Drive FreeCAD from an agent: CAD modeling, drawings, FEM, CFD, EM, and multiphysics simulation.

概覽

AI 產生的概覽

驅動本機 FreeCAD 1.1,讓助理進行 CAD 建模、FEM/CFD 模擬、出圖與設計驗證。

可以做什麼
  • 建立、開啟、儲存與檢視 FreeCAD 文件,支援隔離工作區與工作程序重啟
  • 建構幾何:基本體、PartDesign 特徵、圓角、倒角、薄殼、補強肋、文字刻印,以及齒輪、齒條、鏈輪、皮帶輪、彈簧、緊固件、軸承與螺紋
  • 量測與檢查:距離、角度、邊界框、間隙、干涉、剖視圖、面與邊標籤、形狀有效性及氣密流道
  • 組裝零件、擷取物料清單、發布介面,並匯出圖面與形狀
  • 執行工程分析與設計空間研究,宣告效能契約並依規格最佳化參數
  • 把工作階段匯出為可重播的 Python 腳本,或透過腳本通道執行任意 FreeCAD Python
適用情境
適合讓助理在無圖形介面下做真實的機械 CAD 工作:參數化零件建模、公差與間隙檢查、結構或流體模擬、二維圖面匯出,以及可重現的設計研究。
執行需求
以 Python 套件形式透過 stdio 在本機執行(pip、pipx 或 uvx),需要 Python 3.10–3.14。需另外安裝 FreeCAD 1.1.x;freecadcmd 會自動尋找,或以 ANKUSDRIVE_FREECADCMD 指定。可選的 Linux 求解器堆疊在容器中執行。PDF/SVG 出圖需在 FreeCAD 內建 Python 中安裝 reportlab 與 svglib。不需帳號或 API 金鑰。
安裝前請注意
run_script 工具會執行助理撰寫的 Python,可完整存取你的檔案與程序;pip/pipx/uvx 安裝下預設開啟,由 ANKUSDRIVE_ALLOW_RUN_SCRIPT 控制。重啟或關閉工作區會遺失未儲存的文件與控制代碼。儲存會依你給的路徑寫入 .FCStd 檔案。工具定義體積很大(全部工具族約 114k tokens),應限制啟用的工具族。主機會把工具輸入與結果傳送給其模型供應商。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 AnkusDrive,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

AnkusDrive

[AnkusDrive — align generative intent with the CAD kernel]

A CLI + MCP server that drives FreeCAD through its Python API so LLMs (and humans at a terminal) can design mechanical parts and run FEM simulations without clicking through the GUI.

Why

FreeCAD exposes almost everything it does through a Python API — create documents, build sketches, extrude solids, mesh them, run CalculiX/Elmer FEM solves, read back stress/displacement fields. But that API lives inside FreeCAD's embedded Python (freecadcmd), which is awkward to call from anywhere else. AnkusDrive wraps it behind two surfaces:

  • CLI — one-shot commands (ankusdrive run script.py, ankusdrive box --w 10 --d 20 --h 5 -o part.FCStd) for scripts, CI, and quick iteration.
  • MCP server — 280+ structured tools (new_document, add_primitive, boolean_op, pad, add_gear, fem_new_analysis, fem_run, fem_results) so an LLM agent can model, inspect, and simulate iteratively. Beyond core CAD/FEM this now spans a broad simulation surface (thermal, CFD/CHT, EM, acoustics, FSI, injection molding, granular/DEM, optics, multibody) and a design-control layer (item/part numbers, recipes, variant families, lifecycle/revision, ECO change orders, versioned interfaces).
  • Multi-agent orchestration — a host-side reference layer that lets a team of agents partition one product into components, build them in parallel, and merge the pieces back together with the joints actually fitting (see Multi-agent design).

Target environment

  • FreeCAD 1.1.x. The freecadcmd binary is auto-discovered per-OS (macOS .app bundle, Linux /usr/bin etc., Windows C:\Program Files\FreeCAD 1.1\bin\freecadcmd.exe — version-globbed); override via $ANKUSDRIVE_FREECADCMD or rely on PATH. Run ankusdrive doctor to see exactly what resolved.
  • Bundled Python, ccx (CalculiX), and gmsh already ship inside every FreeCAD install — the macOS .app, the Linux package, and the Windows bin\ — so core CAD + structural FEM work on all three with no extra install.
  • Host-side rendering needs Pillow and numpy; both are installed by AnkusDrive as regular pip deps.
  • One optional exception: drawing PDF/SVG export (export_drawing) renders inside FreeCAD's bundled Python, so it needs reportlab + svglib installed there — see Drawing export (PDF/SVG). DXF export and everything else leave FreeCAD's Python untouched.

Setup

AnkusDrive is a pip-installable package; FreeCAD itself is the only thing you install separately. The host-side dependencies (mcp, Pillow, numpy) come along with the install. freecadcmd is launched as a subprocess and uses its own bundled Python — AnkusDrive doesn't touch it.

bash
# 1. Install FreeCAD 1.1.x from https://www.freecad.org/#    (macOS: drag to /Applications; Linux: distro package or AppImage;#     Windows: run the installer — default C:\Program Files\FreeCAD 1.1)
# 2. Install AnkusDrive. Pick one:pipx install ankusdrive                                       # from PyPI — isolated app, `ankusdrive` on PATHpip install ankusdrive                                        # or into an env you manage yourself# unreleased main, or for development from a clone:pipx install git+https://github.com/gchen19/AnkusDrive.gitgit clone https://github.com/gchen19/AnkusDrive.git && cd AnkusDrivepython3 -m venv .venv && .venv/bin/pip install -e .         # `.venv/bin/ankusdrive`
# 3. Smoke-test that the worker can reach FreeCAD, and see the full setup reportankusdrive ping        # → ping=pong freecad=1.1.1ankusdrive doctor      # per-item FreeCAD + solver checklist with the exact fix each

On Windows, don't follow the block above by hand — there is one scripted path that does all of it including the MCP registration: Windows quickstart (PowerShell).

AnkusDrive is published on PyPI at pypi.org/project/ankusdrive; the distribution roadmap beyond it (marketplace listings, hosted transport) is tracked in epic #303; the original phase plan is kept as a design record at docs/archive/PUBLISHING_PLAN.md.

macOS quickstart — solvers in a container

FreeCAD, CalculiX, SU2, PrusaSlicer and every pip-wheel family run natively on a Mac; the block above is all you need for those. What has no practical macOS build is the Linux solver stack — OpenFOAM, Elmer, YADE, openEMS, Bempp, preCICE, openInjMoldSim. Those run in a container, and AnkusDrive stays on the host and reaches into it. The image is multi-arch, so on Apple Silicon it runs native, not emulated.

bash
# 1. A container engine: Docker Desktop, OrbStack, colima or podman.
# 2. Pull the solver image (0.88 GB on Apple Silicon, 1.15 GB on Intel).docker pull ghcr.io/gchen19/ankusdrive-solvers:latest
# 3. Check it is ours before running your geometry through it (see below).bash scripts/verify-container-image.sh
# 4. Point AnkusDrive at the container substrate.export ANKUSDRIVE_SUBSTRATE=container#    optional: ANKUSDRIVE_CONTAINER_ENGINE=podman|nerdctl   (default docker)#    optional: ANKUSDRIVE_CONTAINER=<name>                  (default ankusdrive-solvers)
# 5. Create the container. $TMPDIR must be mounted at the SAME path inside, because a#    case directory has to mean the same thing on both sides. On macOS $TMPDIR is a#    per-user /var/folders/... path — mount THAT, not /tmp.docker run -d --name ankusdrive-solvers \  --user "$(id -u):$(id -g)" -e HOME=/tmp \  --network none --cap-drop ALL --security-opt no-new-privileges \  --read-only --tmpfs /tmp:rw,exec,size=2g \  -v "$TMPDIR:$TMPDIR" \  ghcr.io/gchen19/ankusdrive-solvers sleep infinity
# 6. Export the in-container paths for the OpenFOAM-backed families. The image#    publishes them; YADE, Elmer, openEMS and Bempp need no export — AnkusDrive finds#    them by asking the container.docker exec ankusdrive-solvers env | grep -E \  '^ANKUSDRIVE_(OPENFOAM_PATH|OPENFOAM_BASHRC|FSI_OPENFOAM_BASHRC|CCX_PRECICE|PRECICE_LIB|OPENFOAM_ADAPTER_LIB|OPENINJMOLDSIM|OPENINJMOLDSIM_BASHRC)='
# 7. Confirm.ankusdrive doctor                  # each family: ready via <solver> (in container)ankusdrive doctor --verify-image   # …and that the image is signed by this repo

The run flags are least privilege, and each is there because the solvers genuinely do not need what it removes — verified by running the live solver suites with them on. --network none in particular: nothing in a mesh is a reason to reach the internet.

Don't copy the image's ANKUSDRIVE_FREECADCMD or ANKUSDRIVE_CALCULIX_PATH — those name paths inside the container, while FreeCAD and ccx run on your Mac.

A config.toml written for a native install is the one trap here: its absolute paths are read as in-container paths. ankusdrive doctor now catches that and says so.

The alternative substrate on macOS is a Multipass VM, which you provision yourself — docs/MACOS.md has the full per-solver reality on a Mac, and docs/CONTAINER_SUBSTRATE.md the container path in depth.

The solver images, and checking they are ours

imagewhat it issize
ghcr.io/gchen19/ankusdrive-solverswhat you want: solvers and their runtime libraries, nothing else1.15 GB amd64 / 0.88 GB arm64
ghcr.io/gchen19/ankusdrive-heavythe CI image — also carries FreeCAD, the driver venv and every build toolchain, because the whole test suite runs inside it5.3 GB / 4.5 GB

Both are public, multi-arch (linux/amd64 + linux/arm64, each built natively) and tagged latest plus sha-<commit>.

Every published manifest is signed through Sigstore with a short-lived GitHub OIDC identity — no key to store or leak — and carries provenance naming the repository, workflow and commit that built it, plus a CycloneDX SBOM of what is inside:

bash
bash scripts/verify-container-image.sh                       # the slim image, :latestbash scripts/verify-container-image.sh ghcr.io/gchen19/ankusdrive-solvers@sha256:<digest>

Needs the GitHub CLI (gh ≥ 2.49, authenticated). Verify a digest and then run that digest: verifying :latest today and pulling :latest next week are two different images. ankusdrive doctor prints the digest the running container was made from, and --verify-image checks it.

There are three answers, and only one is alarming: verified; unsigned (an image you built yourself, or one published before signing existed — silence it with ANKUSDRIVE_ALLOW_UNVERIFIED_IMAGE=1); and mismatch, an image carrying provenance from somewhere else, which no setting silences. Verification never blocks a solve.

Building your own — a subset, or with your own changes — takes minutes, because nothing is compiled (the prebuilt solver trees are copied):

bash
tools/build_solver_image.sh --solvers "openfoam fsi" -t my-solvers:dev   # 0.69 GB

Windows quickstart (PowerShell)

Windows is a first-class target (core CAD + CalculiX FEM run natively against a stock FreeCAD 1.1 install), and the whole core install is one script — venv, pinned dependencies, doctor, and the MCP registration line with resolved absolute paths:

powershell
# 1. Install FreeCAD 1.1.x from https://www.freecad.org/ (default C:\Program Files\FreeCAD 1.1).#    Nothing needs to go on PATH — AnkusDrive globs the versioned install dir itself.
# 2. Clone and run the core installer. Windows PowerShell 5.1 is enough; no admin needed.git clone https://github.com/gchen19/AnkusDrive.gitcd AnkusDrivepowershell -ExecutionPolicy Bypass -File scripts\install-core.ps1

That creates .venv, installs AnkusDrive with the pins that matter (notably mcp<2 — mcp 2.x installs cleanly and then breaks ankusdrive mcp), verifies the resolved mcp/numpy/Pillow, runs ankusdrive doctor + ankusdrive ping, completes a real MCP stdio handshake, and finally prints your registration block. Useful switches: -Python 'C:\Program Files\Python313\python.exe' to pick an interpreter, -Extras mbd,fluids for the pip-wheel solver families, -Persist to write the FreeCAD path into %APPDATA%\ankusdrive\config.toml (MCP hosts launch with a minimal environment, so a $env: set in your terminal will not reach them).

3. Register it with your MCP host. The script prints these with your real paths filled in — a GUI host doesn't inherit your shell PATH, so the absolute path matters:

powershell
# Claude Codeclaude mcp add ankusdrive -- C:\Users\you\AnkusDrive\.venv\Scripts\ankusdrive.exe mcp
# Claude Desktop: %APPDATA%\Claude\claude_desktop_config.json#   { "mcpServers": { "ankusdrive": {#       "command": "C:\\Users\\you\\AnkusDrive\\.venv\\Scripts\\ankusdrive.exe",#       "args": ["mcp"] } } }

Then restart the host; you should see the ankusdrive__* tools appear.

Supported Python: 3.10 – 3.14 (3.14 verified end-to-end on Windows 11 — pip install, MCP stdio handshake, and ankusdrive ping → freecad=1.1.1). The script checks your interpreter before pip runs, so a too-new CPython says so instead of failing inside the resolver.

Optional solvers (SU2, Elmer, PrusaSlicer, WSL-backed OpenFOAM) come afterwards via scripts\install-solvers.ps1. For the Linux-only solvers (OpenFOAM, FSI, injection molding, YADE, openEMS, Bempp), run ankusdrive container setup --install-engine once WSL is installed. It runs them from the prebuilt image with Docker inside WSL (how). Full per-solver reality, the test suite, and the WSL2 route: docs/WINDOWS.md.

Ubuntu 24.04+ / containers (apt has no FreeCAD)

FreeCAD was dropped from Ubuntu 24.04's universe repo, so apt install freecad finds no candidate there, and upstream's snap/flatpak both fail in a container or sandboxed agent environment (no snapd session, no FUSE). The path that works everywhere is the official AppImage, extracted:

bash
scripts/install-freecad-appimage.sh          # or: scripts/install-solvers.sh freecad

It downloads the pinned release AppImage, checks its SHA-256, unpacks it with --appimage-extract (a userspace squashfs unpack — no FUSE, no root, no snapd, which is why it works in a container), symlinks freecadcmd, freecad, ccx and gmsh into /usr/local/bin, and then live-verifies the result with ankusdrive ping plus a real CalculiX solve (ankusdrive fem cantilever). Without a writable /opt it installs to ~/.local/opt/freecad instead; --prefix / --bindir override both, --appimage FILE reuses a download you already have.

The symlink step is optional: AnkusDrive also probes /opt/freecad/squashfs-root/usr/bin (and ~/.local/opt/freecad*/…) directly, so a hand-extracted AppImage in either prefix is auto-discovered. ccx and gmsh ride along inside the AppImage, so structural FEM works off this one download.

Telling AnkusDrive where FreeCAD lives

AnkusDrive auto-discovers freecadcmd in this order: $ANKUSDRIVE_FREECADCMD, then shutil.which(...) on PATH (trying freecadcmd, FreeCADCmd, and freecad.cmd), then a per-OS list of standard install locations:

OSAuto-discovered locations (newest version wins)
macOS/Applications/FreeCAD.app/Contents/Resources/bin/freecadcmd
Linux/usr/bin, /usr/local/bin, /snap/bin/freecad.cmd, extracted AppImage under /opt/freecad*/squashfs-root/usr/bin or ~/.local/opt/freecad*/…, ~/.local/bin
WindowsC:\Program Files\FreeCAD *\bin\freecadcmd.exe (version-globbed), C:\Program Files (x86)\…, %LOCALAPPDATA%\Programs\FreeCAD *\bin\…

So a stock installer on any of the three needs no configuration. For a non-default install, point AnkusDrive at the binary directly:

bash
export ANKUSDRIVE_FREECADCMD=/path/to/freecadcmd            # macOS/Linux
powershell
$env:ANKUSDRIVE_FREECADCMD = "D:\Apps\FreeCAD\bin\freecadcmd.exe"   # Windows

ankusdrive doctor prints which of the three layers (env / PATH / auto) actually resolved FreeCAD, plus every candidate it checked — the fastest way to debug a "FreeCAD not found" on a new box.

Drawing export (PDF/SVG)

export_drawing builds 2-D mechanical drawings (multi-view PDF/SVG/DXF with dimensions) entirely headless. DXF uses FreeCAD's own writer and needs nothing extra. PDF and SVG are composed and rasterised with reportlab + svglib, and because that runs inside the worker — FreeCAD's bundled Python, not the host venv — the two packages must be installed into FreeCAD's Python:

bash
# Resolve FreeCAD's bundled Python from freecadcmd itself (portable across the# macOS .app, a Linux distro package, and an extracted AppImage). freecadcmd# prints a startup banner after the script output, so match a marker line# rather than taking the last line:printf 'import sys; print("DPREFIX="+sys.prefix)\n' > /tmp/_fcprefix.pyFREECAD_PREFIX="$(freecadcmd /tmp/_fcprefix.py 2>/dev/null | sed -n 's/^DPREFIX=//p')"FREECAD_PY="$FREECAD_PREFIX/bin/python"      # some builds: $FREECAD_PREFIX/bin/python3
# Pin svglib<1.6 — newer svglib pulls rlPyCairo -> pycairo, a native build we# don't use (our drawings are line art, no gradients)."$FREECAD_PY" -m pip install reportlab "svglib<1.6"
# Verify:"$FREECAD_PY" -c "import reportlab, svglib; print('drawing export ready')"

Without this, export_drawing still produces .dxf; .pdf/.svg raise a clear ModuleNotFoundError. FreeCAD already bundles Pillow (reportlab needs it), so no separate install is required.

Wiring it into an MCP host

The MCP server speaks stdio. Point your host at the ankusdrive binary and let it run the mcp subcommand.

Claude Desktop — add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

json
{  "mcpServers": {    "ankusdrive": {      "command": "ankusdrive",      "args": ["mcp"]    }  }}

If ankusdrive isn't on the host process's PATH, use an absolute path — e.g. /Users/<you>/.local/bin/ankusdrive (pipx default) or /absolute/path/to/AnkusDrive/.venv/bin/ankusdrive (clone+venv).

Claude Desktop, one click — download ankusdrive-<version>.mcpb from the latest release and open it. Claude Desktop sets up its Python environment with uv, so no pipx step is needed — FreeCAD 1.1 still is. The install dialog has one optional field, the FreeCAD command path, for a FreeCAD that auto-discovery cannot find.

Claude Code — register once:

bash
claude mcp add ankusdrive -- ankusdrive mcp

Other hosts (Cursor, Continue, custom MCP clients) — same shape: stdio transport, command = ankusdrive, args = ["mcp"].

After restarting the host, you should see 280+ ankusdrive__* tools become available. If startup hangs or the host reports a closed connection, run ankusdrive ping directly — that exercises the same worker boot path with cleaner error messages.

Tool families (toolsets). Every tool definition takes up the client's context, and all 283 come to roughly 114k tokens. Tools are grouped into families you can switch on and off: core (always on), drawings, fem, components, sheet_metal, assembly, intent, manufacturing, hand_calcs, simulation, plm, rendering.

  • pip / pipx / uvx / clone: every family is on unless you set ANKUSDRIVE_TOOLSETS, e.g. ANKUSDRIVE_TOOLSETS=drawings,fem,simulation, or toolsets = "..." in config.toml.
  • Claude Desktop extension: core, drawings and fem are on by default (~31k tokens); turn others on in the extension's settings.

setup_status lists the families that are off and exactly how to enable each one.

run_script executes Python the agent writes, with full access to your files and processes. It's controlled by ANKUSDRIVE_ALLOW_RUN_SCRIPT (env, or allow_run_script in config.toml):

  • pip / pipx / uvx / clone: allowed unless set to false.
  • Claude Desktop extension: off by default; turn it on in the extension's settings.

When it's off, the tool isn't offered at all, and setup_status says how to enable it.

Simulation solvers & review-video demos

The base install (FreeCAD + pip install ankusdrive) covers geometry, the analytic oracles, and the MCP surface. The heavy simulation families each shell out to an external solver, discovered at runtime by ankusdrive/solvers.py ($ANKUSDRIVE_<SOLVER>_PATH → PATH → standard install dirs). A family whose solver is absent degrades to a clean {ok: false, reason, install} dict instead of crashing — check what currently resolves with ankusdrive doctor (cross-platform, no server boot needed), the solve_capabilities MCP tool, or the install script's list. The install script installs the pip-wheel solvers and provisions the native ones — scripts/install-solvers.sh on Linux/macOS (apt/conda + source builds), and scripts/install-solvers.ps1 on Windows (pip extras + portable SU2/Elmer/PrusaSlicer downloads; CalculiX auto-detected from FreeCAD's bundle).

Persistent config: every ANKUSDRIVE_* path can instead live in ~/.config/ankusdrive/config.toml (%APPDATA%\ankusdrive\config.toml on Windows; ANKUSDRIVE_CONFIG overrides): freecadcmd = "..." at top level, one lowercased key per solver var under [solvers] (su2_path, elmer_path, openfoam_bashrc, ...). Env vars still win when set; the file is the layer that survives an MCP host's minimal launch environment. ankusdrive doctor reports the file and which layer resolved each value.

Where a solve's files go: every built-in solve writes its deck — a .sif plus mesh, an OpenFOAM case tree, a .inp, a sliced .gcode — into its own directory under <system temp>/ankusdrive-cases, and reports that directory as case_dir. They are kept, because a result names them and a second tool is handed them (a warpage solve consumes the cooling case a fill solve wrote), and they are reaped oldest-first once the root passes 64 directories or 4 GB — never touching one written within the last hour, so a running solve cannot be pulled out from under itself. solve_capabilities reports the root and the live numbers under cases. Tune with ANKUSDRIVE_CASE_ROOT, ANKUSDRIVE_CASE_KEEP, ANKUSDRIVE_CASE_MAX_GB, ANKUSDRIVE_CASE_GRACE_S, or turn reaping off with ANKUSDRIVE_KEEP_SCRATCH=1. A case_dir you supply is never touched, wherever it lives.

Every solve result also carries deck — a manifest of what the solver was handed, taken the moment its first step launched, before it wrote any output into the same directory: {digest, count, bytes, files: {path: hash}}. A session_transcript compares it with s.deck(...) ahead of that solve's checks, so a replayed number that drifted arrives already explained — ~ case.sif printed right above the failing check means the problem changed; deck matches the recording means it did not, and the solver or the environment did. CalculiX FEM results carry their .inp the same way.

Platform note: the solver discovery layer is fully cross-platform (per-OS install dirs, Windows PATHEXT/.exe, env overrides), so ankusdrive doctor gives an honest report on macOS/Linux/Windows. The pip-wheel families (MBD, topology, optics, fluids) install identically everywhere. The native-binary families differ by OS — CalculiX ships inside every FreeCAD install; SU2 and PrusaSlicer have good Windows/macOS binaries; Elmer has a portable Windows zip but no macOS binaries; the OpenFOAM-backed families (CFD, FSI, injection molding) — plus YADE, openEMS and Bempp — rely on a Linux shell + linker glue. On macOS they run through the signed solver container, native on Apple Silicon and with no source builds: see macOS quickstart. On Windows they run through WSL. See docs/WINDOWS.md and docs/MACOS.md for the full per-solver reality and setup on each OS.

The review-video demos under scratch/ turn a solver result into a GIF a human can watch — the real exported geometry in motion with the matching oracle overlaid on the frame (written to artifacts/). Each needs its family's solver plus matplotlib, and the CFD one needs meshio (on top of the base numpy/Pillow):

bash
pip install matplotlib meshio        # frame rendering + reading OpenFOAM's VTK output
Review-video demo (scratch/…)Solver it drivesInstall
dog_clutch_cad_sim.py — rigid-body contact via p.vhacdPyBullet (pip wheel)pip install 'ankusdrive[mbd]'
meshing_gears_video.py — MBD gear trainPyBullet (pip wheel)pip install 'ankusdrive[mbd]'
modal_shape_video.py — FEM modal shapesCalculiX ccx (FreeCAD FEM)apt install calculix-ccx (Linux); FreeCAD finds ccx on PATH
thermal_field_video.py — transient thermal fieldElmerapt install elmerfem-csc; ensure ElmerSolver on PATH (or set ANKUSDRIVE_ELMER_PATH)
cfd_field_video.py — CFD field (lid-driven cavity)OpenFOAM + meshioOpenFOAM via apt/conda, then source <install>/etc/bashrc (or set ANKUSDRIVE_OPENFOAM_BASHRC); pip install meshio

All of them also use FreeCAD for the geometry/meshing, so run each with the same interpreter that launches the worker — e.g. .venv/bin/python3 scratch/cfd_field_video.py.

Optics

Two optics engines sit behind the MCP surface, in two licensing/runtime lanes:

LaneToolsEngineInstall
Sequential — lens design + optimizationoptics_lens_design, optics_lens_optimize, optics_raytraceoptiland / rayoptics (MIT/BSD, in-process)pip install 'ankusdrive[optics]' — or scripts/install-solvers.sh optics
Non-sequential — tracing through STL solidsoptics_solid_traceKrakenOS (GPL-3.0, out-of-process only)pip install 'ankusdrive[optics_gpl]' — or scripts/install-solvers.sh optics_gpl

The sequential engines import in-process, so install the optics extra into the same interpreter that launches the worker (like the other wheels). The non-sequential engine is GPL-3.0 and is therefore never imported by AnkusDrive — it runs in a separate subprocess (ankusdrive/optics_gpl_runner.py), the same arm's-length boundary used for the GPL Elmer/OpenFOAM binaries. The worker locates a Python that can import KrakenOS automatically (from where the wheel is installed); override with ANKUSDRIVE_OPTICS_GPL_PYTHON=/path/to/python. Because of that isolation the GPL extra is opt-in: the no-argument install-solvers.sh run installs only the permissive extras and prints how to add optics_gpl. Rendered examples for both lanes (lens layout, spot diagram, optimization, prism TIR, and a ball-lens spherical-aberration study) live in examples/optics/optics_gallery/ — regenerate with .venv/bin/python examples/optics/optics_gallery.py (and …_3d.py, optics_ball_lens.py), or bootstrap everything in one shot (installs both lanes, then renders every figure):

bash
scripts/install-solvers.sh --optics-gallery

Architecture sketch

 ┌────────────┐      ┌────────────┐      ┌──────────────────────┐ │  MCP host  │ ───► │ AnkusDrive   │ ───► │  freecadcmd worker   │ │  (Claude)  │      │ (Python)   │ IPC  │  (long-lived Python) │ └────────────┘      └────────────┘      └──────────────────────┘       ▲                    ▲                        │       │                    │                        ▼       └── CLI user ────────┘               .FCStd / .inp / .vtk

Key decision: long-lived worker with JSON-over-stdin/stdout, not subprocess-per-call. FreeCAD startup is ~1–2s; re-paying that per tool call is unacceptable for an interactive agent. The worker is a small Python loop launched under freecadcmd, reading commands, dispatching to handlers, returning structured results (including object IDs so follow-up calls can reference created geometry).

FreeCAD API surface we care about

Notes gathered from the scripting docs and the FEM Python tutorial:

Core (App):

  • App.newDocument(name) / App.ActiveDocument / doc.recompute() / doc.save(path)
  • doc.addObject("Part::Box", "name") — typed object creation; properties set after (box.Height = 5)
  • doc.supportedTypes() for introspection; obj.TypeId, obj.isDerivedFrom("Part::Feature")

Modeling:

  • Part — makeBox, makeCylinder, makeSphere, boolean cut/common/fuse, fillets, lofts (OpenCASCADE under the hood)
  • Draft — 2D primitives, move, arrays
  • Sketcher + PartDesign — parametric sketch-driven solids (most "real" mechanical design happens here)
  • FreeCAD.Vector, Placement for positioning

FEM (ObjectsFem + femtools):

  • ObjectsFem.makeAnalysis(doc, "Analysis") — container
  • makeSolverCalculixCcxTools / makeSolverElmer — solver objects with tunables (GeometricalNonlinearity, ThermoMechSteadyState, …)
  • makeMaterialSolid — assign YoungsModulus, PoissonRatio, Density
  • Constraints: makeConstraintFixed, makeConstraintForce, makeConstraintPressure, makeConstraintDisplacement, contact/tie/spring, thermal
  • Mesh: makeMeshGmsh + femmesh.gmshtools.GmshTools(...).create_mesh() (or Netgen)
  • Run: femtools.ccxtools.FemToolsCcx().run()
  • Results: iterate analysis.Group for Fem::FemResultObject; read .DisplacementVectors, stress fields

Headless invocation:

  • freecadcmd script.py — runs script then exits
  • freecadcmd with no args — interactive Python REPL (what the worker will drive)
  • --console, -M <moddir>, -P <pypath>, --pass <args>, FreeCAD.ConfigGet(...) for env info
  • FreeCADGui is not available headless — keep design logic in App/Part/Fem only

How an agent reaches FreeCAD: three layers

AnkusDrive exposes FreeCAD through three layers, each with a different audience and a different cost-of-use. Knowing which layer a feature lives in tells you how to invoke it.

Layer 1 — typed MCP tools (the agent surface)

280+ first-class MCP tools span the core mechanical-design surface, a broad engineering-analysis / simulation surface, and a design-control (PLM) layer. They have validated parameters, structured returns, and stable handles for chaining. This is the happy path — what an agent uses for things people do every day.

DomainWhat's covered
Document lifecyclenew_document, open_document, save_document, list_documents, set_active_document, close_document, restart_worker
Geometry primitivesadd_primitive (box/cyl/sphere), boolean_op, export_shape (STEP/IGES/BREP/STL)
Selection (stable refs)list_faces, list_edges, query_faces, resolve_face, resolve_edge, register_handle, verify_feature
PartDesignmake_body, make_datum_plane, make_sketch, add_sketch_geometry, add_sketch_constraint, add_sketch_external, close_sketch, pad, pocket, revolve, hole, loft, sweep, helix, partdesign_fillet, partdesign_chamfer, linear_pattern, polar_pattern, mirrored, thickness, draft
Direct modeling & feature opsfillet_edges, chamfer_edges, shell_solid, add_rib, engrave_text, oring_groove, transform, scale_shape, copy_shape
Parametric componentsadd_gear, add_rack, add_sprocket, add_pulley, add_spring, add_fastener, add_bearing, add_thread, list_thread_options
Metrology & inspectionmeasure_distance, measure_angle, bounding_box, check_shape, section_view, min_clearance, envelope_check, interference_check
Generic property accessget_object, set_property
Functional intent & invariantsannotate_face, list_face_roles, classify_face_sides, check_airtight_path, declare_intent, verify_intent
Performance contractsdeclare_performance, verify_performance — a quantitative spec ("Cd ≤ 0.30 at 30 m/s", "Δp ≤ 50 Pa", "first mode ≥ 200 Hz") persisted on the part and re-proved after every edit, with a three-state verdict: a measurement whose uncertainty band straddles the limit is indeterminate (escalate), never a pass. The contract is consulted at the gates (#261): merge_assembly, substitutability_check and component_contract_check read the last recorded verdict, so an unmet spec blocks a merge and an unverified one is reported as its own outcome rather than passing silently
Design-space studies (DOE)study_submit — sweep recipe/tool parameters over a full grid or a Latin hypercube and keep the WHOLE search as a table, not just the last point. A response is any AnkusDrive tool + a metric path (including a whole verify_performance verdict, so points stay comparable across fidelity tiers); screening responses evaluate inline, solver responses fan out concurrently behind one collector job. Sampling is deterministic from seed, so re-submitting a crashed or widened study re-runs only the new points and reports the rest as cache hits
Optimize to a specoptimize_submit — vary bounded parameters until every constraint passes, then report whether it was proven. A bounded Nelder-Mead (derivative-free; there is no adjoint through a CFD solve) over the same objective/constraint mapping the contract layer uses, with a screen→solver fidelity ladder. Two rules come from the contract layer: an indeterminate constraint is a measurement problem, not a failed step (it neither attracts nor repels the search), and convergence is not proof — a margin narrower than its own uncertainty band is reported unproven, however tidily the simplex converged
Assembly & interfacesmake_assembly, add_part, list_assembly_parts, merge_assembly, publish_interface, interface_align_check, assembly_lock, assembly_lock_check, bom_extract
Drawings (TechDraw, headless)make_drawing_page, add_projection_group, add_section_view, add_thumbnail, add_dimension, add_annotation, add_feature_note, add_gdt_callout (feature control frames), set_title_block, fit_page, export_drawing (PDF/SVG/DXF), plus completeness/legibility gates drawing_gate, drawing_legibility
Inspection (first-article)balloon_drawing (revision-stable balloon numbering), inspection_plan (characteristic list with a measurement method per row, by the gauge-maker's 10:1 rule), fai_report (AS9102-Form-3-shaped CSV/SVG/PDF — not a certified submission); drawing_gate(require_ballooned=True) makes a ballooned print a release requirement
Release packages (vendor / RFQ)release_package — the one-call deliverable bundle for an item at a revision: STEP + drawings (PDF/SVG/DXF) + recursive BOM + inspection package + a blake2b-checksummed manifest. Gated before anything is written: the item must be in a releasable lifecycle state (or draft=True, which watermarks every artifact PRELIMINARY), drawing_gate must pass for every included page, and the title block's part number / revision / material must match the items registry — a mismatch is a failure with a naming diff, never a silent fix. Byte-reproducible (the same revision re-releases to identical checksums), stamps the ECO into the manifest and the print, and rfq=True adds quantity breaks + the cost_estimate rollup while dropping internal-only artifacts
Off-the-shelf parts (buyability)catalog_search (what standard components exist, in which sizes and stocked lengths), catalog_nearest (snap a wanted size to a real one — asked for an M4×13 it answers 12 and 16), catalog_check, standard_part_designate (canonical designations: ISO 4762 M4×12 A2, 608-2RS, AS568-214 NBR70, stamped on the part at creation), designation_check, bom_extract(orderable=True) (per-line stocked / not_stocked with alternatives)
Visual feedbackrender_view, render_views (8 preset views, multi-view sheets), render_photoreal / render_photoreal_submit (Blender studio scene with per-part appearance for whole assemblies, or the FreeCAD Render add-on renderers; renderer="auto"), render_capabilities
FEM (FreeCAD/CalculiX/Elmer)fem_new_analysis, fem_set_solver, fem_set_material, fem_set_nonlinear_material, fem_add_constraint (fixed/force/pressure/displacement/temperature/heatflux/initial_temperature), contact_setup, fem_mesh, fem_mesh_refinement, fem_modal, fem_buckling, fem_run, fem_run_submit (the same CalculiX solve off the MCP channel; results readers accept its job_id), fem_results, fem_result_probe (stress/disp/temp at a point or face), fem_modal_results, fem_buckling_results, fem_thermal_results, plus the legacy fem_cantilever_demo
Engineering oracles & hand-calcsmachine elements (gear_rating, bearing_life, belt_drive, spring_check, bolted_joint_check, press_fit_stress, seal_check), structural (beam_modal, beam_buckling, plate_check, hertz_contact, elastica_deflection, plastic_collapse, random_vibration, harmonic_response), durability (fatigue_check, fracture_check, creep_flag, wear_estimate), thermal (thermal_lumped, thermal_transient_1d, thermal_composite_wall, h_estimate), tolerance/GD&T (tolerance_stackup, fit_check, fit_class, gdt_check)
Simulation families (external solvers, async)screens + full solves that shell out to OpenFOAM/Elmer/CalculiX/openEMS/YADE/KrakenOS, most via a submit→poll job pattern: thermal/CHT (cht_channel_submit, cht_graetz_submit, thermal_transient_submit, thermal_radiation_submit), CFD (cfd_pipe_flow, cfd_body_drag, cfd_internal_flow_submit, cfd_external_flow_submit — including the virtual wind tunnel: hand it a solid and get Cd/Cl/Cm from an integrated force, gated against the sphere drag curve; every steady solve carries a trust block (convergence, checkMesh, measured y+) and cfd_mesh_independence_submit/grid_convergence put a Richardson/GCI error band on geometry with no analytic twin), EM (em_skin_depth, em_dc_resistance, em_field, em_conduction_submit, em_induction_submit, em_fullwave_submit), acoustics (acoustic_screen, acoustic_fem_submit, acoustic_radiation_submit), FSI (fsi_*), molding (molding_screen, molding_fill_submit, molding_warpage_submit), drop/impact (drop_impact, bar_impact, impact_dynamics_submit — the meshed part flown into a rigid floor, flat / edge / corner, gated against the exact St-Venant bar), granular/DEM (granular_screen, dem_pack_submit, dem_flow_submit), optics (optics_lens_design, optics_lens_optimize, optics_raytrace, optics_solid_trace), multibody (mechanism_kinematics, mechanism_simulate_submit), topology (topology_optimize_submit, topology_to_solid)
Async jobsjob_status, job_result, job_list — poll/collect any *_submit long-running solve; solve_capabilities reports which solvers currently resolve
Materials & fluidsmaterial_list, material_get, material_select, fluid_props — mechanical-property / molding / CoolProp thermophysical corpora behind a typed lookup
Sheet metalsheet_base (base flange), sheet_flange / sheet_tab / sheet_hem (bends placed by stable edge tag), sheet_unfold (K-factor flat pattern + per-bend allowance/deduction, with the K in force and its source echoed into every result), sheet_refold (round-trip verification against the folded solid), sheet_flat_export (layered DXF — CUT / BEND_UP / BEND_DOWN, the file a laser/brake shop quotes from), sheet_check (min bend radius by material, min flange, hole-to-bend, refold collision)
Manufacturing & Design-for-Xdfm_check (also runs the sheet-metal press-brake rules when handed a sheet part), dfa_check, moldability_check, optics_moldability_check, pack_check, cost_estimate, slice_estimate, slice_gcode_submit, laminate_properties, drop_impact
CNC (machinability + machining time)cnc_machinability_check (setups from the tool-approach census, undercuts, tool L/D, sharp/small internal corners, thin walls — pure geometry, no CAM engine), cnc_time_estimate (material-removal-rate model: removed volume / MRR plus finishing area, ±50 % against the flat table's ±100 %; feeds cost_estimate(machine_time_hr=…))
Tolerance ↔ costtolerance_cost_check (per-dimension IT grade, the cheapest process that holds it naturally, a relative cost index, and a flag when a dimension is tighter than the declared process can hold without a secondary operation), suggest_loosening (the loosest tolerance that works — greedy loosening, every step re-verified against tolerance_stackup's cpk); cost_estimate(tolerance_class=…) puts the same curve in the rollup
Design control / PLMitems & part numbers (items_new, items_validate, items_resolve, items_check_manifest), recipes (recipe, recipe_list, recipe_schema, recipe_validate), feature templates (feature_instantiate, feature_list, feature_schema, feature_validate), variant families (family_materialize, family_validate), lifecycle/revision (lifecycle_transition, lifecycle_editable, lifecycle_classify_change, lifecycle_apply_change), change control (eco_create, eco_validate, change_impact, where_used, baseline_create, baseline_verify), interface registry + substitutability (get_interface, substitutability_check), projects (scaffold_project, project_validate, project_check_references, project_resolve_manifest)
Operationstransaction_open, transaction_commit, transaction_abort
Session transcriptssession_transcript: the session so far as a Python script that regenerates it (model, drawings, simulations) through these same tools. Handles are variables, job polls are one s.wait(job), measured results are s.check() lines that stop a drifted replay, and paths are relative to WORKDIR. It is also the analysis provenance record: every hand-calc and solve is kept and checked whole, run_script carries its SHA-256, and a PROVENANCE block names AnkusDrive / FreeCAD / platform / substrate plus every solver the session reached — path, substrate and probed version. Read-only: it returns the script as text. Tool calls are kept in server memory unless you opt into the durable journal: ANKUSDRIVE_JOURNAL_DIR appends every call to a per-session JSONL file, and journal_export / ankusdrive journal export turn a past session back into the same record (PRIVACY.md)

All tools return JSON; geometry-creating tools return a handle (e.g. pad_1) that subsequent calls reference. The heavy simulation families return a {ok: false, reason, install} dict (rather than crashing) when their solver isn't installed — see Simulation solvers.

Layer 2 — generic property reflection

For the long tail of "I just need to tweak this one property" without a dedicated tool:

  • get_object(handle) — dump every entry in obj.PropertiesList with Quantities → float (mm/deg), Vectors → list, Placements → dict.
  • set_property(handle, name, value) — set any single property by name.

Use this when a typed tool exists for the object kind but doesn't expose the exact property you need (e.g. Refine on a Pad, Sections ordering on a Loft, internal tunables on a CCX solver).

Layer 3 — run_script (the universal escape hatch)

For features that have no first-class MCP tool at all — e.g. Path workbench (CAM toolpaths), Surface workbench, Arch/BIM, Spreadsheet, TechDraw dimensions, contact/spring FEM constraints, B-spline sketcher operations, expression-engine bindings, anything in a workbench AnkusDrive doesn't wrap.

python
run_script(code='''import Pathjob = Path.Job.Create("Job", [_resolve("pad_1")])__result__ = {"job_name": job.Name}''')

Inside the script, the worker pre-injects: App / FreeCAD, Part, ObjectsFem, plus _register(prefix, obj) / _resolve(handle) / _handles so scripts can register new objects into the same handle registry that typed tools use. Set __result__ = ... to a JSON-serializable value to return data; print statements go to /dev/null.

The escape hatch costs more (the agent has to write FreeCAD Python) but makes the entire FreeCAD API reachable. The Phase 2 plan's "After Phase 2" section calls out which run_script patterns deserve promotion to typed tools — that's how the surface grows over time.

Regenerating a session

session_transcript() returns the session so far as a script:

python
with Session() as s:    r2 = s.add_primitive(kind='box', w=40.0, d=20.0, h=5.0)    box_1 = r2['handle']    r3 = s.add_primitive(kind='cylinder', h=5.0, r=3.0)    cylinder_1 = r3['handle']    r4 = s.boolean_op(op='cut', base=box_1, tool=cylinder_1)    s.check(r4, 'volume', 3964.657082647114)    s.save_document(path=str(WORKDIR / 'bracket.FCStd'))

Save it and run python transcript.py [WORKDIR] to rebuild the session in a fresh worker. ankusdrive.replay.Session calls the same functions the MCP server serves, so a transcript can call no tool the server doesn't have. run_script stays behind its switch, and a missing solver stops the run at that step. Each s.check() stops the run at the first result that differs from the recording. The script's header lists what it can't reproduce: failed calls, run_script code, and files the session read, which you copy into WORKDIR first.

Auditing an analysis

The same tool answers the other question a transcript is for: what exactly produced this number? A simulation session exports with its derivation intact — the closed-form estimates are not dropped as "inspection", every number an analysis or a solve reported becomes an s.check(), and the verdict fields become s.expect(), so a replay that reaches a different solver or falls back to a different correlation stops there rather than returning a plausible figure:

python
PROVENANCE = {'env': {'ankusdrive': '0.5.5', 'freecad': {'version': '1.1.0'},                      'substrate': 'container', ...},              'solvers': {'elmer': {'version': '26.2', 'via': 'container',                                    'path': '/usr/bin/ElmerSolver', ...}}}
with Session() as s:    s.provenance(PROVENANCE)                     # prints every difference from the recording
    r1 = s.h_estimate(geometry='vertical_plate', characteristic_mm=100.0, t_surface_c=200.0)    s.check(r1, 'h_total_w_m2k', 8.4111, rel=0.001)    s.expect(r1, 'correlation', 'churchill_chu_vertical_plate')
    r2 = s.thermal_transient_submit(half_thickness_mm=1.5, h_conv=8.0, duration_s=600.0, ...)    r5 = s.wait(r2['job_id'])    s.check(r5, ('result', 't_center_c'), 45.49120518934, rel=0.001)    s.expect(r5, ('result', 'solver'), 'elmer')

s.provenance() re-resolves the whole environment on the replaying machine and prints what moved: a different Elmer version, a solver that relocated when the substrate changed, a FreeCAD that is not the one that built the model. A replay on a different solver is not a failure — it is the finding. Solver paths under $HOME are collapsed to ~/…, so the record says which install without saying who. session_transcript also returns the record as provenance for attaching to a report; pass provenance=False to skip it and the version probes it runs.

Keeping the record: the durable journal

A transcript lives as long as the server does. For analysis that feeds a design decision, turn on the durable journal — it is off by default, and one setting enables it:

bash
export ANKUSDRIVE_JOURNAL_DIR=~/ankusdrive-journal     # or journal_dir = "..." in config.toml

Every tool call is then appended to session-<start>-<pid>-<id>.jsonl in that directory: a header with the environment (AnkusDrive / Python / platform / substrate), the FreeCAD each worker booted, and one line per call — arguments whole (run_script code verbatim with its SHA-256), the trimmed result, the solver each solve resolved to, and result_digest, a SHA-256 over the full result, so a result the journal had to trim is still pinned. Writes are best-effort: a journal that cannot be written logs a warning and never fails or changes a tool call.

After the server has exited, the file turns back into what session_transcript would have returned — replay script, recorded environment, ordered call ledger with digests:

bash
ankusdrive journal listankusdrive journal export latest -o transcript.py      # --json for the whole record

or, from an agent, journal_export(session="latest") (omit session to list).

Retention is stated, not left to the OS: at most 50 session files and 512 MB (ANKUSDRIVE_JOURNAL_KEEP, ANKUSDRIVE_JOURNAL_MAX_MB), oldest first, never the live session's file or one written in the last hour (ANKUSDRIVE_JOURNAL_GRACE_S); one session past 64 MB (ANKUSDRIVE_JOURNAL_FILE_MAX_MB) keeps arguments and digests but drops result bodies. ANKUSDRIVE_JOURNAL_REDACT=1 hashes paths and names (document names, labels, title-block fields) in arguments, results and errors — never run_script code or handles. The trade-off: a redacted journal is auditable by digest, but the script it exports is not runnable.

To make the record travel with the model, save with save_document(path, attach_provenance=True) (off by default). The document then carries the same record — replay script, environment and solver identities, ledger with each result's SHA-256 — in its Meta map, which survives FreeCAD re-saving the file. It covers the calls that built that document: the saving workspace's current worker, from the new_document / open_document that produced it, while it was the active document, successful calls only. ANKUSDRIVE_JOURNAL_REDACT applies to it too. A save without the flag removes an earlier record. Read it back without FreeCAD:

bash
ankusdrive journal export bracket.FCStd -o transcript.py   # --json for the whole record

What the CLI is (and isn't)

The CLI is not the agent surface — it's a human-debugging + transport tool. Seven subcommands:

CommandPurpose
ankusdrive ping / versionHealth check — boot a worker, prove FreeCAD is reachable
ankusdrive box / cylinderSingle-shot primitive → .FCStd (manual smoke tests)
ankusdrive export <in.FCStd> -o <out.step>Headless format conversion
ankusdrive run <script.py>Execute arbitrary FreeCAD Python in a live worker (set __result__ to return JSON)
ankusdrive mcpStart the MCP server over stdio — this is how an MCP host launches AnkusDrive
ankusdrive fem cantileverRun the built-in canned demo
ankusdrive journal list / export <session|latest|file>Read the opt-in durable journal: list past sessions, export one as a replay script + provenance record

Agents do not invoke the CLI. They speak MCP via stdio after the host has launched ankusdrive mcp. The CLI's job is (a) to start that server and (b) to give a human a way to poke at the worker without writing an MCP client.

Decision rule

NeedUse
Standard CAD/FEM operationFirst-class MCP tool (Layer 1)
Tool exists but I need property Xget_object / set_property (Layer 2)
Workbench / API not wrapped at allrun_script (Layer 3)
Smoke test from a shell, or stand up MCPCLI

Multi-agent design

The roadmap above is about deepening what one agent can do. The orchestration/ layer is about many agents sharing the work: split a product into components and subassemblies, build those in parallel (each agent cold, seeing only its own contract slice), then merge the whole back up with the joints actually fitting. The design is written up in docs/MULTI_AGENT.md; it targets partition + merge, not shared co-editing of one live document (a single worker = one App.ActiveDocument, so concurrent mutation is a non-goal for now).

Concurrent agents on one MCP server — workspaces. FastMCP runs sync tools in a thread pool, so a host can have several tool calls in flight at once. The server keeps a pool of named workspaces, each its own freecadcmd process with its own App.ActiveDocument and handle registry. Each concurrent agent passes its own workspace=<name> on every tool call, which pins that call to its workspace (created on first use); handles and documents do not cross workspaces. Don't use use_workspace for this: its selection is server-wide, so one agent's switch retargets everyone's calls (Claude Code subagents, for one, share their parent session's server). A single agent may call use_workspace once instead; a client that does neither sees the historical single-worker behavior byte-for-byte (everything routes to the default workspace). Worker.call() is internally serialized so two threads can never interleave the stdin/stdout protocol on one process. The pool is capped (ANKUSDRIVE_MAX_WORKSPACES, default 4 including default) and idle workspaces are reaped (ANKUSDRIVE_WORKSPACE_IDLE_S, default 900s) so abandoned sessions don't leak processes; list_workspaces / close_workspace manage it.

The split of responsibilities is deliberate:

  • AnkusDrive ships the thin, tool-agnostic primitives that make a merge verifiable — publish_interface (declare a component's mating frames), merge_assembly (combine component files into one assembly), and the gates that decide whether a merge is sound: interface_align_check (do published frames line up?), interference_check (do solids collide?), envelope_check (does it fit its bounding budget?), plus an assembly_lock / assembly_lock_check contract lockfile. These are real MCP tools usable by any host.
  • orchestration/ is the host-side reference coordinator — explicitly not part of the ankusdrive package. Given a free-text brief it decomposes it into a validated manifest, fans out one builder agent per component, merge_assemblys them, reads the gates, and on failure renegotiates — re-dispatching only the components implicated by the failing gate — up to a round budget. It runs against a real Anthropic client or a scripted stub (ScriptedClient) for free dry runs; the merge and gates are real worker calls either way. Any host (Claude, another tool, a human) can use it, replace it, or ignore it — the only contract that matters is the manifest + the component files on disk.

How well partition+merge holds up is measured by a dedicated eval ladder (tests/test_multiagent_m1.py / _m2.py, runnable in CI) with hard-oracle merge gates and a single-agent baseline — see tests/MULTI_AGENT_EVAL.md. Early experiments have partition performing at or above the single-agent baseline on the harder toys.

Designs, not just parts — the design-control layer

Multi-agent orchestration partitions one product across a team. A separate axis makes a design (not just a part) something you can parameterize, vary, and evolve under control — the mechanisms a PLM/PDM workflow expects, mapped onto AnkusDrive's deterministic, headless, git-diffable grain. The keystone insight: the build recipe is the feature tree; the parameters are its inputs; regeneration is re-running the recipe — so AnkusDrive gets parametric regen and family tables without a live in-file expression engine. The full scoping and rationale is in docs/DESIGN_HIERARCHY.md; the agent-facing judgment lives in the design-modularly skill.

  • Parametric hierarchy — recipes (recipe, recipe_validate) are named, declared-input build templates (AnkusDrive's PowerCopy/UDF and its intra-part parametric model); a relations DAG drives driven dimensions from master parameters by formula (pitch_d = module * teeth, arithmetic only — no iterative solve, no double-driving); feature templates (feature_instantiate) graft reusable features onto reference geometry by name; the typed units layer rejects dimensionally-wrong inputs at the door ("5 N" for a length is an error, not a silent mis-scale).
  • Variant families — family_materialize expands a row × column design table into a set of variants deterministically, running the recipe per row and allocating part numbers in table order.
  • Identity & lifecycle — items (items_new) give a part a stable part-number identity decoupled from its file path; a lifecycle state machine (lifecycle_transition: in_work → in_review → released → obsolete) enforces released-immutability, and a Form/Fit/Function predicate decides revision bump vs. new part number on a change.
  • Change control — ECOs (eco_create) are first-class change records; where_used / change_impact compute blast radius over the dependency graph before you commit; baseline_create / baseline_verify pin reproducible snapshots.
  • Versioned interfaces — an interface-type registry (get_interface, nema17_face@1-style named/versioned types) plus a Liskov substitutability_check gate enforce Form/Fit/Function compatibility as code, so a swapped part is verified to actually mate.
  • Projects — scaffold_project + project_validate / project_check_references promote the multi-agent directory convention to a first-class project.json (manifest-of-manifests) with a master/skeleton single-source-of-truth slot and reference-integrity guards.

Like the merge gates, these are thin, deterministic, mostly FreeCAD-free primitives — the logic layers import and test without launching a worker.

Status

Phase 3 closed 2026-05-10 (v0.3.0). The core mechanical-design surface from Phase 2 (2026-04-25) is intact; Phase 3 layered intent-encoding APIs on top of it. Since then the tool surface has grown from ~100 to 280+ tools across several waves: a command-tier expansion (parametric components + direct feature ops + metrology), the multi-agent orchestration layer, a broad engineering-analysis + external-solver simulation surface (thermal/CFD/CHT/ EM/acoustics/FSI/molding/granular/optics/multibody), and a design-control (PLM) layer (items, recipes, variant families, lifecycle, ECO/change, versioned interfaces, projects).

  • Worker + transport — long-lived freecadcmd worker, newline-JSON over stdio with stdio hygiene (FreeCAD C++ chatter redirected off the protocol fd).
  • CLI — ping, version, box, cylinder, export, run, mcp, fem cantilever, plus top-level --version.
  • MCP server — FastMCP over stdio, 280+ typed tools across document lifecycle, primitives, selection (face/edge tags), full PartDesign (sketcher + pad/pocket/revolve/hole/loft/sweep/helix/fillet/chamfer/pattern/mirror/thickness/draft), direct-modeling feature ops, parametric components, metrology/inspection, generic property reflection, mass properties, assembly + interface gates, TechDraw (incl. headless PDF/SVG/DXF export, dimensions, gates), multi-view + photoreal rendering, FEM (static + modal + buckling + thermal + nonlinear + result-probe), the engineering-analysis oracles and external-solver simulation families (sync + async *_submit/job_*), the materials/fluids corpora, Design-for-X / manufacturing checks, the design-control (PLM) layer, and transactions.
  • Command tiers 1–3 — 21 new tools: parametric components (add_gear, add_rack, add_sprocket, add_pulley, add_spring, add_fastener, add_bearing, add_thread), direct feature ops (fillet_edges, chamfer_edges, shell_solid, add_rib, engrave_text, oring_groove, transform, scale_shape, copy_shape), and metrology/inspection (measure_distance, measure_angle, bounding_box, check_shape, section_view, min_clearance).
  • Multi-agent orchestration — AnkusDrive ships the thin merge primitives + gates (publish_interface, merge_assembly, interface_align_check, envelope_check, assembly_lock/_check); the host-side reference coordinator (orchestration/) decomposes a brief, fans out per-component builders, merges, gates, and renegotiates. See Multi-agent design and docs/MULTI_AGENT.md.
  • Phase 3 intent-encoding additions — direction='into_body'|'away_from_body' and through='wall'|'body' on pocket/hole (ray-cast wall depth handles hollow shells correctly); intended_for='print'|'machine'|'drawing' on hole drives ModelThread; verify_feature diffs actual-vs-expected volume change to catch silent failures; visibility hygiene at save hides consumed inputs; register_handle + run_script auto_register close the escape-hatch one-way trapdoor; list_thread_options surfaces the coupled ThreadType/ThreadSize enums dynamically; revolve has an OCCT pre-check that flags axis-coincident edges with an actionable error.
  • Selection layer — list_faces / list_edges / query_faces / resolve_* produce stable geometric tags that survive edits; FEM constraints take tags directly.
  • Rendering — host-side software rasterizer (ankusdrive/render.py) with per-pixel z-buffer (render_view / render_views return PNGs as MCP ImageContent), plus photoreal render_photoreal via the FreeCAD Render addon + an external renderer (POV-Ray / LuxCore / Appleseed / Cycles / OSPRay / PBRT). Support matrix, install, and limitations: docs/RENDERING.md.
  • Simulation surface — engineering-analysis oracles (machine elements, structural, durability, thermal, tolerance/GD&T) plus external-solver families that shell out to OpenFOAM / Elmer / CalculiX / openEMS / YADE / KrakenOS, discovered at runtime by ankusdrive/solvers.py and degrading cleanly when absent. Long solves use an async submit→poll job pattern (*_submit + job_status/job_result/job_list). Catalog and result schemas: docs/SIMULATION_TOOLS.md; proof harness: docs/SIMULATION_EXAMPLES.md. Materials/fluids back these via material_* and fluid_props (mechanical-property / molding / CoolProp corpora).
  • Design-control (PLM) layer — recipes + a relations DAG (parametric regen), feature templates, variant families from a design table, item/part-number identity, a lifecycle/revision state machine, ECO change records with where-used/impact + baselines, a versioned interface registry + Liskov substitutability gate, and project containers with reference-integrity guards. Scoping + rationale: docs/DESIGN_HIERARCHY.md. See Designs, not just parts.
  • Tests — ~980 test functions across ~90 files (worker / MCP / CLI / render / determinism / edit stability / negative paths / perf / multi-agent / simulation families / molding / PLM layer), runnable via tests/run_all.sh (Linux/macOS) or tests/run_all.ps1 (Windows — single-interpreter, skips the Linux-only solver families; see docs/WINDOWS.md). Reliability harness (Layer A classification, B diff-detection, C agent-loop closure) is gated behind RUN_RELIABILITY=1; see tests/RELIABILITY.md.

See docs/ROADMAP.md for the Phase 1/2 per-slice record — a changelog of how the surface above was built, frozen at the close of Phase 2. Open work (FEM contact/spring/tie refinements, feature_tree introspection, deeper external-solver integrations) lives on the issue tracker, not in that file.

The committed showcase — every GIF, render, drawing and exported solid the docs point at, with the script that regenerates each one — is indexed in artifacts/README.md.

Open questions

  • Error model: FreeCAD raises plain Python exceptions from C++; worker catches and serializes them, but stack context across the JSON boundary is still lossy.
  • Async / concurrency: multi-doc shipped (list_documents / set_active_document / close_document), and every long-running solve — CalculiX included, via fem_run_submit — can run off the channel through the *_submit + job_* pattern. The synchronous fem_run remains for small solves. One worker still means one main thread: a job's FreeCAD-side steps (writing the solver input, importing results) run on it, between requests.

Privacy Policy

AnkusDrive runs entirely on your computer and collects nothing: no telemetry, no analytics, no accounts, and no network requests from its own code. It reads and writes only the files you point it at, plus its config file and temp working directories. Your MCP host (e.g. Claude Desktop) sends tool inputs and results to its AI model provider under the host's own policy. The full policy, including exactly what is stored where, is in PRIVACY.md.

Evaluating the Claude Desktop extension? The reviewer guide covers installation, a setup check, and three example prompts with expected results.

License

Licensed under the Apache License, Version 2.0. Contributions submitted to this project are licensed under the same terms (Apache 2.0 §5: inbound = outbound), which means contributors retain copyright but grant the project — and everyone downstream — a perpetual, irrevocable license to use their work, including a patent grant. The intent is to keep the project welcoming to contributors while ensuring nobody can later re-proprietize what they contributed.

The code is Apache-2.0; the name is not. Apache-2.0 §6 grants no trademark rights, so the AnkusDrive word mark and the brand assets in logo/ are covered separately — see TRADEMARKS.md for what you may do without asking (referring to the project, compatibility claims, redistribution, packaging, and forking all qualify) and NOTICE for the attribution a redistributor must carry.

References

來源:README.md,提交 bcbfab3

工具

200

ping

Ping

Check that the FreeCAD worker is alive. Returns 'pong' on success.

輸入參數workspace

restart_worker

Restart Worker

Kill the current workspace's FreeCAD worker process and spawn a fresh one. Use when the worker is wedged (e.g. App.ActiveDocument desynced from internal state). All open documents, unsaved changes, and handles in THIS workspace are lost — save first if needed. Other workspaces are untouched. Returns {restarted: True, workspace: <name>, freecad: [...]}.

輸入參數workspace

use_workspace

Use Workspace

Claim (creating if needed) an isolated workspace and make it the target of subsequent tool calls. Each workspace is its own freecadcmd process with its own ActiveDocument and handle registry — handles do NOT cross workspaces. The selection is SERVER-WIDE: it retargets every client sharing this server, including other subagents of the same session. Concurrent agents must instead pass `workspace=<unique name>` on every tool call, which pins just that call and leaves this selection alone. The pool is capped (ANKUSDRIVE_MAX_WORKSPACES, default 4) and idle workspaces are reaped (ANKUSDRIVE_WORKSPACE_IDLE_S, default 900s); claiming a workspace beyond a full pool raises — close an idle one first. Pass "default" to return to the baseline single-agent workspace. Returns {workspace: <name>, freecad: [...], workspaces: [names]}.

輸入參數name必填workspace

list_workspaces

List Workspaces

List the live workspaces (freecadcmd processes) and the pool limits. Each entry is {name, alive, idle_s, current}. Use to see who is holding a slot before claiming or closing one. `current` is the workspace THIS call runs in: the `workspace` you passed, else the use_workspace selection. `selected` is that server-wide selection, which a pinned call neither follows nor moves. Returns {current, selected, max, idle_reap_s, workspaces: [...]}.

輸入參數workspace

close_workspace

Close Workspace

Shut down a workspace's worker and free its pool slot. All of that workspace's documents, unsaved changes, and handles are lost. The default workspace can be closed too (its worker respawns clean on next use). If `name` is the use_workspace selection, the selection returns to "default", whoever closes it. A call pinned with `workspace` never moves the selection otherwise: closing your own pinned workspace leaves the pin as it is, so your next call pinned to that name gets a fresh, empty worker (old handles fail). `current` is the workspace this call ran in (as in list_workspaces), `selected` the server-wide selection after the close. Returns {closed: <bool>, workspace: <name>, current: <name>, selected: <name>}.

輸入參數name必填workspace

session_transcript

Session Transcript

Export this session as a Python script that regenerates it (model, drawings, simulations) by calling these same tools in a fresh worker — and, with it, the record of what computed each number. Read-only: returns the script as text and writes nothing. Covers one workspace (default: the current one). In the script, handles are variables, job polls are one s.wait(job), and inspection calls are dropped unless a later call used their result. Absolute paths become WORKDIR-relative. The header lists failed calls, run_script use and input files. THE AUDIT SURFACE. Analysis calls are never dropped — every hand-calc, FEM read and *_submit is in the script, because they are the derivation, not inspection. Each analysis and solve result is checked whole: every number it reported becomes an s.check() and its verdict fields (fidelity, correlation, solver, gate pass) become s.expect(), so a replay that reached a different solver or fell back to a different correlation stops there instead of returning a plausible number. run_script code is carried verbatim with its SHA-256. With provenance (default), the script opens with a PROVENANCE record — AnkusDrive/FreeCAD/platform/substrate plus every solver the session reached, with its resolved path, substrate and probed version — and calls s.provenance(), which re-resolves that on the replaying machine and prints every difference. Hand this to anyone who has to answer "what exactly produced this figure?". include_read_only: keep every inspection call. checkpoints: emit s.check()/s.expect(). prune_aborted: drop calls a transaction_abort undid. provenance=False skips the environment record (and the solver version probes it runs, ~0.1-3 s per solver). Returns {workspace, script, calls, exported, skipped: {reason: count}, warnings, prerequisites, truncated, provenance}. Run it as `python transcript.py [WORKDIR]`.

輸入參數workspaceprovenancecheckpointsprune_abortedinclude_read_only

journal_export

Journal Export

Export a past session from the on-disk journal (off by default; on with ANKUSDRIVE_JOURNAL_DIR) as session_transcript would have: the replay script, the recorded environment, and the ordered call ledger with each result's SHA-256. Read-only. session: an id or "latest"; omit to list sessions. workspace: default is the one current when the session ended. provenance=False skips solver version probes. A redacted journal (ANKUSDRIVE_JOURNAL_REDACT) exports an audit record, not a runnable script. More options: `ankusdrive journal export`. Returns {enabled, dir, live_session, sessions} when listing, else {session, workspace, env, script, ledger, warnings, prerequisites, truncated, redacted, integrity, provenance, ...}.

輸入參數sessionworkspaceprovenance

version

Version

Return FreeCAD and bundled Python versions from the worker.

輸入參數workspace

new_document

New Document

Create a new FreeCAD document and make it active. Returns {doc: <name>}.

輸入參數nameworkspace

open_document

Open Document

Open an existing .FCStd file, make it active. Returns doc name and object list.

輸入參數path必填workspace

save_document

Save Document

Save the active document to the given .FCStd path. visibility_hygiene (default True): before saving, hide any object that has been consumed as a producer-input (the Base/Tool of a Cut, the BaseFeature of a Body, every feature inside a Body's Group, etc.). Without this the re-opened doc double-renders intermediates on top of the final shape — a failure mode that looks identical to broken geometry. Pass False to keep explicit set_visibility overrides intact. attach_provenance: embed this document's journal_export record (PRIVACY.md).

輸入參數path必填workspaceattach_provenancevisibility_hygiene

list_objects

List Objects

List objects in the active document. Returns [{name, type, label}, ...].

輸入參數workspace

add_primitive

Add Primitive

Add a primitive to the active document. kind: 'box' (uses w, d, h), 'cylinder' (uses r, h), or 'sphere' (uses r). placement: optional [x, y, z] mm translation. name: optional human-facing name. It sets the object's LABEL — what list_objects, bom_extract and the drawing/manifest layers display — and leaves the internal FreeCAD Name alone, since handles and register_handle key off Name and it must stay unique and stable. Omit it and the label stays the type default ('Box' / 'Cylinder' / 'Sphere'), which BOM and designation checks read as an unnamed generic solid. Returns {handle, name, label, volume}: `name` is FreeCAD's internal id and `label` is the display name (equal to `name` when you passed none). The handle (e.g. 'box_1') is how you reference this object in subsequent boolean_op calls.

輸入參數dhrwkind必填nameplacementworkspace

add_gear

Add Gear

Add an involute spur gear (FreeCAD's core involute generator), extruded to a solid. teeth: tooth count (>= 3). module: mm (pitch diameter = module * teeth). height: extrusion thickness mm. pressure_angle: deg (default 20). external: True for an external gear; False for an internal/ring tooth profile. placement: optional [x, y, z] mm translation. Returns {handle, name, volume, pitch_radius, tip_radius, root_radius, teeth, module, external}. Two external gears MESH when their axes are spaced (pitch_radius_a + pitch_radius_b) apart; phase one by half a tooth to avoid tooth-on-tooth interference.

輸入參數nameteeth必填heightmodule必填externalplacementworkspacepressure_angle

add_rack

Add Rack

Add a linear gear rack (a spur gear's straight counterpart) as a solid. A rack is a gear of infinite radius: straight-flanked teeth on a rail. Standard full-depth tooth form (addendum = module, dedendum = 1.25*module, tooth height = 2.25*module, flanks at pressure_angle from vertical). teeth: number of teeth (>= 1). module: mm (sets tooth size; circular pitch = module * pi). height: extrusion thickness mm along +Y (the rack's face width; default 6). width: mm, rail base-band thickness below the tooth root line (default 10). pressure_angle: deg, flank angle from vertical (default 20; 0 < pa < 45). placement: optional [x, y, z] mm translation of the rack origin. name: object label (default "Rack"). The profile lies in the XZ plane: root line at z=0, base band from z=-width to z=0, teeth from z=0 to z=2.25*module, extruded along +Y by height. Returns {handle, name, volume (mm^3), pitch (mm/tooth = module*pi), module, teeth, tooth_height (2.25*module mm), length (teeth*module*pi mm)}. A spur gear MESHES with this rack when their `pitch` values match (gear module*pi == rack pitch); `length` sizes the rail for the travel.

輸入參數nameteeth必填widthheightmodule必填placementworkspacepressure_angle

add_sprocket

Add Sprocket

Add a roller-chain sprocket (ISO 606 / ANSI), built as a static solid plate. teeth: tooth count (>= 3). chain_pitch: chain link pitch in mm (e.g. 12.7 for #40 / ANSI 40 chain). roller_diameter: chain roller diameter in mm. height: plate thickness in mm (default 6.0). placement: optional [x, y, z] mm translation. Build: a disc of tip radius ~= pitch_radius + chain_pitch*0.3 with `teeth` roller seats (circular pockets, radius roller_diameter/2 * 1.05) cut on the pitch circle, one per tooth. This is a fit/visualisation approximation of the true ISO 606 tooth form, not a load-rated profile. Returns {handle, name, volume, pitch_diameter, chain_pitch, teeth, tip_radius, bore}. pitch_diameter (mm) = chain_pitch / sin(pi/teeth) and chain_pitch are the MATING numbers: a chain of the same chain_pitch wraps the sprocket, and the centre distance between two sprockets derives from their pitch_diameters. bore is 0 (no shaft hole cut yet — drill one with the `hole` command).

輸入參數nameteeth必填heightplacementworkspacechain_pitch必填roller_diameter必填

add_pulley

Add Pulley

Add a timing-belt (or V) pulley as a static solid. Axis is +Z; toothed belt face spans z in [0, width]. teeth: tooth count (>= 6). belt_pitch: belt tooth pitch mm/tooth (e.g. 2.0 for GT2, 3.0 for GT3/HTD-3M); pitch diameter PD = belt_pitch * teeth / pi. width: belt-face length mm. flanged: True adds two thin guide discs (radius PD/2 + 2*belt_pitch) at each end to retain the belt. height: optional mm; OVERRIDES width when given (default height = width). placement: optional [x, y, z] mm translation of the axis base. name: object label. Returns {handle, name, volume, pitch_diameter, belt_pitch, teeth, width, flanged}. pitch_diameter (mm) is the mating number: the centre distance to a mating pulley plus the required belt length derive from the two pitch diameters and the same belt_pitch.

輸入參數nameteeth必填width必填heightflangedplacementworkspacebelt_pitch必填

add_spring

Add Spring

Add a helical compression spring: a round wire swept along a cylindrical helix. All lengths in mm; angles n/a. wire_diameter: wire (stock) diameter d, mm. outer_diameter: spring outer diameter OD, mm (must be > wire_diameter). free_length: uncompressed overall length along the axis, mm. coils: number of turns (active coils), may be fractional. kind: 'compression' (only supported mode in v1; end coils are not squared yet). placement: optional [x, y, z] mm translation of the spring's base. Geometry: mean coil diameter D = outer_diameter - wire_diameter; coil pitch = free_length / coils. Spring rate is computed for STEEL (shear modulus G = 79.3 GPa) as k = G*d^4 / (8*D^3*coils), reported in N/mm. Returns {handle, name, volume (mm^3), mean_diameter (mm), free_length (mm), coils, kind, solid_height (mm, = coils*wire_diameter, the fully-compressed block height), spring_rate_n_per_mm (N/mm)}. Use free_length, solid_height and spring_rate_n_per_mm to spec the spring into a mechanism (available travel = free_length - solid_height; force = spring_rate_n_per_mm * deflection).

輸入參數kindnamecoils必填placementworkspacefree_length必填wire_diameter必填outer_diameter必填

add_fastener

Add Fastener

Add a standard ISO metric fastener (screw / bolt / nut / washer) as a solid. kind: one of "socket_head_cap_screw", "hex_bolt", "hex_nut", "washer". - socket_head_cap_screw: cylindrical head with a cosmetic hex socket + plain shank (threads not modeled). - hex_bolt: hex head (across-flats) + plain shank. - hex_nut: hex prism with an axial clearance hole. - washer: flat annular ring. size: ISO designation, one of "M3","M4","M5","M6","M8","M10","M12". length: shank length in mm. REQUIRED for socket_head_cap_screw and hex_bolt; ignored for nut/washer. grade: optional material / property class AS ORDERED — ISO 898-1 for steel screws ("8.8", "12.9"), ISO 898-2 for nuts ("8", "10"), ISO 3506 for stainless ("A2", "A4-80"). It changes no geometry. What it changes is the ORDERABLE designation stamped on the part: with it you get "ISO 4762 M4×12 A2", which a buyer can quote; without it the designation comes back complete=False saying nobody has chosen between class 8.8 steel and A2 stainless yet. An unrecognised grade is a loud error, never a guess. placement: optional [x, y, z] mm translation of the fastener origin (head top sits at z=0, shank runs in -z for screws/bolts). name: optional object name (default derived from kind). All dimensions are in mm. Threads are cosmetic (the shank is a plain cylinder of the major diameter). Returns {handle, name, kind, size, major_diameter, pitch, volume, designation, orderable} plus, by kind: screws/bolts add {length, head_diameter, head_height, model_thread:false}; nut adds {head_diameter (wrench across-flats), head_height}; washer adds {head_diameter (outer diameter), head_height (thickness)}. Mating numbers: drill a through-hole of major_diameter (+ clearance) for the shank; head_diameter sizes a counterbore. `designation` is the canonical orderable identity ("ISO 4762 M4×12 A2") and `order

輸入參數kind必填namesize必填gradelengthplacementworkspace

add_bearing

Add Bearing

Add a deep-groove ball bearing as an assembly *envelope* solid: an annular ring (outer-diameter cylinder minus bore cylinder) of the given width, axis along +Z. Balls/races are not modeled — this is the fit envelope a coordinator needs to size the shaft, the housing bore, and the shoulder spacing. Specify dimensions ONE of two ways: - designation: a standard metric series code, looked up in a built-in table. Known: "608", "623", "624", "625", "626", "688", "6000", "6200", "6800", "6900". (e.g. "608" -> bore 8, OD 22, width 7 mm.) - bore + outer_diameter + width: explicit dims in mm (all three required). Explicit values override a designation's table values when both are given. bore: inner-bore diameter mm (sizes the shaft). outer_diameter: OD mm (sizes the housing bore). width: axial length mm (shoulder spacing). placement: optional [x, y, z] mm translation of the bearing's near face. seals: open (default) | RS | 2RS | RZ | 2RZ | Z | 2Z. It changes no geometry — the envelope is identical — but it IS part of the orderable identity: 608, 608-2Z and 608-2RS are three different purchases with different drag, speed limits and prices. It is folded into the canonical designation stamped on the part ("608-2RS"). A bearing built from raw bore/OD/width with NO designation is a dimensional envelope, not a purchasable part, and is deliberately left undesignated rather than given a made-up catalog number. Raises ValueError if the designation is unknown and dims are incomplete, or if outer_diameter <= bore. Returns {handle, name, designation, bore, outer_diameter, width, volume, orderable, catalog}. handle starts "bearing_". designation is None when built from explicit dims; `orderable` is the designation card, whose own `designation` carries the seal suffix; `catalog` is the off-the-shelf verdict (stocked or not, None for an undesignated envelope).

輸入參數borenamesealswidthplacementworkspacedesignationouter_diameter

oring_groove

O-Ring Groove

Compute a static O-ring gland (groove) and optionally cut it into a face. This is the gland calc designers always fumble, plus an optional cut. Given the O-ring cross-section it returns standard static-seal gland dimensions; with cut=True it also machines the annular groove into a flat face. cross_section: O-ring wire cross-section diameter in mm (e.g. 1.78, 2.62). Required, > 0. inner_diameter: groove inner diameter in mm (the O-ring's nominal seal ID). Required when cut=True; used to size the returned diameters either way. handle: host solid to cut into (required only when cut=True). face: the flat face to cut the groove into — a stable f_* tag (preferred), a 'FaceN' index string, or an int. Required when cut=True. Must be planar. gland_type: seal-geometry label, default 'static_radial' (informational). compound: optional elastomer + durometer the RING is ordered in ("NBR70", "FKM75", "EPDM70"). Changes no geometry; it completes the AS568 designation of the ring itself — the purchased part this groove exists to hold, which no BOM would otherwise contain because the ring is never a modelled object. cut: True (default) cuts the groove and returns a new solid; False makes this a pure calculator (no geometry, no handle). name: name for the resulting solid when cut=True. Gland rule (static seal): groove_depth = cross_section*0.75 (~25% squeeze, clamped to a 20-30% band), groove_width = cross_section*1.30. The groove's inner diameter equals inner_diameter and it spans outward by groove_width. Returns {groove_depth, groove_width, groove_inner_diameter, groove_outer_diameter, squeeze_pct, cross_section, gland_type, oring} (all mm except squeeze_pct in percent). When cut=True it ALSO returns {handle, name, volume} for the grooved solid; the host input is hidden. mating numbers: cut a groove of inner_diameter to seat an O-ring of that ID; groove_ou

輸入參數cutfacenamehandlecompoundworkspacegland_typecross_section必填inner_diameter

chamfer_edges

Chamfer Edges

Chamfer (bevel) edges of a shaped Part object — fillet_edges' counterpart. edges: e_* tags (preferred), 'EdgeN' strings, or 1-based ints. size: symmetric leg distance mm (> 0). per_edge / allow_partial: as fillet_edges. The base object is hidden. Validated exactly like fillet_edges (#283). Returns {handle, name, volume (mm^3), edges (indices chamfered), checks {valid, solids, envelope_ok, envelope_growth_mm, envelope_tol_mm}, mode, partial}; when partial, also skipped_edges and a warning. On a failed check the feature is removed and BlendCheckFailed names the offending edges.

輸入參數namesizeedges必填handle必填per_edgeworkspaceallow_partial

shell_solid

Shell Solid

Hollow a raw Part solid into a thin-walled shell (`thickness` does the same for PartDesign bodies). faces: NON-EMPTY list of faces to REMOVE (the openings) — f_* tags (preferred), 'FaceN', or 1-based ints. thickness: wall mm (> 0), grown INWARD so outer dimensions are kept. The input is hidden. Returns {handle (starts 'shell_'), name, volume (mm^3), wall_thickness, removed_faces}. Raises on empty faces, thickness <= 0, a bad index, or an offset too large for a valid shell.

輸入參數namefaces必填handle必填thickness必填workspace

add_thread

Add Thread

Generate a REAL helical ISO-style 60-degree thread as a static solid. Unlike `hole`/`list_thread_options` (which only flag a thread as metadata), this cuts actual helical geometry: a truncated triangular rib swept along a helix and fused to a core cylinder. All lengths in mm, angles in degrees. diameter: nominal MAJOR (crest) diameter, mm. For internal=True this is the bore the tap fits. pitch: thread pitch, mm per turn (e.g. M8 coarse = 1.25). length: threaded length along +Z from z=0, mm. internal: False (default) -> a finished externally-threaded stud. True -> a TAP/insert cutting-tool solid sized to the bore; fuse it into (or cut it from) a bored hole in your part to produce a threaded bore. starts: number of thread starts, >=1. Multi-start repeats the helix rotated by 360/starts and uses lead = pitch*starts. grade: optional material / property class as ordered ("8.8", "A2"). Changes no geometry; it completes the DIN 976-1 threaded-rod designation stamped on an EXTERNAL single-start thread — that solid is studding, a thing you buy by the metre. An internal thread is a tap-shaped cutting tool and a multi-start is not a stock item, so neither is designated at all. placement: optional [x, y, z] mm translation of the solid's base (default at the origin, axis along +Z). name: optional object name. Geometry note: the modeled minor (root) uses the ISO 5H/8 truncation; the reported minor_diameter uses the standard ISO formula diameter - 1.0825*pitch. Fallback behaviour: if the helical sweep cannot produce a valid solid the tool returns a plain cylinder tagged with the thread spec and modeled=False (this is rare for sane M-series inputs); always check the modeled flag. Returns {handle, name, volume (mm^3), major_diameter (mm), minor_diameter (mm), pitch (mm), length (mm), starts (int), internal (bool), modeled (bool), designation,

輸入參數namegradepitch必填length必填startsdiameter必填internalplacementworkspace

engrave_text

Engrave Text

Engrave (cut) or emboss (add) extruded text on a planar face of a solid, centred on the face centroid. The host is replaced by the returned handle. face: planar face — f_* tag (preferred), 'FaceN', or int. size: cap height mm. depth: engrave/emboss depth mm. mode: 'engrave' (removes material) | 'emboss' (adds). position: optional [u, v] mm offset from the centroid along the text's X/Y. font: absolute path to a .ttf/.ttc. Omitted: only macOS Arial/Helvetica are probed, so elsewhere it raises — pass a path. Returns {handle, name, volume (mm^3 of the result), text, mode, depth}.

輸入參數face必填fontmodenamesizetext必填depthhandle必填positionworkspace

add_rib

Add Rib

Add a reinforcing rib/web inside a PartDesign Body by thickening an OPEN sketch spine (line, arc, or connected polyline; NOT a closed loop) into a wall that fuses with the surrounding material. The sketch plane sets orientation. thickness: wall thickness mm (> 0). midplane: centre the wall on the spine (default) or grow one side. reversed: flip if the rib lands on the wrong side of its sketch plane. Returns {handle (starts 'rib_'), name, volume (the Body's mm^3 after — always greater), thickness}. Headless FreeCAD lacks PartDesign::Rib, so it is built as an equivalent midplane Pad of the offset spine; very intricate spines may differ. Raises ValueError on a closed/degenerate profile or thickness <= 0, RuntimeError if the rib adds nothing (spine does not span between walls).

輸入參數body必填namesketch必填midplanereversedthickness必填workspace

transform

Transform

Move and/or rotate an object in place (no new handle) — use instead of editing Placement via set_property. translate: [x, y, z] mm. rotate_axis: [x, y, z], any length (default Z); angle: DEGREES about it, around the object's local origin. relative: True composes onto the current placement; False sets it absolutely. Returns {handle, name, placement: {base [x,y,z] mm, axis, angle_deg}}.

輸入參數anglehandle必填relativetranslateworkspacerotate_axis

scale_shape

Scale Shape

Scale a shape uniformly or per-axis, baking a fresh static solid. Scaling breaks parametric history, so this produces a standalone Part::Feature (not a linked/parametric feature); the source object is hidden since its geometry is consumed into the scaled copy. handle: source shape handle. factor: scalar for uniform scale, or [sx, sy, sz] for per-axis scale. All factors must be > 0. center: optional [x, y, z] mm pivot to scale about; when omitted the scale is about the world origin (so the shape also moves away from/toward origin). name: object label (default 'Scaled'). Lengths in mm. Returns {handle, name, volume, factor} where `factor` is the normalized [sx, sy, sz] applied and `volume` (mm^3) equals the source volume times sx*sy*sz.

輸入參數namecenterfactor必填handle必填workspace

copy_shape

Copy Shape

Duplicate a shaped object as an INDEPENDENT static solid. Unlike add_part (which creates an App::Link that tracks the source), this deep-copies the geometry: later edits to the original do NOT propagate to the copy. Use it to seed a mirror/pattern, or to drop a standalone duplicate instance into an assembly. handle: handle of the source object (must have a Shape). placement: optional absolute [x, y, z] translation in mm applied to the copy's base. Omit to leave the copy coincident with the source. The source object is unchanged and stays visible. name: optional name for the new object (default '<SourceName>_copy'). Returns {handle, name, volume}: handle is a new 'copy_N' handle, name is the FreeCAD object name, volume is the copied solid's volume in mm^3.

輸入參數namehandle必填placementworkspace

measure_distance

Measure Distance

Minimum (closest-approach) distance between two entities, mm — verify gaps, clearances, and contact. 0 means touching or interpenetrating; for overlap volume use min_clearance / interference_check. a, b: object handles. a_ref, b_ref: optional sub-shape to measure from instead of the whole solid — an f_* / e_* tag or "FaceN"/"EdgeN" (1-based). Returns {distance_mm, point_on_a ([x,y,z] mm), point_on_b, touching (distance_mm < 1e-7)}. A measurement: no handle.

輸入參數a必填b必填a_refb_refworkspace

measure_angle

Measure Angle

Angle (degrees) between two planar faces or two straight edges. a, b: object handles. a_ref, b_ref: REQUIRED sub-shape references, one per handle. Both must be the SAME kind: - face tags ('f_*' from list_faces/query_faces, or 'FaceN', or 1-based int) -> angle is between the faces' outward normals. Faces must be planar. - edge tags ('e_*' from list_edges, or 'EdgeN', or 1-based int) -> angle is between the edges' tangent directions. Edges must be straight. Mixing a face ref with an edge ref, a non-planar face, or a curved edge raises. Units: degrees. Returns: - angle_deg: raw angle between the two direction vectors, 0..180. - supplement_deg: 180 - angle_deg (the complementary angle; use this for the acute reading when angle_deg is obtuse). - kind: "face" or "edge". Two adjacent box faces -> angle_deg 90. Two opposite parallel box faces -> angle_deg 180, supplement_deg 0. Read-only: measures, creates no geometry.

輸入參數a必填b必填a_ref必填b_ref必填workspace

bounding_box

Bounding Box

Axis-aligned bounding box (AABB) of a shaped object, mm, world coordinates. A measurement: returns numbers, creates nothing. QUIRK (#284): min/max/size are OCC's ANALYTIC box, an UPPER bound. A trimmed face is boxed by its untrimmed carrier surface, so cuts through fillets, chamfers, lofts or spheres can report mm of material that isn't there (a real trim at X=-32.0 read -36.7). Before calling a part wrong-sized, check `verified` and pass tight=True. oriented: also return the minimum-volume box at any orientation (OBB) under "oriented" (null if unsupported). Same analytic over-estimate. tight: also tessellate and return the mesh-derived box under "tight" — the trustworthy numbers. Opt-in: ~1.4s on a 200mm plate with 60 filleted holes. deflection: mesh chord tolerance mm for tight (default diagonal/2000, floor 0.001). Returns {min, max, size, center ([x,y,z] mm, analytic), diagonal (mm), oriented (null | {size, center, diagonal}), verified, tight (null | {min, max, size, center, diagonal, deflection, triangles}), warnings (which face over-estimates, by how many mm)}. verified: "exact" (vertices touch all six faces) | "mesh_agrees" (tight found no disagreement) | "unverified" (usual on curved parts: upper bound only, re-run tight=True) | "over_estimate" (use "tight"; min/max/size wrong-big). The true box lies between "tight" and the analytic box.

輸入參數tighthandle必填orientedworkspacedeflection

min_clearance

Min Clearance

Closest approach between two solids — the measured gap, richer than the binary interference_check. `a` and `b` are object handles. All lengths mm, volumes mm³. Returns a dict: status: "clear" (a positive gap separates them), "contact" (faces/edges touch, gap ~ 0), or "interference" (the solids interpenetrate / share material). clearance_mm: minimum distance between the two solids (mm). 0.0 when they are touching or interfering. overlap_volume_mm3: volume of interpenetration (mm³). Present ONLY when status == "interference". point_on_a: [x,y,z] of the closest point on `a`. Present when status is "clear" or "contact" (omitted for "interference"). point_on_b: [x,y,z] of the closest point on `b`. Present when status is "clear" or "contact" (omitted for "interference").

輸入參數a必填b必填workspace

check_shape

Check Shape

Check a shaped object's validity and topology before building on it — a guard after booleans/sweeps/imports. Inspection only; does NOT auto-repair. Watertight does not mean an internal channel is unobstructed: for ducts and manifolds use check_airtight_path. Returns {valid (OCC topology/geometry sound), watertight_solid (one solid AND valid AND closed — the 'safe to keep building' verdict), shape_type ('Solid', 'Shell', 'Compound', ...), closed (no free edges), solids (want 1), shells, faces, edges, volume_mm3 (0 for open/2D), is_null, check (only when invalid; diagnostics in the worker log), check_error (only if diagnostics raised)}.

輸入參數handle必填workspace

check_airtight_path

Check Airtight Path

Functional check for an enclosed-flow part (a vacuum adapter, manifold, duct): is there a single connected void joining the inlet to the outlet, bounded by solid everywhere else? This catches what `check_shape` cannot — a watertight solid can still have a blocked flow path or a hidden leak. Inspection only: measures, returns no handle, mutates nothing. handle: the part to inspect. inlet / outlet: a face reference naming each port OPENING (the rim face around the hole) — an f_* tag, 'FaceN', int index, or a role/name declared with annotate_face (e.g. "inlet"). Both ports are sealed with cap solids and the negative-space void is analysed. min_aperture_mm2: optional minimum acceptable bottleneck cross-section; a connected-but-pinched path (a near-zero 'almond slit') then fails. pad_mm: optional bounding-box margin (default max(2.0, 0.05*diagonal)). Returns a dict (lengths mm, areas mm², volumes mm³): ok (bool) connected AND not leaky AND aperture >= threshold status (str) 'airtight' | 'bottleneck' | 'blocked' | 'leaky' connected (bool) one void joins inlet and outlet leaky (bool) with both ports capped the cavity still reaches ambient, so an unintended opening exists min_aperture_mm2 (float|null) narrowest section of the flow void bottleneck_point ([x,y,z]|null) a point on the narrowest section plane flow_void_volume_mm3 (float|null) volume of the connecting void void_components (int) number of void solids (ambient + enclosed) inlet / outlet (str) the resolved 'FaceN' references pad_mm (float) the margin used

輸入參數inlet必填handle必填outlet必填pad_mmworkspacemin_aperture_mm2

classify_face_sides

Classify Face Sides

Inside-vs-outside topology: for every face, decide whether its outward side opens into an enclosed cavity (wetted) or ambient (exterior). Answers the "which faces are inside the airflow path" question from issue #19 and suggests a role per face. Inspection only; returns no handle, mutates nothing. With seal_ports=True (default) any declared inlet/outlet roles (annotate_face) are capped first, so an OPEN duct's bore reads as the enclosed flow cavity rather than as ambient. handle: the part. seal_ports: cap declared inlet/outlet before classifying. Returns a list (one per face) of dicts: tag / index (str) stable f_* tag and 'FaceN' kind (str) surface kind (planar/cylindrical/…) side (str) 'interior' | 'ambient' | 'ambiguous' suggested_role (str) 'wetted' for interior, 'ambient' for exterior, else null declared_role (str) the role already annotated on this face, if any

輸入參數handle必填workspaceseal_ports

section_view

Section View

Cut a solid with a plane and measure the exposed cross-section — the way to "see inside" a part blind. Does not modify the input. mm / mm^2. plane: "XY", "XZ", "YZ" (normals +Z, -Y, +X, as FreeCAD) or a datum-plane handle (its local +Z). offset: plane shift along its normal, mm (plane="XY", offset=10 cuts z=10). emit_profile: also add the section wires as a new registered object and return its handle (raises if the plane misses). Default: measure only. name: the emitted profile's name. Returns {plane, offset_mm, normal, section_area_mm2 (closed wires), wire_count (0 = plane misses), closed_wire_count, bbox ({min, max, size} or None), handle?, name? (only with emit_profile)}.

輸入參數nameplanehandle必填offsetworkspaceemit_profile

list_faces

List Faces

List all faces of a shaped object with stable tags + geometric descriptors. Returns [{tag, index, kind, area, centroid, normal?, axis?, radius?}, ...]. Tags survive geometry edits as long as the face's surface kind, area, centroid, and (where meaningful) normal/axis/radius do not change. Use the tag in subsequent calls instead of the FaceN index.

輸入參數handle必填workspace

list_edges

List Edges

List all edges of a shaped object with stable tags + descriptors. Returns [{tag, index, kind, length, centroid, axis?, radius?}, ...]. Same stability story as list_faces.

輸入參數handle必填workspace

query_faces

Query Faces

Filter faces by a structured predicate. Returns matching descriptors. Predicate fields (all optional, ANDed): - kind / type: 'planar' | 'cylindrical' | 'conical' | 'spherical' | 'toroidal' | 'spline' - normal_dir: [x, y, z] unit vector for planar faces (with normal_tol) - radius_eq: float, matches cylindrical/conical/spherical (with radius_tol) - area_min / area_max: bounds in mm^2 Optional ordering: - centroid_max / centroid_min: 'x' | 'y' | 'z' (sorts result) - order: 'area_desc' | 'area_asc' Example: {"type": "planar", "normal_dir": [0, 0, 1], "centroid_max": "z"} finds the topmost +Z-facing face.

輸入參數handle必填predicate必填workspace

resolve_face

Resolve Face

Resolve a face tag to the current FaceN index. Errors on miss or ambiguity. Use this when you need to pass a (object, 'FaceN') reference into a tool that doesn't accept tags directly (e.g. legacy FEM constraints).

輸入參數tag必填handle必填workspace

resolve_edge

Resolve Edge

Resolve an edge tag to the current EdgeN index. Errors on miss or ambiguity.

輸入參數tag必填handle必填workspace

make_body

Make Body

Create a PartDesign Body. Subsequent sketches/features go inside it. Returns {handle, name}.

輸入參數nameworkspace

make_datum_plane

Make Datum Plane

Create a Datum Plane in a Body. base: 'XY' | 'XZ' | 'YZ' for body origin planes. Pass a face_tag dict {handle, tag} for attachment to a face on another shape. offset: shift along the plane normal in mm.

輸入參數basebody必填nameoffsetworkspace

make_sketch

Make Sketch

Create a sketch in a Body, attached to a plane. plane: 'XY' | 'XZ' | 'YZ' for origin planes, or a datum-plane handle. Returns {handle, name}.

輸入參數body必填nameplaneworkspace

add_sketch_geometry

Add Sketch Geometry

Append geometric primitives to a sketch. items is a list of dicts: - {type: 'line', start: [x,y], end: [x,y], construction?: bool} - {type: 'circle', center: [x,y], radius: float} - {type: 'arc', center: [x,y], radius, start_angle, end_angle} (radians) - {type: 'point', pos: [x,y]} Returns {indices: [...]} — Sketcher-assigned indices for use in constraints.

輸入參數items必填sketch必填workspace

add_sketch_constraint

Add Sketch Constraint

Add a constraint to a sketch. type: Coincident, Horizontal, Vertical, Distance, DistanceX, DistanceY, Radius, Diameter, Equal, Parallel, Perpendicular, Tangent, Block, Symmetric, Angle. refs: list of [geom_idx, vertex_role] pairs. vertex_role: 0=edge, 1=start, 2=end, 3=center. value: numeric value (mm or radians) for dimensional constraints.

輸入參數refs必填type必填valuesketch必填workspace

close_sketch

Close Sketch

Recompute and report DOF status. Returns {geometry_count, constraint_count, open_vertices, fully_constrained}.

輸入參數sketch必填workspace

pad

Pad

Pad a sketch by `length` mm. symmetric=True extrudes both directions.

輸入參數namelengthsketch必填reversedsymmetricworkspace

pocket

Pocket

Subtract a pad of `length` mm from the body. through_all ignores length. through ('wall'|'body'): preferred over through_all. 'wall' ray-casts to the first exit and cuts exactly one wall — right for solids AND shelled bodies; 'body' (legacy ThroughAll) punches every wall of a shell. Implies direction='into_body'; result carries wall_depth_mm. direction ('into_body'|'away_from_body', preferred over the legacy raw `reversed`, honoured only when neither is set): the tool probes both Reversed values and picks the one that does / does not remove material. strict: raise instead of warn on a degenerate pocket. Returns {handle, name, volume, removed_volume, volume_ratio}, plus `warnings` ONLY when the pocket removed the whole body or nothing (strict=True makes those errors). direction='away_from_body' never warns.

輸入參數namelengthsketch必填strictthroughreverseddirectionworkspacethrough_all

revolve

Revolve

Revolve a sketch around a body origin axis ('X'|'Y'|'Z') by `angle` deg.

輸入參數axisnameanglesketch必填reversedworkspace

partdesign_fillet

PartDesign Fillet

PartDesign Fillet on edges of a feature in a Body. `edges` accepts e_* tags or 'EdgeN' index strings; `radius` in mm (> 0). Validated before a handle is issued, like fillet_edges (issue #283): Shape.isValid(), unchanged solid count, and no growth of the tight bounding box. PartDesign needs it as badly as the Part workbench — r=0.6 on a 40x40x1 pad returns an 'Up-to-date' single solid 15% LARGER than the pad. per_edge: add the edges one at a time, validating after each. allow_partial: accept a partial result instead of aborting. Off by default. Returns {handle, name, volume (mm^3), edges (the 'EdgeN' names actually filleted), checks {valid, solids, envelope_ok, envelope_growth_mm, envelope_tol_mm}, mode ('batch' | 'per_edge'), partial}; when partial is True, also skipped_edges and a warnings entry. On a failed check the feature is removed, the Body's Tip is restored, and BlendCheckFailed is raised naming the offending edges and the subset that does fillet cleanly.

輸入參數nameedges必填radiusfeature必填per_edgeworkspaceallow_partial

partdesign_chamfer

PartDesign Chamfer

PartDesign Chamfer on edges of a feature in a Body. `edges` accepts e_* tags or 'EdgeN' index strings; `size` is the chamfer leg in mm (> 0). Validated and rolled back on failure exactly like partdesign_fillet (issue #283), with the same per_edge / allow_partial opt-ins. Returns {handle, name, volume (mm^3), edges (the 'EdgeN' names actually chamfered), checks {valid, solids, envelope_ok, envelope_growth_mm, envelope_tol_mm}, mode, partial}; when partial is True, also skipped_edges and a warnings entry. On a failed check the feature is removed, the Body's Tip is restored, and BlendCheckFailed is raised naming the offending edges.

輸入參數namesizeedges必填feature必填per_edgeworkspaceallow_partial

hole

Hole

Drill a parametric Hole from a sketch (one or more circles) placed on a face of an existing body feature. Lengths mm. depth_type: 'Dimension' (use `depth`) or 'ThroughAll'. cut_type: 'None' | 'Counterbore' | 'Countersink' | 'Counterdrill'; when not 'None', cut_diameter (head clearance) and cut_depth apply. threaded=True taps it. thread_type / thread_size are COUPLED enums ('M4' fits 'ISOMetricProfile', not 'UNC') — discover them with list_thread_options(). intended_for ('print'|'machine'|'drawing'): sets the ModelThread default when threaded. print → modelled thread (a printed pilot won't self-tap); machine/drawing → cosmetic thread (CAM/drawings read the metadata; modelled threads bloat files and fight patterns/fillets). model_thread overrides it. through ('wall'|'body'): preferred over depth_type. 'wall' ray-casts to the first exit and drills exactly one wall — use it on shelled bodies, where 'body' (ThroughAll) destroys the cavity. Implies direction='into_body'; result carries wall_depth_mm. direction ('into_body'|'away_from_body', preferred over the legacy raw `reversed`, honoured only when neither is set; Hole and Pocket read it differently): picks the Reversed value that does / does not remove material. strict: raise instead of warn on a degenerate hole. Returns {handle, name, volume, removed_volume, volume_ratio}, plus `warnings` ONLY when the hole consumed the whole body or removed nothing (strict=True makes those errors). direction='away_from_body' never warns.

輸入參數namedepthsketch必填strictthroughcut_typediameterreversedthreadedcut_depthdirectionworkspace另有 6 個

list_thread_options

List Hole Thread Options

Discover the COUPLED ThreadType / ThreadSize enums on the hole tool. Call with no args to list valid thread_type values. Call with thread_type=... to list the valid thread_size values for that type (the coupling: thread_size='M4' is valid for 'ISOMetricProfile' but not for 'UNC'). Use this BEFORE calling hole(threaded=True, thread_type=..., thread_size=...) to avoid a failed enum-value call. Returns either {thread_types: [...]} or {thread_type, thread_sizes: [...]}.

輸入參數workspacethread_type

linear_pattern

Linear Pattern

Repeat a PartDesign feature linearly along a direction. direction: 'X'|'Y'|'Z' for body origin axes, or {handle, edge: tag|'EdgeN'} for an edge-aligned direction. length: total span (mm) covered by the pattern. occurrences: number of copies (>=2). Includes the original.

輸入參數namelengthfeature必填reverseddirectionworkspaceoccurrences

polar_pattern

Polar Pattern

Repeat a PartDesign feature around an axis. axis: 'X'|'Y'|'Z' for body origin axes, or {handle, edge: tag|'EdgeN'}. angle_deg: total swept angle (default 360 = full circle). occurrences: number of copies (>=2). Includes the original.

輸入參數axisnamefeature必填reversedangle_degworkspaceoccurrences

mirrored

Mirrored

Mirror a PartDesign feature across a plane. plane: 'XY'|'XZ'|'YZ' for body origin planes, a datum-plane handle string, or {handle, face: tag|'FaceN'} for a face-defined mirror plane.

輸入參數nameplanefeature必填workspace

loft

Loft

Loft (additively) between two or more sketches. sketches: ordered list of sketch handles. The first becomes the Profile, the rest become Sections. closed: True connects the last section back to the first (toroidal). ruled: True uses straight ruled surfaces between adjacent sections.

輸入參數nameruledclosedreversedsketches必填workspace

sweep

Sweep

Sweep a profile sketch along a spine sketch (additive pipe). profile: handle of the cross-section sketch. spine: handle of the path sketch (in the same body). mode: 'Standard' | 'Frenet' | 'Auxiliary' | 'Binormal'. transition: 'Transformed' | 'Right corner' | 'Round corner'.

輸入參數modenamespine必填profile必填workspacetransition

helix

Helix

Generate a Part::Helix curve. radius, pitch, height in mm. angle (deg) is the cone angle (0 = cylindrical helix, >0 = conical). Returns a handle to a 1D helical curve. To get a 3D helical solid (e.g. for threads), use the curve as the spine of a sweep.

輸入參數nameanglepitchheightradiusworkspace

thickness

Thickness

Hollow out a solid into a shell. base: handle of the body's tip feature (the solid to hollow). open_faces: list of {handle, face: tag|'FaceN'} that become the shell's openings. thickness: wall thickness (mm). reversed: True (default) grows the wall INWARD into the solid (the natural "hollow this part" interpretation). False grows outward. join: 'Arc' | 'Intersection'. mode: 'Skin' | 'Pipe' | 'RectoVerso'.

輸入參數base必填joinmodenamereversedthicknessworkspaceopen_faces必填

draft

Draft

Apply a draft angle to faces (for moldability). base: feature handle. faces: list of {handle, face: tag|'FaceN'}. angle_deg: draft angle (positive degrees). neutral_plane: {handle, face: tag|'FaceN'} for the plane along which the angle is measured (typically the parting plane). reversed: flip the direction of the draft.

輸入參數base必填namefaces必填reversedangle_degworkspaceneutral_plane

list_documents

List Documents

List all open documents: [{name, label, file_path, dirty, active, object_count}, ...].

輸入參數workspace

set_active_document

Set Active Document

Switch the active document by name (the value returned from new_document/open_document).

輸入參數name必填workspace

close_document

Close Document

Close a document by name (or 'active' for the currently active one). Frees its objects and invalidates any handles into the closed doc. Returns {closed, invalidated_handles}.

輸入參數nameworkspace

transaction_open

Transaction Open

Begin an undoable transaction on the active document. Pair with transaction_commit (keep the changes) or transaction_abort (roll back). Transactions nest — the most-recent open is committed/aborted first.

輸入參數labelworkspace

transaction_commit

Transaction Commit

Commit the most recent open transaction; changes are kept.

輸入參數workspace

transaction_abort

Transaction Abort

Roll back the most recent open transaction. Implementation note: FreeCAD 1.1 headless `abortTransaction` is unreliable, so the worker commits then undoes — net effect is a clean rollback.

輸入參數workspace

add_sketch_external

Add Sketch External

Project an external edge/face/vertex into a sketch as construction geometry. ref: {handle, edge: tag|'EdgeN'} (or face/vertex variant; or {handle, tag} where tag is e_/f_ prefixed). The projected element gets a negative geom index so subsequent constraints can reference it. Use this to make a sketch that stays anchored to upstream geometry (e.g. a hole 5mm from a tagged edge that survives pad-length edits).

輸入參數ref必填sketch必填workspace

get_object

Get Object

Dump a handle's properties + shape stats. Useful when no dedicated tool exposes what you need.

輸入參數handle必填workspace

set_property

Set Property

Set a single property by name on an object. Coerces lists → Vector for Vector properties; other values pass through.

輸入參數name必填value必填handle必填workspace

verify_feature

Verify Feature

Compare a feature's actual volume change against an expected signed delta. Run after each subtractive/additive operation to catch silent failures — Pocket on a curved surface that under-cut, Hole that drilled outside the body, Cut whose Tool didn't intersect the Base. handle: PartDesign feature (Pad/Pocket/Hole/Revolve/etc.) or Part::Cut. expected_delta_mm3: SIGNED expected change. Subtractive → negative, additive → positive. Wrong sign is its own useful error. tolerance: relative tolerance (default 0.05 = 5%). abs_tolerance: absolute mm³ fallback for tiny expected magnitudes (default 0.01). Pass if EITHER tolerance is satisfied. Returns {passed, message, actual_delta_mm3, expected_delta_mm3, ratio, previous_volume_mm3, current_volume_mm3, handle, name}. Does NOT raise on mismatch — inspect `passed` to decide whether to abort.

輸入參數handle必填toleranceworkspaceabs_toleranceexpected_delta_mm3必填

set_visibility

Set Visibility

Override an object's persistent Visibility flag. By default save_document auto-hides producer-inputs (the Base/Tool of a Cut, features inside a Body) so the re-opened doc shows just the final composition. Use this to override — e.g. to keep a reference primitive visible next to a derived part. Note that the next save_document with visibility_hygiene=True (the default) may re-hide it; pass visibility_hygiene=False to save_document to lock the override in.

輸入參數handle必填visible必填workspace

fillet_edges

Fillet Edges

Fillet edges of a shaped Part object. `edges`: e_* tags (preferred), 'EdgeN' strings, or 1-based ints; `radius` mm (> 0). Validated before a handle is issued (#283) — this OCC fillet can silently return corrupt geometry (a 20 mm cube, all 12 edges at r=11, comes back one LARGER solid). Checks: Shape.isValid(), unchanged solid count, no growth of the tight bounding box. per_edge: add edges one at a time, validating each (slower; for blend-hostile geometry). allow_partial: accept a partial result instead of aborting (off by default). Returns {handle, name, volume (mm^3), edges (indices filleted), checks {valid, solids, envelope_ok, envelope_growth_mm, envelope_tol_mm}, mode ('batch' | 'per_edge'), partial}; when partial, also skipped_edges and a warning that the solid is NOT the part asked for. On failure it retries per edge, removes the feature and raises BlendCheckFailed naming the offending edges and the subset that fillets cleanly — never a handle to corrupt geometry.

輸入參數edges必填handle必填radiusper_edgeworkspaceallow_partial

boolean_op

Boolean Op

Boolean operation on two objects by handle. op: 'cut' (base minus tool), 'fuse' (union), or 'common' (intersection). strict: raise instead of warning on a degenerate cut. Returns {handle, volume, removed_volume (base - result mm3; negative on a fuse), volume_ratio (result/base, None if base empty)}, plus `warnings` ONLY on a degenerate cut ("warnings" in result is the test): annihilation (result ~0 — later features operate on nothing) or miss (result == base — nothing removed). Warn-don't-fail by default; strict=True makes both errors.

輸入參數op必填base必填tool必填strictworkspace

export_shape

Export Shape

Export a shape to STEP/IGES/BREP/STL. Format inferred from path extension. object: FreeCAD object name (NOT a AnkusDrive handle). If omitted, exports the document's single final shape: inputs consumed by another feature (a cut's base/tool, Body features) are skipped, and solids win over sketches. If more than one final shape remains, it raises and lists them. It never picks one for you.

輸入參數path必填objectworkspace

run_script

Run Python Script

Escape hatch: execute Python in the worker with App/Part/ObjectsFem in scope. Set `__result__` in the script to return a JSON-serializable value. auto_register (default True): any new shape-bearing object the script creates is automatically registered into the handle table. The result includes a `registered` list of {handle, name, type} entries so the next tool call (render_view, list_faces, fillet_edges, mass_properties, etc.) can address script-created objects via handle without a separate register_handle round-trip. Disabled unless ANKUSDRIVE_ALLOW_RUN_SCRIPT permits it (the Claude Desktop extension defaults it off); when disabled this tool is not registered at all. Returns {result, registered}.

輸入參數code必填workspaceauto_register

register_handle

Register Handle

Register an existing FreeCAD object into the AnkusDrive handle table. Use after run_script (when auto_register=False) or after open_document to bring objects into the handle ecosystem so subsequent tool calls accept them via handle. object: the FreeCAD object's .Name (e.g. 'Helix001', 'Cut'). prefix: handle prefix (default 'manual'). Each call returns a fresh handle; registering the same object twice produces two aliases. Returns {handle, name, type, label}.

輸入參數object必填prefixworkspace

mass_properties

Mass Properties

Mass properties of a shaped object: volume (mm³), surface area (mm²), centroid, bounding box, inertia tensor. If density (kg/mm³) is given, also returns mass (kg). Steel = 7.9e-6, aluminum = 2.7e-6, ABS = 1.05e-6.

輸入參數handle必填densityworkspace

make_assembly

Make Assembly

Create an App::Part container to hold linked parts. Returns {handle, name}.

輸入參數nameworkspace

add_part

Add Part

Add a part to an assembly via App::Link. source is one of: {"handle": "<handle>"} — link an in-doc body {"path": "/path/to/part.FCStd"} — link first body / subassembly {"path": "/path/to/part.FCStd", "object": "X"} — link named object placement: [x, y, z] or {position: [...], axis: [...], angle_deg: ...}. mate: place by aligning this part's published interface frame to an already-placed parent's, instead of (or after) a raw placement: {"child_iface": "<name>", "parent": "<link-name|handle>", "parent_iface": "<name>"}. Frames come from publish_interface.

輸入參數matenamesource必填assembly必填placementworkspace

list_assembly_parts

List Assembly Parts

List parts of an assembly: name, type, linked-target name, position, volume.

輸入參數assembly必填workspace

interference_check

Interference Check

Pairwise interference: compute volume of intersection between every pair of parts. Returns [{a, b, interference_mm3}, ...] descending by volume. Empty list = no interference.

輸入參數assembly必填workspace

bom_extract

BOM Extract

Walk an assembly and return [{part, count, total_volume_mm3, total_mass_kg?}, ...] grouped by source (component file + object), NOT the bare object name, so two distinct components both named "Box" don't collapse into one row. density (kg/mm³) is optional. recursive (default True): descend into linked subassemblies (App::Part) so the BOM flattens to leaf parts. False counts each subassembly as one line. orderable (default False): opt in to the BUYABILITY view — is every purchased line on this BOM a part that actually exists off the shelf? The default return is unchanged — a bare list — because everything downstream consumes it. With orderable=True you instead get a dict: rows the same BOM rows, each also carrying designation / standard / part_class plus the catalog verdict (stocked, catalog_code, catalog) consumables purchased parts that are NOT modelled objects and would otherwise never reach a BOM — today the O-ring an oring_groove was cut for, counted across every part that calls for it undesignated purchased rows a buyer cannot order from (no designation) not_stocked rows naming a part nobody stocks, each with a reason and the nearest stocked alternatives designation the full designation_check verdict stocked_count how many purchased lines resolved to a stocked item ok False when anything purchased is undesignated OR not stocked check_stock=False designates without checking availability. A design built out of fasteners that do not exist is the failure this catches, and the offending rows stay IN the list rather than being quietly dropped. Availability is a curated snapshot of a market (`captured`, `market`), not physics.

輸入參數densityassembly必填orderablerecursiveworkspacecheck_stock

standard_part_designate

Standard Part Designate

The canonical, orderable designation of a purchased standard part — the string a buyer can actually quote against: "ISO 4762 M4×12 A2", "608-2RS", "AS568-214 NBR70". A BOM row that reads "SocketHeadCapScrew" is not a buyable line; this is what turns it into one. Three ways in: handle read the designation add_fastener / add_bearing / add_thread stamped on the part when it was built. An object with no stamp reports designation=None plus whether its NAME reads like a purchased part. Nothing is ever inferred from geometry, so a hand-modelled bracket cannot acquire a false designation. designation normalise/parse a string — 'iso4762 m4x12 a2' becomes 'ISO 4762 M4×12 A2', so two spellings of one part can never become two BOM lines. family+spec build one from facts. family is fastener | bearing | oring | threaded_rod, and spec is respectively {kind, size, length, grade} / {designation, seals} / {inner_diameter, cross_section, compound} / {diameter, pitch, length, grade}. Offline and deterministic — no network, no supplier, no credentials. Returns the designation card: {ok, family, standard, designation, complete, reason, purchased, ...family-specific fields}. complete=False means the string is not yet enough to order against (typically nobody said which material grade), with `reason` naming the gap — a missing fact is reported, never defaulted to a plausible-looking lie.

輸入參數specfamilyhandleworkspacedesignation

designation_check

Designation Check

Gate: every purchased part on this assembly must be orderable. Flags BOM rows a buyer cannot act on — a purchased standard part with no canonical designation, or one whose designation is missing a fact needed to order it (no material grade). Run it before quoting or releasing a package: a BOM whose purchased lines are geometry names silently pushes the sourcing work onto a human, once per revision. Purchased-ness is EXACT for AnkusDrive-generated parts (the object carries a stamp) and a documented name heuristic for everything else — so a hand-modelled "Bracket" is never flagged, while a hand-modelled "M6Screw" is. Pass `rows` instead of `assembly` to check BOM rows you already hold. Returns {ok, findings, purchased, designated, undesignated, incomplete, basis}; each finding carries part / count / code (no_designation | incomplete_designation) / certainty (stamped | name_heuristic) / reason. ok=False means the BOM cannot be ordered as it stands.

輸入參數rowsassemblyrecursiveworkspace

catalog_nearest

Catalog Nearest

Snap a desired standard part to the nearest one that actually exists. This is the call that changes how you design. Ask for an ISO 4762 M4×13 and it tells you that 12 and 16 are stocked and 13 is not — which turns "I need a 13 mm screw" into "I need to adjust my stack-up to 12 or 16". Use it the moment a fastener length falls out of a dimension chain, before the geometry hardens around a size nobody sells. standard: a product standard or alias ("ISO 4762", "DIN 912", "ISO 7380-1"). size: thread designation, nominal mm, or shaft/bore mm. length: the wanted length in mm. Omit it for a product with no length dimension (a nut, a washer, a circlip), or to list the whole stocked ladder. grade: optional property class / material, used to complete the designation the call hands back. Exact arithmetic on a DISCRETE ladder: nothing is interpolated, and nothing is silently rounded on your behalf. A length between two rungs is not a part, so you get both rungs and the signed deltas and you decide which way to move. Returns {ok, standard, name, size, requested_length, exact, stocked, below, above, nearest [{length, delta}], lengths, grades, designation, reason, fidelity, captured, market}. `designation` is the canonical designation of the RECOMMENDED part, so the answer is directly usable in a BOM. ok=True means what you asked for is already stocked.

輸入參數size必填gradelengthstandard必填workspace

catalog_check

Catalog Check

Is this exact part something you can buy off the shelf? Pass a canonical `designation` ("ISO 4762 M4×12 A2", "608-2RS") or the `handle` of a part whose designation was stamped when it was generated. Offline and deterministic. Returns {ok, code, standard, size, length, stocked, grade_ok, reason, nearest, lengths, grades, fidelity, captured, market}, where code is: stocked the size/length exists and the material is listed (for a cut-to-length product like threaded rod, any length up to the longest stock bar counts, with a note that it is a cut) not_stocked the size exists but the LENGTH is not a stocked rung — `nearest` names the rungs either side size_not_stocked the standard does not cover this size at all grade_not_listed the size exists, that material does not not_catalogued the product standard is outside this corpus's coverage. That is an absence of evidence, explicitly NOT a claim that the part is unavailable — check `not_covered` from catalog_search undesignated there was no designation to check ok is True only for `stocked`.

輸入參數handleworkspacedesignation

envelope_check

Envelope Check

Keep-out gate: assert each named part's world bounding box stays inside its declared envelope. envelopes maps a part's link name (or label) to {"min": [x,y,z], "max": [x,y,z]} in the assembly frame. Returns violations [{part, axis, got, allowed}, ...]; empty means everything is within its box.

輸入參數assembly必填envelopes必填workspace

publish_interface

Publish Interface

Record a named interface frame on a component so other parts can mate to it — the published "here is where you bolt to me, and how it's oriented". handle: the component's shaped object. name: interface name (e.g. "lid_seat", "bolt_circle", "bore_axis"). frame: {origin:[x,y,z], z_axis:[...]?, x_axis:[...]?}. z_axis defaults +Z, x_axis +X. Extra keys (e.g. bolt-circle metadata) are stored verbatim. Persists in the component's .FCStd as a JSON property bag, so merge_assembly can mate against it later. Returns {handle, name, frame, interfaces}.

輸入參數name必填frame必填handle必填workspace

annotate_face

Annotate Face

Declare the semantic ROLE of a face — what it is FOR — so later edits can be checked against intent instead of re-derived from raw geometry. The role binds to the face's stable f_* tag and persists in the .FCStd as a JSON property bag (same mechanism as publish_interface); it survives save/reopen. Once declared, check_airtight_path accepts the role/name directly (e.g. inlet="inlet"). handle: the part. face: an f_* tag, 'FaceN', or int index of the face to annotate. role: one of 'inlet' | 'outlet' | 'sealing' | 'wetted' | 'ambient' | 'mating'. name: optional unique label for this annotation (default: the role, then role_2, role_3, …); re-using a name updates that annotation. meta: optional dict stored verbatim (e.g. {"spec": "32mm hose"}). Returns a dict: {handle, name (the annotation key used), role, tag (the f_* the role is bound to), index ('FaceN' at annotation time), roles (sorted list of all annotation names now on the part)}.

輸入參數face必填metanamerole必填handle必填workspace

list_face_roles

List Face Roles

Read back the semantic face roles declared on a part (see annotate_face). Each entry re-resolves its stored tag against the CURRENT geometry, so a drifted or deleted face is reported rather than silently resolving wrong. Returns a list (sorted by name) of dicts: name (str) the annotation key role (str) inlet | outlet | sealing | wetted | ambient | mating tag (str) the f_* face tag the role is bound to present (bool) whether that tag still resolves on the current shape index (str) 'FaceN' on the current shape (only when present) meta (dict) the verbatim metadata (only when set)

輸入參數handle必填workspace

declare_intent

Declare Intent

Record the functional invariants a part must keep satisfying, so they can be re-checked after every edit (see verify_intent). Persists in the .FCStd as a JSON property bag (AD_Intent); one contract per part — re-declaring replaces. handle: the part. contract: a dict with any of these (declare at least one): watertight (bool) require check_shape's watertight_solid verdict. airtight_path (dict) {inlet, outlet, min_aperture_mm2?}; each port is a face tag / 'FaceN' / int / declared role-or-name. required_faces (list) face tags / 'FaceN' / declared role-or-names that must still resolve (catches a deleted/drifted face). Returns {handle, contract} — the stored contract.

輸入參數handle必填contract必填workspace

verify_intent

Verify Intent

Re-run every invariant declared with declare_intent — the regression gate to run after each edit. Composes check_shape / check_airtight_path / face-role resolution; never raises on a failing invariant (a failure is a passed=False row), so it is safe to call in a loop. Inspection only; mutates nothing. handle: the part (must have a declared intent contract). Returns a dict: handle (str) ok (bool) True iff every declared invariant passed results (list) one {invariant, passed, detail} per declared invariant — invariant in {watertight, airtight_path, required_faces}, detail a human-readable summary of what was measured

輸入參數handle必填workspace

verify_contract

Verify Contract

Build-time self-check of a component against its manifest slice (RFC §11.3): a builder calls this on its OWN part before save, so a contract violation is caught locally and cheaply instead of after a fan-in merge (build → merge → gate-fail → rebuild becomes build → self-check → fix). Never raises on a failing check (a failure is a passed=False row), so it is safe to call in a loop. Inspection only; mutates nothing. handle: the component's shaped object. contract: the component's slice — all keys optional, give at least one: envelope {min:[x,y,z], max:[x,y,z]} the part's LOCAL bbox must fit in it. interfaces {name: {origin:[x,y,z], z_axis?:[x,y,z], tol_mm?, angle_tol_deg?}} each named frame must be PUBLISHED (publish_interface) and within tolerance of the contracted origin (and axis, if z_axis given) — catches "forgot to publish" / "published in the wrong place". features [ {kind, ...} ] per-feature self-checks: {kind:"gear", module_mm, teeth, internal?, tol_mm?} {kind:"bore", diameter_mm, tol_mm?} {kind:"extent", axis:"x"|"y"|"z", length_mm, tol_mm?} intent bool also run verify_intent. Returns {handle, ok, results:[{check, passed, detail}]} — ok True iff every check passed; check names are envelope / interface:<name> / feature:<name> / intent.

輸入參數handle必填contract必填workspace

component_contract_check

Component Contract Check

Builder-side contract gate for one component (issue #169) — the local half of the gate merge_assembly re-runs at fan-in. Any MCP host builds a component with the full AnkusDrive tool surface, then calls this on its part BEFORE saving, against its `builder brief` (a ankusdrive.builder_brief/1 slice), and repairs any failing check. Catching a violation here turns the expensive loop (build → merge → gate-fail → rebuild) into a cheap local one. Never raises on a failing check. handle: the component's shaped object. brief: a builder brief. Only three of its keys drive checks (the rest guide the build, not the gate): envelope {min:[x,y,z], max:[x,y,z]} the part's LOCAL bbox must fit inside. interfaces {name: {origin?:[x,y,z], z_axis?:[x,y,z], tol_mm?, angle_tol_deg?}} each named frame must be PUBLISHED (publish_interface) with a sane frame, and within tolerance of a pinned origin/axis if the brief gives one. performance {requirements?: [{name, limit}], required?: bool} the quantitative spec (#226) the builder must DECLARE (declare_performance, no looser than briefed) and PROVE (verify_performance) before fan-in. Checks run: watertight (check_shape's one-clean-solid verdict), envelope (local bbox inside the keep-out box), interface:<name> (published + sane + in tol), performance_spec:<name> (declared as briefed) and performance:<name> (the last RECORDED verify_performance verdict says it is met). A performance requirement the record says is NOT met fails the gate. One with no verdict yet — never verified, a solve still in flight, or a verdict invalidated by a later edit — is neither passed nor failed: it comes back in `skipped` with a reason, because "unverified" is not "fine" and must not be actioned as either. Returns {handle, ok, checks:[{check, passed, detail}], reasons:[...], s

輸入參數brief必填handle必填workspace

interface_align_check

Interface Align Check

Gate: verify declared interface pairs coincide in world space — the "do the OTHER interfaces line up?" check for multi-interface mates. After the primary mate seats a part, this confirms its secondary interfaces (a second bolt pattern, a bore axis) actually meet the parent's. pairs: [{child, child_iface, parent, parent_iface}, ...] (child/parent are link names in the assembly). Returns misaligned pairs [{..., gap_mm}], empty if every pair coincides within tol_mm.

輸入參數pairs必填tol_mmassembly必填workspace

validate_manifest

Validate Manifest

Validate a manifest WITHOUT building it (the cheap front door, RFC §11.7). Structural + cross-reference checks a JSON shape can't enforce: every component has exactly one of file/manifest/library; a library carries a tool; every instance references a known component; every mate/check references a known instance; a present `schema` is the known version ("ankusdrive.manifest/1"). manifest: path to the manifest JSON. Returns {ok, problems, schema, manifest_hash} — ok is True iff problems is empty; manifest_hash fingerprints the contract content (what the lockfile records so a stale contract is detectable). Run this before merge_assembly to reject a malformed contract before any geometry is built.

輸入參數manifest必填workspace

merge_assembly

Merge Assembly

Construct-up an assembly from a manifest JSON (the coordinator's one call): create the doc, link each component by file path, place it, recompute, and run the gates. Component files resolve relative to the manifest's directory; links auto-reload, so re-running picks up updated components (deterministic, idempotent). manifest shape: { "name": "gearbox", "root": "gearbox.FCStd", "components": {"<id>": {"file": "rel/part.FCStd", "object": "<name>"?, "envelope": {"min":[...],"max":[...]}?}}, "instances": [{"component":"<id>", "name":"<instance>"?, "placement": [x,y,z] | {position,axis,angle_deg}, "mate": {"child_iface","parent","parent_iface", "verify_align":{"child_iface","parent_iface"}?}?}], "mates": [{"child","parent","child_iface","parent_iface", "verify_align":{...}?}]? } Placement positions anchors; mate-by-frame positions everything else by aligning published interface frames (see publish_interface). Any component carrying a PERFORMANCE contract (declare_performance, #226) is gated on it too, with no manifest opt-in: the merge consults the verdict verify_performance last RECORDED on that part and never measures, so it stays synchronous and deterministic. A requirement measured as NOT met fails the merge; a requirement with no verdict yet is neither passed nor failed and rides in report["performance"]["skipped"] — "unverified" is never read as "fine". Returns {assembly, doc, root, placed, gates:{interference, bom, envelope, interface_align?, typed?, requirements?, mobility?, performance?}, ok, requirements?, mobility?, performance?, children?, library?}. The `performance` gate and report block are absent entirely when no component declares a contract.

輸入參數manifest必填workspace

assembly_lock

Assembly Lock

Write a lockfile recording each component's content hash, published- interface hash, and mate dependencies — the provenance baseline a coordinator uses to detect drift across a team. Call after a clean merge. lockfile defaults to <manifest>.lock.json. Returns {lockfile, components}.

輸入參數lockfilemanifest必填workspace

assembly_lock_check

Assembly Lock Check

Compare current component files to a lockfile and classify drift (change propagation, RFC §9): modified — file changed since lock interface_changed — published interface frames moved (subset of modified) stale — mates to an interface_changed component and was NOT itself rebuilt: a neighbor that needs re-dispatch new / removed — components added to / dropped from the manifest ok = nothing stale and no new/removed (safe to re-merge without re-dispatch). Returns {modified, interface_changed, stale, new, removed, ok}.

輸入參數lockfilemanifest必填workspace

make_drawing_page

Make Drawing Page

Create a TechDraw page using a built-in A4 landscape template by default. template: optional absolute path to a .svg template.

輸入參數nametemplateworkspace

add_projection_group

Add Projection Group

Add a multi-view projection group of `body` to a drawing page. views: list of FreeCAD view codes ('Front', 'Top', 'Right', 'Left', 'Bottom', 'Rear', 'FrontTopLeft', etc.). Default: ['Front', 'Top', 'Right'].

輸入參數body必填namepage必填viewsworkspace

export_drawing

Export Drawing

Export a drawing page to PDF, SVG, or DXF (format inferred from the path extension), headless. PDF/SVG are composed from the template, the per-view geometry, and any dimensions/annotations on the page; DXF uses FreeCAD's native page writer. Returns {path, size, format, views, dimensions}.

輸入參數page必填path必填workspace

add_dimension

Add Dimension

Add dimension(s) to a drawing page. Modes (pick one): * auto=True: overall horizontal + vertical extents for every part-view (or only `views`, by name or projection code). * view + edge=<edge tag>: true length of a model edge (the real length, not the foreshortened projection). * view + kind='diameter'|'radius' + edge=<circular edge tag>: ⌀/R of a hole or arc. * view + kind='angle' + face=<conical face tag>: included angle (2×) of a cone/countersink/taper; half_angle=True for the half-angle. * view + from_point/to_point ([x,y,z] model points): point to point. view: a view handle, object name, or projection code ('Front', 'Top', ...). kind: 'aligned' (default) | 'horizontal' | 'vertical' | 'diameter' | 'radius' | 'angle'. tolerance: rendered next to the value: {"sym": 0.1} for ±0.1, {"plus": .., "minus": ..}, or {"fit": "H7"} / {"fit": "H7/g6"} (ISO 286 hole-side limits at the basic size). Returns {dimensions: [{handle, name, type, value}, ...]}; value is the true measured size.

輸入參數autoedgefacekindpage必填viewviewsto_pointtoleranceworkspacefrom_pointhalf_angle

add_annotation

Add Annotation

Add a free text annotation to a drawing page at page position (x, y) in mm (origin bottom-left, +Y up, matching TechDraw view placement). Returns {handle, name, text}.

輸入參數xynamepage必填text必填workspace

add_feature_note

Add Feature Note

Attach an explicit manufacturing NOTE that satisfies a curved/periodic feature the drawing_gate enumerates (issue #108) — the 'per CAD model / profile table' coverage for geometry a single number can't capture (a freeform/BSpline wall's profile, a tooth pattern's full parameter set) or a documented cone angle. `feature` is the enumerated feature id (e.g. 'FREEFORM1', 'PAT1', 'CONE1', from drawing_gate's `enumerated_features`); `text` defaults to a sensible callout. The gate reads the note back as coverage. Returns {handle, name, feature, text}.

輸入參數xykindnamepage必填textfeature必填workspace

set_title_block

Set Title Block

Populate the drawing's title block (composed bottom-right on SVG/PDF export; calling this opts the page into rendering it). Scale, sheet size, units and part name are auto-derived; these fields override or add to them. Returns {handle, name, fields}.

輸入參數revdatepage必填partunitsprojectdrawn_bymaterialworkspace

drawing_gate

Drawing Gate

Manufacturing-completeness gate for a drawing page: do the placed dimensions fully and non-redundantly reconstruct the part? Reads the real solid + placed dimensions and accounts degrees of freedom, process-aware: 'prismatic' needs each hole located X/Y from a datum and the block sized W×H×T; 'turned' needs only Ø + axial length per step. process='auto' infers it. Datum discipline (a location not measured from a datum face → no_datum) turns on when faces carry role='datum' (annotate_face), or with datums_declared=True. require_ballooned=True also fails unballooned characteristics (not_ballooned; see balloon_drawing), as a release flow does. Returns {ok, violations, slots_total, slots_covered, process, features, dimensions, enumerated_features, datum_faces, section_recommended ({recommended, reasons, feature_ids}; see add_section_view), ballooned ({ok, total, ballooned, missing})}. Each violation has a `code` (under = size/location missing; redundant = DOF dimensioned twice; conflict = twice with disagreeing values; extra = pins nothing; no_datum; not_ballooned) and a `reason`. ok=True means manufacturing-complete.

輸入參數page必填processworkspacedatums_declaredrequire_ballooned

fit_page

Fit Drawing to Page

Auto-fit a drawing to its sheet: recentre the views so the part AND its placed dimensions sit inside the printable border (clear of the title block). The projection group's Automatic scale already sizes the part; its dimensions extend a fixed margin beyond it which can run off an edge — call this after placing dimensions to slide everything inside. `margin` mm is the border inset. Returns {scale, fits, envelope, border}; fits=False means the part + dims are too large even when centred (use a larger sheet).

輸入參數page必填marginworkspace

drawing_legibility

Drawing Legibility

Legibility gate for a drawing page: on the ACTUAL placed graphics, flag the ways the layout becomes unreadable — overlapping dimension labels, a dimension line crossing a view it does not reference, or anything past the sheet border. min_gap (mm) is the breathing room required between two labels. Returns {ok, violations, labels, segments, views}; each violation has a `code` (overlap/crosses_view/out_of_border) and a human `reason`. ok=True means the placed dimensions read cleanly on the sheet. Inspection balloons count as placed graphics too — a numbered circle sitting on a neighbour or off the sheet is flagged like any other label.

輸入參數page必填min_gapworkspace

add_gdt_callout

Add GD&T Callout

Place a GD&T feature control frame on a drawing page, so the tolerance is declared on the print: it renders as a compartmented symbol and inspection_plan / fai_report read it back as a ballooned characteristic. control: ASME Y14.5 characteristic, as gdt_check accepts: flatness, straightness, circularity, cylindricity, profile_line, profile_surface, perpendicularity, parallelism, angularity, position, concentricity, runout, total_runout. zone: tolerance zone mm (Ø-prefixed for position, concentricity, circularity, cylindricity). datums: ordered datum reference frame, e.g. ["A", "B", "C"]. With datums the plan routes it to CMM; datum-free form controls go to the surface plate. feature: optional enumerated feature id (drawing_gate `enumerated_features`). mmc_bonus: material-condition bonus tolerance, mm. modifier: free text in the tolerance compartment (e.g. "Ⓜ"). x / y: page position mm (origin bottom-left, +Y up). Returns {handle, name, control, zone, datums, text}.

輸入參數xynamepage必填viewzone必填datumscontrol必填featuremodifiermmc_bonusworkspace

balloon_drawing

Balloon Drawing

Number every characteristic on a drawing page (dimensions, feature control frames, feature notes) with an inspection balloon, rendered on SVG/PDF export; inspection_plan and fai_report key their rows on it. Numbers are a persistent IDENTITY: re-running reassigns nothing, a new dimension gets the next number, a deleted one RETIRES its number (never reused). renumber=True restarts from 1 and invalidates existing inspection records. Call after dimensions are placed and fit_page has run. Returns {count, balloons (source object name -> number), assigned, kept, retired, next_balloon}.

輸入參數page必填renumberworkspace

inspection_plan

Inspection Plan

The ballooned characteristic list for a drawing page as data (every dimension, feature control frame, feature note), with nominal, limits, and a suggested measurement method per row. The method follows the tolerance: the `ratio`:1 gauge rule (default 10:1) walked down a per-family instrument ladder (bores use pin/bore gauges; datum-referenced GD&T → CMM; datum-free form → surface plate). Resolution is exact; instrument mapping is shop convention, hence fidelity='correlation'. Returns {ok, characteristics, count, by_method, unmeasurable, retired, next_balloon, fidelity, band_pct, basis}. ok=False means something cannot be inspected as drawn: no_tolerance (untoleranced size) or no_instrument (band finer than any instrument), named in `unmeasurable`.

輸入參數page必填ratioworkspace

fai_report

FAI Report

First-article inspection report for a drawing page, shaped like AS9102 Rev B Form 3: one row per ballooned characteristic with the Form 3 fields, limits, suggested method, and computed status. results: balloon number -> measured value; rows are accepted/rejected against limits. For `position` pass {"x":.., "y":..} (deviation 2·√(x²+y²), as gdt_check). Omit for a BLANK form: every row 'not_evaluated', never a pass. path: optionally write .csv (data), .svg or .pdf (printable table). part / rev: identity stamped in; default page part name / title-block rev. reference: AS9102 field 6 (Reference Location); defaults to each characteristic's view. NOT a certified AS9102 submission (every artifact says so). Returns {ok, columns, rows, summary, disclaimer, part, rev, plan_ok, unmeasurable, path?, size?, format?}; ok=False means a characteristic measured out of limits.

輸入參數revpage必填partpathratioresultsreferenceworkspace

add_thumbnail

Add Thumbnail

Place a small isometric pictorial (vector TechDraw view) in the sheet's top-right corner IF it fits without crowding existing views/dimensions. It is pinned there, never dimensioned, and ignored by drawing_gate. Call it AFTER views, dimensions and fit_page. Returns {placed, box, scale?, view?, reason?}; placed=False with a reason when the corner is occupied.

輸入參數namepage必填workspace

add_section_view

Add Section View

Add a cross-section view when the part has internal features the outline / hidden-line views convey ambiguously (counterbore, blind hole/bore, pocket). The need is judged from the real solid; the cut runs lengthwise through such a feature and the view is placed in clear space. auto (default True): add it ONLY if hidden internal geometry exists, else return {added: False, recommended: False}; auto=False forces a section. process: 'auto' | 'prismatic' | 'turned' — how features are enumerated. Returns {added, recommended, reasons, feature_ids, view?, normal?, origin?}. drawing_gate's `section_recommended` tells you in advance.

輸入參數autonamepage必填symbolprocessworkspace

render_view

Render View

Render an isometric/orthographic view of a shaped object as a PNG. view: 'iso' | 'top' | 'bottom' | 'front' | 'back' | 'left' | 'right' | 'side'. deflection: tessellation accuracy in mm (smaller = finer mesh, slower). edges: draw triangle edges over filled faces. Returns {png_base64, width, height, view, vertices, triangles}.

輸入參數viewedgeswidthhandle必填heightworkspacedeflection

render_views

Render Views

Render multiple views of a single object. Returns {views: {view_name: {png_base64,...}}}. Default views: ['iso', 'top', 'front'].

輸入參數viewswidthhandle必填heightworkspacedeflection

render_fem_results

Render FEM Results

Render a completed FEM result's surface, colored by a per-vertex field. Pulls the result surface (boundary triangulation + per-node field values + displacement vectors) from the worker, then colors it with a viridis colormap (barycentrically interpolated), overlays the deformed shape, and draws a colorbar with the field min/max — the "agent eyes" for a stress / displacement / thermal solve. field: 'vonmises' (default) | 'displacement' | 'temperature'. view: a preset ('iso'|'top'|'front'|…) or a custom '(azimuth,elevation)' camera passed as e.g. "45,35". deformation_scale: 'auto' scales the peak displacement to ~8% of the model diagonal; a number is used verbatim; '0' disables the deformed overlay. Returns {png_base64, width, height, field, units, min, max, view, node_count, triangle_count}.

輸入參數viewedgesfieldwidthheightanalysis必填workspacedeformation_scale

render_photoreal

Render Photoreal

Photorealistic "hero image" of a part or a whole assembly — presentation quality, unlike render_view's fast software-rasterized preview. Call render_capabilities first when unsure what is installed. renderer='auto' (default) uses Blender (Cycles, full install, run headless) when it resolves, otherwise POV-Ray (or another FreeCAD Render add-on renderer). Name one to force it: 'Blender' | 'Povray' | 'Luxcore' | 'Appleseed' | 'Cycles' (the stripped standalone build, not Blender) | 'Ospray' | 'Pbrt'. Renders never modify the live model. What to render: `handle` (a part, or an assembly handle — every leaf part is rendered in place) or `parts`, a list of {handle, appearance?, name?} entries. Appearance per part: `appearances` maps an assembly's part names (as list_assembly_parts reports them) to an appearance; `parts[].appearance` sets it inline; `material` is the fallback for every unassigned part. An appearance is a card name ('Aluminium', 'Brass', 'Carpaint', 'Emission', 'Glass', 'GlossyPlastic', 'Gold', 'Iron', 'Matte', 'RoughPlastic', ...) or a neutral PBR dict {base?, color ('#rrggbb' or linear [r,g,b]), metallic, roughness, emission, emission_strength, transmission, ior, coat, finish ('none'|'fdm_layers'|'brushed'), layer_height_mm}. Unknown cards/fields raise listing the valid ones. Blender-only (reported under `ignored` on an add-on renderer): per-part appearance, scene ('studio': seamless cyclorama, soft key/fill/rim area lights, contact shadows), quality ('draft' 16 | 'preview' 64 (default) | 'final' 384 samples, OIDN-denoised), device ('auto' GPU-if-present | 'cpu' | 'gpu'), and output_dir (where render.png + render.blend are written; default a temp dir). view: 'iso' | 'top' | 'bottom' | 'front' | 'back' | 'left' | 'right' | 'side'. Returns {png_base64, png_path, renderer, auto_selected, view, material, width, height}. The Blender backend adds {blend_path, s

輸入參數viewpartsscenewidthdevicehandleheightqualitymaterialrendererworkspaceoutput_dir另有 1 個

render_photoreal_submit

Render Photoreal Submit

Start a photorealistic render asynchronously; returns immediately with {job_id, status, renderer} instead of blocking for the whole render. Use this (rather than render_photoreal) for renders that may take a long time — quality='final', large images, big assemblies — so the worker stays responsive. Poll render_job(job_id) until status is 'done' (then it returns the PNG and, for Blender, blend_path) or 'failed'. Same arguments, renderer selection and not-installed miss dict as render_photoreal; see render_capabilities for what resolves on this machine.

輸入參數viewpartsscenewidthdevicehandleheightqualitymaterialrendererworkspaceoutput_dir另有 1 個

render_job

Render Job

Poll an async render started by render_photoreal_submit. Returns {job_id, status} where status is 'running', 'done', or 'failed'. When 'done', also returns {png_base64, png_path, renderer, view, material, width, height}; when 'failed', {error}. The result remains available for repeat polls. Pass discard=True once you have a terminal result to free the job immediately (drops the cached image and closes its temp document); ignored while running. Jobs are also auto-evicted oldest-first once finished jobs exceed an internal cap.

輸入參數job_id必填discardworkspace

render_capabilities

Render Capabilities

Report which photoreal renderers are usable right now — Blender (the studio backend) and the FreeCAD Render add-on renderers — so you can pick one for render_photoreal, or tell the user exactly what to install, instead of discovering availability by trial and error. Takes no arguments and renders nothing. Resolves Blender like a solver (ANKUSDRIVE_BLENDER_PATH -> PATH -> per-OS install dirs, incl. the versioned `Blender Foundation\Blender X.Y` and /opt|~/.local/opt/blender-*) and launches it once (cached) to read its version and GPU; resolves each add-on renderer as render_photoreal would, changing no settings. Returns {default_renderer ('auto'), auto_selects (the renderer 'auto' would use now, or None), recommended ('Blender'), platform, available (ready renderer names for `renderer=`), renderers: {name: {available, backend ('blender' | 'render_addon'), path or install_hint, ...}} — Blender adds version, device, gpu, oidn, supported, scenes, qualities, devices, finishes — materials (appearance card names), appearance_fields (the PBR dict keys), addon_importable, addon_error (when the add-on does not import), and `suggestion` {renderer, why, install} while Blender is missing}. When the user wants better renders, relay the Blender `install_hint` verbatim — never run installers yourself. setup_status reports the same install under the `studio_render` family.

輸入參數workspace

solve_capabilities

Solve Capabilities

Report which P2 external solvers (CFD/MBD/topology/transient-thermal/optics) are usable right now — so you can pick a working solver for a *_submit family instead of discovering availability by trial and error. The solver twin of render_capabilities. Takes no arguments. Resolves each solver side-effect-free: a binary by ANKUSDRIVE_<SOLVER>_PATH env -> PATH -> per-OS install dirs; a pip-wheel solver by importability. It executes nothing and installs nothing. `families[*].any_available` is the gate to trust: it means AnkusDrive can actually DRIVE that family here, not just that a binary resolved. A solver that resolves but that no AnkusDrive tool can build a case for is listed under the family's `prepared_case_only` (with the reason on the solver entry) and does NOT set `any_available` — today that is SU2, which only ever runs a `case_dir` you prepared yourself (*.cfg + *.su2); every built-in CFD case mode is OpenFOAM-only. Returns {platform, available (sorted ready solver names), unwired, prepared_case_only, solvers: {name: {available, kind ('binary'|'wheel'), family, extra, and either path/module (when available) or install_hint, plus prepared_case_only when nothing can build it a case}}, families: {family: {solvers, available, unwired, prepared_case_only, any_available}}, extras: {extra: [solver names]} for `pip install ankusdrive[<extra>]`, toolsets: {enabled, disabled: {family: {label, tools, enable}}}, cases: {root, count, bytes, bytes_exact, keep, max_gb, grace_s, reaping}}. A family listed under `toolsets.disabled` has no tools registered in this session — tell the user its `enable` instruction rather than concluding the capability does not exist. `cases` is where every generated solver deck is written (one managed root) and the retention it is held to: at most `keep` directories and `max_gb` GB, reaped oldest-first, never touching one younger than `grace_s`

輸入參數workspace

setup_status

Setup Status

The machine-readable form of `ankusdrive doctor` — resolve FreeCAD and every solver family and report, per item, found/missing with the exact fix. Call this when the user asks to set up, diagnose, or finish installing AnkusDrive, then walk them through the per-item `fix`/`install_hint` commands for their `platform`. Read-only and side-effect-free: nothing is executed or installed and no environment is mutated (`verify_freecad_boot=True` additionally boots FreeCAD once, time-boxed, purely to read back its version — leave it False unless the user doubts the install actually runs). `verify_image=True` additionally checks, OVER THE NETWORK, that the solver container's image was signed by this repository — use it when the user asks whether the solvers they are running are authentic. It adds `container_image.verification` = {status, reason, repo, workflow, allowed_by_config, self_declared}, where status is: • `verified` — provenance names this repository's image workflow; • `unsigned` — no attestation: an image the user built themselves (common and legitimate), one published before signing existed, or no network/`gh`. Relay it as expected-for-a-custom-image, and mention ANKUSDRIVE_ALLOW_UNVERIFIED_IMAGE=1 silences it; • `mismatch` — an attestation exists but names a DIFFERENT repository. Say so plainly: that is an image claiming to be ours. No setting silences it; • `unavailable` — the check could not run (`reason` says why). Never report this as authentic. `self_declared` is what the image says about itself and is never evidence — only the signature is. Returns {platform: {system, machine}, freecad: {available, path, source, version?, fix?}, install: {kind, source} (venv | pipx | uv_tool | uvx | mcpb — the install every fix string is written for), toolsets: {enabled, disabled: {family: {label, tools, enable}}} (tool families swi

輸入參數workspaceverify_imageverify_freecad_boot

fem_new_analysis

FEM New Analysis

Create a Fem::FemAnalysis container. Returns {handle, name}.

輸入參數nameworkspace

fem_set_solver

FEM Set Solver

Add a solver to an analysis. kind: 'ccx' (CalculiX) or 'elmer'. tunables: dict of solver-property values, e.g. {"GeometricalNonlinearity": "linear", "ThermoMechSteadyState": True, "MatrixSolverType": "default"}. Sensible CCX defaults are filled in if omitted.

輸入參數kindnameanalysis必填tunablesworkspace

fem_set_material

FEM Set Material

Bind a material to a body in an analysis. material is a dict with at minimum: {"YoungsModulus": "210000 MPa", "PoissonRatio": "0.30", "Density": "7900 kg/m^3", "Name": "Steel-Generic"} Any extra keys are passed through to the FEM material card.

輸入參數body必填nameanalysis必填material必填workspace

fem_add_constraint

FEM Add Constraint

Add a constraint by face/edge tag (Slice 1). kind: structural: 'fixed' | 'force' | 'pressure' | 'displacement' thermal: 'temperature' | 'heatflux' | 'initial_temperature' refs: list of {handle, tag} dicts; 'tag' may be a face tag (f_...) or edge tag (e_...). Resolved against the live shape so refs survive unrelated geometry edits. For 'force': force (N), optional direction {handle, edge|tag}. For 'pressure': pressure (MPa). For 'displacement': x/y/z (mm) or x_free/y_free/z_free. For 'temperature' / 'initial_temperature': temperature (°C / K). For 'heatflux': flux_type ('DFlux'|'Convection'|'Radiation'), and DFlux: flux (W/m²); Convection: ambient_temp (°C) + film_coef (W/m²K); Radiation: ambient_temp + emissivity.

輸入參數xyzfluxkind必填namerefs必填forcex_freey_freez_freeanalysis必填另有 9 個

fem_modal

FEM Modal

Configure analysis for modal (frequency) extraction. Sets solver AnalysisType='frequency' and EigenmodesCount=n_modes. f_low / f_high (Hz) optionally bound the requested mode range. Caller still calls fem_run, then fem_modal_results to read frequencies.

輸入參數f_lowf_highn_modesanalysis必填workspace

fem_modal_results

FEM Modal Results

Extract natural frequencies from a completed modal run. Pass `analysis`, or the `job_id` of a finished fem_run_submit solve. Returns {frequencies_hz: [...], modes: [{mode, frequency_hz, max_displacement_mm}, ...]}.

輸入參數job_idanalysisworkspace

contact_setup

Contact Setup

Set up surface-to-surface contact between face pairs for a CalculiX solve and flip the solver to nonlinear — no new solver (promotes the CCX contact/nonlinear flags the FEM path already exposes). `face_pairs` is a list of {a:{handle, tag|face}, b:{handle, tag|face}} (master, slave) pairs; `friction` is the Coulomb coefficient (0 = frictionless); `slope` optionally sets the penalty contact stiffness; `nonlinear` (default True) sets the solver's GeometricalNonlinearity. Run fem_run + fem_results after. Gate RELATIVE to a bonded reference on the same mesh: a bonded model is stiffer (less peak displacement) than frictional contact. Returns {contacts:[handles], n_pairs, friction, nonlinear}.

輸入參數nameslopeanalysis必填frictionnonlinearworkspaceface_pairs必填

fem_set_nonlinear_material

FEM Set Nonlinear Material

Attach an elastoplastic (`*PLASTIC`) hardening curve to a linear FEM material and switch the CalculiX solve to nonlinear — the material-nonlinearity half of the nonlinear FEM path (`contact_setup` is the geometric/contact half). No new solver: this promotes the CCX `MaterialNonlinearity` / `GeometricalNonlinearity` flags the FEM path already exposes. `base_material` is the handle from `fem_set_material` (its YoungsModulus/PoissonRatio stay the elastic branch). Give the post-yield curve either as `yield_points` ([[stress_MPa, plastic_strain], ...], first point at plastic_strain 0 = initial yield) or from `yield_mpa` (+ optional `tangent_modulus_mpa` linear-hardening slope and `max_plastic_strain`). With no tangent modulus the curve is elastic–perfectly-plastic and caps the stress at σ_y exactly. `hardening`: 'isotropic' (monotonic) or 'kinematic' (cyclic/Bauschinger). Set `geometric_nonlinearity=true` to combine plasticity with large deflection (*NLGEOM). `ramp_increments` sub-divides the load step so ccx's plastic return-mapping converges. Run `fem_run` + `fem_results` after; gate against `plastic_collapse` (perfectly-plastic stress saturates at σ_y, collapse at M_p). Returns {handle, name, hardening, yield_points, n_points, solver_material_nonlinear, solver_geometric_nonlinear, ramp_increments}.

輸入參數nameanalysis必填hardeningworkspaceyield_mpayield_pointsbase_material必填ramp_incrementsmax_plastic_straintangent_modulus_mpageometric_nonlinearity

fem_buckling

FEM Buckling

Configure analysis for linear buckling. Apply a unit-magnitude force constraint at the load location; the result factors are the multipliers at which buckling occurs.

輸入參數analysis必填n_factorsworkspace

fem_buckling_results

FEM Buckling Results

Extract buckling load multipliers from a completed buckling run. Pass `analysis`, or the `job_id` of a finished fem_run_submit solve. Returns {buckling_factors: [...], modes: [{mode, factor}, ...]}.

輸入參數job_idanalysisworkspace

fem_thermal_results

FEM Thermal Results

Extract temperature field summary from a completed thermal run. Pass `analysis`, or the `job_id` of a finished fem_run_submit solve. Returns {temperatures_c: {min, max, mean}, top_n_hot_nodes: [...]}.

輸入參數top_njob_idanalysisworkspace

fem_mesh_refinement

FEM Mesh Refinement

Add a local mesh-refinement region to an existing FEM mesh. mesh: handle of the FEM mesh. refs: list of {handle, face|edge|tag} dicts identifying the elements (faces/edges) to refine on. char_length: characteristic element length (mm) on those elements; should be smaller than the global mesh setting to actually refine.

輸入參數mesh必填namerefs必填workspacechar_length必填

fem_mesh

FEM Mesh

Create a Gmsh mesh of `body`, attached to `analysis`. char_length: max characteristic element length in mm. 0 = let Gmsh pick. min_length: min element length in mm. Default char_length/10, which keeps sliver edges in imported CAD from exploding the mesh; 0 removes the floor. element_order: '1st' or '2nd' (quadratic). Use '2nd' for bending/modal accuracy — linear tets (C3D4) shear-lock and overstiffen thin sections (a cantilever's first natural frequency lands ~50% high with only 1-2 elements through the thickness; 2nd-order tets bring it within ~1% of beam theory). Default lets Gmsh choose. Returns {handle, name, nodes, tets, min_length}.

輸入參數body必填nameanalysis必填workspacemin_lengthchar_lengthelement_order

fem_run

FEM Run

Run the CalculiX solver on an analysis. BLOCKS the MCP channel until the solve finishes (up to 600 s). Which to reach for: fem_run for small solves — a coarse mesh, linear static, a few thousand nodes — that finish in seconds, where a poll round-trip only adds latency. fem_run_submit for anything that may take a minute or more (fine or 2nd-order meshes, modal/buckling with many modes, nonlinear material or geometry), so the channel stays free while ccx runs. `workdir` defaults to `<TMPDIR>/ankusdrive_fem_<worker pid>`, which is removed when the worker exits (close_workspace, restart_worker, server shutdown); name your own workdir to keep the .inp/.frd/.dat. Raises if ccx exits non-zero (with its *ERROR lines), or if a fem_run_submit solve is still running on this analysis or workdir. Returns {workdir, status: 'ok', returncode, analysis_type, result_objects, analysis}.

輸入參數workdiranalysis必填workspace

fem_run_submit

FEM Run Submit

Run the CalculiX solver on an analysis OFF the MCP channel — the submit→poll form of fem_run, for solves long enough to stall the channel (see fem_run for which to use). The solver input is written before this returns, so a missing solver, material or mesh fails here, synchronously, exactly as fem_run would. ccx then runs in the background. Poll job_status: once ccx exits, the results are imported into the analysis on your NEXT poll (FreeCAD work runs on the worker's main thread, which a poll gives a turn) — so keep polling until status is 'done'; a job nobody polls never finishes. A ccx error fails the job with its *ERROR lines. When done, read results with fem_results / fem_result_probe / fem_modal_results / fem_buckling_results / fem_thermal_results passing either `analysis` or this `job_id` (don't job_result(discard=True) first if you plan to use the job_id). While the job runs, fem_run or another submit on the same analysis or workdir is refused. Returns {job_id, status, cache_hit}; job_result's `result` is fem_run's return dict.

輸入參數workdiranalysis必填workspace

fem_results

FEM Results

Extract summary results from an analysis. Pass `analysis`, or the `job_id` of a finished fem_run_submit solve (a running or failed job is an error that says so). Returns {max_vonmises_mpa, max_displacement_mm, max_displacement_vector, top_stress_nodes: [{node, vonmises_mpa, displacement_mm}, ...]}.

輸入參數top_njob_idanalysisworkspace

fem_result_probe

FEM Result Probe

Probe FEM results at a specific location, instead of only the global max + top-N that `fem_results` returns. Answers "what is the stress at this point / on this face?" — the agent-friendly form for design iteration. Pick exactly one mode: * POINT: point=[x, y, z] (mm, model coordinates). Interpolates the field barycentrically inside the tet containing the point (method='interpolated'); if the point is outside the mesh it falls back to the nearest node and reports distance_mm (method='nearest_node'). * FACE: handle=<body handle> + face=<'f_*' tag or 'FaceN'>. Aggregates the field over the mesh nodes on that CAD face, returning {min, max, mean}. field: 'auto' (default — every field present in the result) | 'vonmises' | 'displacement' | 'temperature'. Pass `analysis`, or the `job_id` of a finished fem_run_submit solve. Returns (point mode) {mode:'point', query_point, method, element_id?, node?, distance_mm, vonmises_mpa?, displacement_mm?, displacement_vector?, temperature_c?}; (face mode) {mode:'face', face, node_count, vonmises_mpa?:{min,max,mean}, displacement_mm?:{...}, temperature_c?:{...}}.

輸入參數facefieldpointhandlejob_idanalysisworkspace

fem_cantilever_demo

FEM Cantilever Demo

Run the built-in cantilever FEM demo end-to-end (geometry → mesh → CalculiX). Dimensions in mm, force in N. Returns {nodes, tets, max_displacement_mm, max_vonmises_mpa, workdir}. A fresh document is created; existing state in the session is NOT overwritten but a new document becomes active.

輸入參數forcewidthheightlengthworkdirmesh_sizeworkspace

material_get

Material Get

Look up a material by name (e.g. 'AL6061-T6'). Returns the full property card as SI quantity strings (YoungsModulus, PoissonRatio, Density, yield_strength, fracture_toughness, thermal_conductivity, cte, rough_cost, refractive_index where applicable, source, basis). The structural keys are FEM-card-compatible, so the result feeds fem_set_material directly. On a miss returns {ok:false, reason} with a did_you_mean suggestion.

輸入參數name必填workspace

material_select

Material Select

Ashby-style selection: filter the corpus, then rank survivors. criteria keys are min_<accessor>/max_<accessor> (e.g. min_yield_mpa, max_density_g_cc, min_service_temp_c). rank_by: specific_strength | specific_stiffness | strength | stiffness | cost | density. Returns {rank_by, count, criteria, candidates:[{name, score, yield_mpa, density_g_cc, youngs_gpa, cost_usd_kg}, ...]} best-first; an empty filter returns no candidates rather than the closest miss.

輸入參數rank_bycriteriaworkspace

material_list

Material List

List available materials, optionally filtered to one category ('aluminum' | 'steel' | 'titanium' | 'magnesium' | 'polymer' | 'glass'). Returns {count, category, materials:[{name, category}, ...]} sorted by name.

輸入參數categoryworkspace

fluid_props

Fluid Props

Thermophysical properties of a fluid at (T, P) from CoolProp's equation of state (issue #100). ``name`` is a fluid (e.g. 'water', 'air', 'R134a', 'CO2', 'nitrogen'), ``T_K`` absolute temperature [K], ``P_Pa`` pressure [Pa, default 1 atm). Returns {ok, density [kg/m³], viscosity [Pa·s], cp [J/kg·K], conductivity [W/m·K], prandtl, kinematic_viscosity [m²/s], fidelity, valid_range_ok, source, warnings, coolprop_available}. This is the DEFAULT fluid-property source behind the convection/CFD screens; explicit caller props still override. Degrades cleanly when the (opt-in) CoolProp extra is absent: air/water return ≈20 °C constants (fidelity='constant_fallback'); other fluids return {ok:false, reason, install}. CoolProp (BSD-3) is cited as the source.

輸入參數T_K必填P_Paname必填workspace

bolted_joint_check

Bolted Joint Check

Rate a bolted joint (VDI 2230-lite). Geometry from bolt_dia_mm (+pitch_mm) OR a standard thread named via bolt_size ("M8"/"M8x1.0") that pulls dia/pitch and the standards-table tensile stress area; proof strength from the ISO 898-1 property_class ("8.8") when given. Preload from torque via T=K*F*d (pass torque_nm OR preload_n). Returns {preload_n, tensile_stress_area_mm2, bolt_stress_mpa, preload_pct_proof, bolt_stress_with_load_mpa, separation_load_n, separation_margin, pass, governing}. `preload_target_pct` is the fraction of proof strength the preload is judged against (0.75 default; 0.65 for a reused bolt, 0.90 for a critical permanent joint).

輸入參數k_factormaterialpitch_mmbolt_sizepreload_ntorque_nmworkspacebolt_dia_mmproperty_classexternal_load_npreload_target_pctproof_strength_mpa另有 1 個

bearing_life

Bearing Life

Basic rating life L10 (ISO 281): L10=(C/P)^p rev (p=3 ball, 10/3 roller), L10h=L10*1e6/(60n). Supply the dynamic rating C as dynamic_load_c_n, or pull it from the deep-groove ball catalog by designation (e.g. "6205" -> C=14.0 kN, also reporting bore/OD/width, C0 and static safety factor). Returns {l10_million_rev, l10_hours, load_ratio, dynamic_load_c_n, exponent, pass, ...} (pass vs target_hours when given).

輸入參數kindspeed_rpm必填workspacedesignationtarget_hoursdynamic_load_c_nequivalent_load_p_n必填

spring_check

Spring Check

Rate a helical compression spring (Wahl). rate k=G d^4/(8 D^3 Na); corrected shear tau=Kw 8FD/(pi d^3). Pass force_n OR deflection_mm. Returns {spring_index, wahl_factor, rate_n_mm, force_n, deflection_mm, shear_stress_mpa, slenderness, buckling_flag, allowable_shear_mpa, shear_sf, pass}. G and the allowable come from `material` unless overridden. `shear_modulus_mpa` sets G directly; `allowable_shear_mpa` replaces the 0.45·UTS estimate, which falls back to 700 MPa (spring steel) for a material carrying no UTS — pass it explicitly for anything else, since shear_sf and `pass` scale with it.

輸入參數force_nmaterialworkspacewire_dia_mm必填active_coils必填deflection_mmfree_length_mmcoil_mean_dia_mm必填shear_modulus_mpaallowable_shear_mpa

gear_rating

Gear Rating

Rate spur-gear tooth bending (Lewis): sigma=Ft/(b*m*Y). Pass tangential_force_n, or power_w + pinion_speed_rpm. Returns {tangential_force_n, pitch_dia_mm, pitch_line_velocity_m_s, lewis_form_factor, bending_stress_mpa, allowable_bending_mpa, bending_sf, pass}. First-order screen, not full AGMA. The allowable defaults to the material's fatigue endurance (else 0.3·UTS); `allowable_bending_mpa` overrides it with an AGMA/spec number.

輸入參數teeth必填power_wmaterialmodule_mm必填workspaceface_width_mm必填pinion_speed_rpmlewis_form_factortangential_force_nallowable_bending_mpa

belt_drive

Belt Drive

Rate a belt drive (Eytelwein/capstan). Wrap theta=pi-2asin((D-d)/2C), Fe=P/V, T1/T2=e^(mu*theta) (V-belt divides mu by sin(beta/2)). Returns {wrap_angle_deg, belt_speed_m_s, effective_force_n, tension_ratio, tight_side_n, slack_side_n, transmissible_power_w, pass}.

輸入參數power_w必填workspacefriction_coefsmall_pulley_rpm必填vbelt_groove_degcenter_distance_mm必填tight_side_limit_nlarge_pulley_dia_mm必填small_pulley_dia_mm必填

press_fit_stress

Press Fit Stress

Rate an interference (press/shrink) fit via Lamé. p=delta_r E (ro^2-rc^2)/ (2 rc ro^2); hub bore hoop=p(ro^2+rc^2)/(ro^2-rc^2); torque=2pi mu p rc^2 L. interference_mm is diametral. E comes from `material`'s Materials-DB card; `youngs_modulus_mpa` overrides it. Every output is LINEAR in E, so a material the corpus has no modulus for is an ERROR, not an assumption (#269) — the message names both exits. `pass` is THREE-state: the hub yield check needs the card's yield_mpa, and a card without one (Fused-Silica, Gold, Concrete, ...) leaves the check unperformed. That returns pass=null with the reason in `warnings` — an unperformed check is not a passed one. Test `pass is True`, not truthiness. Returns {contact_pressure_mpa, hub_hoop_stress_mpa, torque_capacity_nm, axial_force_n, hub_yield_sf, youngs_modulus_mpa, youngs_basis, hub_yield_basis, pass, warnings}.

輸入參數materialworkspaceshaft_dia_mm必填friction_coefinterference_mm必填hub_outer_dia_mm必填youngs_modulus_mpaengagement_length_mm必填

seal_check

Seal Check

Rate an O-ring gland (pairs with oring_groove): squeeze=W-depth, fill= (pi/4 W^2)/(width*depth). Squeeze must sit in the application band (static 15-30%, dynamic 10-20%), fill below max_gland_fill_pct. Returns {squeeze_mm, squeeze_pct, gland_fill_pct, squeeze_range_pct, within_squeeze, within_fill, pass}.

輸入參數workspaceapplicationgroove_depth_mm必填groove_width_mm必填max_gland_fill_pctcross_section_dia_mm必填

chain_drive

Chain Drive

Rate an ANSI roller-chain drive (ASME B29.1). Rated at the lower of the link-plate-fatigue (HP1=0.004*N1^1.08*n1^0.9*P^(3-0.07P), low speed) and roller-impact (HP2=1000*Kr*N1^1.5*P^0.8/n1^1.5, high speed) envelopes, P in inches. Give chain_pitch_mm (matching add_sprocket) OR a chain_number ("40","60",...) for pitch+Kr; strands scale by the B29.1 factor. Powers in W. Returns {rated_power_w, type1_power_w, type2_power_w, governing, strands, strand_factor, ..., power_sf?, pass}. `k_r` overrides the roller-impact constant the chain_number lookup supplies (29 for the 25-240 series, 17 for the lightweight #41) — needed for a chain outside the ANSI table.

輸入參數k_rpower_wstrandsspeed_rpm必填workspaceteeth_small必填chain_numberchain_pitch_mm

weld_group

Weld Group

Rate a planar fillet-weld group by Blodgett's treat-weld-as-a-line method. segments=[((x1,y1),(x2,y2)),...] (mm); an in-plane force_n=[Fx,Fy] at load_point_mm=[px,py] gives direct shear f=F/L plus torsional f=T*r/J from the eccentric moment about the weld centroid, added vectorially at the worst end. required_leg = f_r/(0.707*allowable); given leg_mm, throat stress f_r/(0.707*leg) is checked vs allowable. Returns {weld_length_mm, centroid_mm, Ix_mm3, Iy_mm3, J_mm3, direct_shear_n_per_mm, max_shear_n_per_mm, worst_point_mm, required_leg_mm, throat_stress_mpa?, shear_sf?, pass}. `weld_type` ('fillet' default) labels the group and selects the throat convention.

輸入參數leg_mmforce_n必填segments必填weld_typeworkspaceload_point_mm必填allowable_shear_mpa

tolerance_stackup

Tolerance Stackup

Stack a dimension chain. Each chain entry is {name, nominal, plus, minus} with plus/minus the signed upper/lower deviations (plus>=minus; symmetric shorthand {nominal, tol}); add direction:-1 for a subtractive/gap link. method: worstcase | rss | montecarlo (each adds a deeper block). Half-bands are read as 3-sigma; cpk/pct_in_spec use spec_min/spec_max if given, else the worst-case bounds. Returns {nominal, worstcase:{min,max,spread}, rss:{sigma,min_3s,max_3s}, montecarlo:{mean,std,cpk,pct_in_spec,spec}}. Instead of a hand-built chain, pass a live `handle` (+ `axis`, '+z'/'-x'/… or [x,y,z]) and the chain is derived off the solid: planar step faces perpendicular to the axis become consecutive station-to-station links (the stack a height gauge reads off a stepped part). Per-link tolerance: `default_tol` (± mm), else the ISO 2768-1 `general` class ('f'|'m'|'c'|'v', default 'm' — the drawing-note default for untoleranced dimensions). The result then echoes the derived chain (+ axis, n_step_faces). `seed` fixes the montecarlo draw (12345 default) so the same chain returns the same cpk/pct_in_spec run to run — that determinism is a contract, so change it only to check a result is not an artefact of one draw.

輸入參數axisseedchainhandlemethodgeneralsamplesspec_maxspec_minworkspacedefault_tol

fit_check

Fit Check

Classify a hole/shaft pair. hole and shaft are {nominal, plus, minus} (signed deviations) or {nominal, tol}. Returns {fit_class:'clearance'| 'transition'|'interference', min_clearance, max_clearance, nominal_clearance, prob_interference} (prob from a normal model with half-band = 3-sigma).

輸入參數hole必填shaft必填workspace

fit_class

Fit Class

ISO 286 limits for a fit code (e.g. 'H7/g6'), in mm. v1 covers a hole-basis H with shaft clearance letters (h, g, f, e). Returns {basic_size, fit, hole:{upper_dev,lower_dev,min,max}, shaft:{...}, fit_class, min_clearance, max_clearance, prob_interference}. Errors on an out-of-table size (>500 mm) or an unsupported code (non-H hole or interference shaft letter).

輸入參數fitworkspacebasic_size必填

gdt_check

GD&T Check

Check a measured feature against a GD&T tolerance zone. control: position | flatness | straightness | circularity | cylindricity | perpendicularity | parallelism | angularity | concentricity | runout | total_runout | profile_line | profile_surface. actual is the measured deviation; for position pass offset={x,y} to use the diametral 2*hypot(x,y). mmc_bonus adds bonus tolerance. Returns {control, zone, effective_zone, actual, margin, pass, datum_refs}.

輸入參數zone必填actualoffsetcontrol必填mmc_bonusworkspacedatum_refs

fatigue_check

Fatigue Check

Rate fatigue life (S-N Basquin + Goodman mean-stress correction). σ_a = stress_range/2; infinite-life SF = 1/(σ_a/σ_e + σ_m/σ_uts); finite life from an equivalent fully-reversed amplitude on a log-log S-N line. σ_e/σ_uts come from the material (or overrides). pass = survives `cycles` (σ_ar ≤ σ_e ⇒ infinite life); a tensile mean ≥ σ_uts fails outright. Returns {stress_amplitude_mpa, mean_stress_mpa, endurance_mpa, uts_mpa, equiv_reversed_mpa, safety_factor, life_cycles, required_cycles, pass, governing_mode, endurance_basis}. The S-N line runs from (1e3, `s1000_fraction`·UTS) to (`endurance_cycles`, σ_e). Both default to the steel convention (0.9 and 1e6); aluminium and other non-ferrous alloys have no true endurance knee, so set `endurance_cycles` to the life the quoted σ_e was measured at (commonly 5e8).

輸入參數cyclesuts_mpamaterialworkspaceendurance_mpas1000_fractionmean_stress_mpaendurance_cyclesstress_range_mpa必填

fracture_check

Fracture Check

Rate brittle fracture (LEFM): K = Y·σ·√(π·a) vs K_IC. a is crack length in mm; Y (geometry_factor) defaults 1.12 (edge crack), 1.0 for a centre crack. K_IC from the material (or override). Critical crack a_c = (K_IC/(Y·σ))²/π. Returns {k_applied_mpa_sqrt_m, k_ic_mpa_sqrt_m, geometry_factor, safety_factor, margin, critical_crack_mm, pass}; a crack past a_c gives SF<1 and margin<0.

輸入參數materialworkspacestress_mpa必填crack_len_mm必填geometry_factorfracture_toughness_mpa_sqrt_m

wear_estimate

Wear Estimate

Estimate sliding wear (Archard): V = k·F·s/H. k (wear_coef) is empirical — pass it, or it's looked up by the material_pair's category pair (order-of- magnitude). H (hardness_mpa) defaults to Tabor 3·σ_y of the softer member. With apparent_area_mm2 a mean depth is reported and gated by max_depth_mm. Returns {wear_coef, hardness_mpa, volume_loss_mm3, depth_loss_mm, coef_basis, hardness_basis, pass}.

輸入參數load_n必填wear_coefworkspacehardness_mpamax_depth_mmmaterial_pairsliding_dist_m必填apparent_area_mm2

creep_flag

Creep Risk Flag

Screen for creep risk: compare operating temperature to the material's max service temperature (Materials DB, or an override). A screen, not a Larson-Miller life model. pass = below the service limit. Returns {operating_temp_c, service_temp_c, margin_c, stress_mpa, creep_risk, pass, reason}.

輸入參數temp_c必填materialworkspacestress_mpa必填max_service_temp_c

h_estimate

Heat Transfer Coefficient Estimate

Screening convection coefficient h (NO solver) — the honest h_conv to feed thermal_lumped / thermal_transient_1d / a convection BC, instead of a guess. `geometry` picks the correlation: natural (velocity_m_s = 0) 'vertical_plate' | 'horizontal_cylinder' (Churchill–Chu); forced (velocity_m_s > 0) 'flat_plate' (averaged laminar/mixed Nu) | 'cylinder_crossflow' (Hilpert). `characteristic_mm` is the plate height/length or cylinder diameter. Film-temp air properties built in; another `fluid` needs explicit k_w_mk + nu_m2_s + pr (+ beta_per_k for natural). emissivity > 0 adds the linearized radiation screen into h_total_w_m2k. This is a focusing estimate, not a gate: fidelity='correlation' with band_pct the literature scatter (±15–20 %). Escalate to the conjugate solve `cht_channel_submit` (or a meshed convection BC via thermal_transient_submit) when the thermal margin is within ~2× band_pct. Returns {geometry, mode, correlation, h_conv_w_m2k, h_rad_w_m2k, h_total_w_m2k, nusselt, reynolds, rayleigh, prandtl, film_temp_c, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數prfluidk_w_mknu_m2_sgeometry必填workspacebeta_per_kemissivityt_ambient_ct_surface_c必填velocity_m_scharacteristic_mm必填

acoustic_screen

Acoustic Screen

Closed-form acoustics screen (NO solver). `kind`: 'cavity_modes' (lx/ly/lz_mm -> the lowest n_modes rigid-cavity eigenfrequencies f=(c/2)·√(Σ(n/L)²) with [nx,ny,nz] indices — exact, and the future oracle for the planned Elmer HelmholtzSolve FEM) | 'helmholtz' (neck_area_mm2 + neck_length_mm + cavity_volume_mm3 -> resonance with flanged end correction — ±10 %) | 'mass_law' (frequency_hz + surface_density_kg_m2 -> limp-wall TL = 20·log₁₀(f·m″)−47 dB — ±3 dB) | 'duct_cutoff' (duct_width_mm or duct_diameter_mm -> first cross-mode; plane waves only below — exact). Sound speed from air at t_ambient_c unless c_m_s given. Fidelity is labeled per kind; escalate to the Elmer `acoustic_fem_submit` solve (Tier B1) when the margin is within ~2× the band. Returns {kind, c_m_s, fidelity, band_pct, band_db, valid_range_ok, warnings, escalate_to} plus per kind: {modes:[{f_hz,n}], f_fundamental_hz} | {f_resonance_hz, neck_radius_mm, l_eff_mm} | {tl_db, fm_product} | {f_cutoff_hz, geometry}.

輸入參數kind必填c_m_slx_mmly_mmlz_mmn_modesworkspacet_ambient_cfrequency_hzduct_width_mmneck_area_mm2neck_length_mm另有 3 個

plate_check

Plate Check

Handbook bending of a uniformly loaded flat plate (NO solver) — the "do I need FEM at all?" screen. `shape`: 'rectangular' (a_mm × b_mm, short side drives; Roark/Timoshenko ν=0.3 coefficients σ=β·q·b²/t², δ=α·q·b⁴/(E·t³), interpolated in a/b) | 'circular' (diameter_mm; exact closed forms). `support`: 'simply_supported' | 'clamped' (all edges). E from youngs_gpa or a Materials-DB `material` (which also supplies yield for yield_safety_factor). Exact within thin-plate theory, and the limits are returned as flags (thin_plate_ok: span/t ≥ 10; small_deflection_ok: δ ≤ t/2) — a tripped flag means escalate to the CCX fem_* pipeline (escalate_to='fem_run'). Returns {shape, support, aspect_ratio, beta, alpha, sigma_max_mpa, deflection_max_mm, yield_safety_factor, thin_plate_ok, small_deflection_ok, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數a_mmb_mmshape必填poissonsupportmaterialworkspaceyoungs_gpadiameter_mmpressure_kpa必填thickness_mm必填

beam_buckling

Beam Buckling

Exact column-buckling screen (Euler + Johnson, NO solver) — the closed-form twin the CalculiX `fem_buckling` eigen-solve is gated against (as `beam_modal` is to `fem_modal`). Section: width_mm+height_mm (solid rectangle, weak axis automatic), diameter_mm (round), or explicit area_mm2+i_min_mm4. E/σ_y from youngs_gpa/yield_mpa or a Materials-DB `material`. `end_condition`: 'pinned_pinned' | 'fixed_free' | 'fixed_pinned' | 'fixed_fixed' (theoretical K). Euler σ_cr=π²E/λ² above the transition slenderness √(2π²E/σ_y), Johnson parabola below (both exactly σ_y/2 at it). With load_n the safety factor P_cr/P is returned. Escalate to `fem_buckling` for non-prismatic / eccentric / built-up cases. Returns {end_condition, k_factor, slenderness, transition_slenderness, governing, sigma_cr_mpa, p_cr_n, area_mm2, i_min_mm4, radius_gyration_mm, safety_factor, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數load_narea_mm2materialwidth_mmheight_mmi_min_mm4length_mm必填workspaceyield_mpayoungs_gpadiameter_mmend_condition

plastic_collapse

Plastic Collapse

Exact plastic-hinge collapse of a solid rectangular beam (NO solver) — the closed-form twin the perfectly-plastic CalculiX solve (`fem_set_nonlinear_material`) is gated against. The beam bends about the `width_mm` axis (depth = `height_mm`). σ_y from `yield_mpa` or a Materials-DB `material`. Elastic modulus S = b·h²/6, plastic modulus Z = b·h²/4, shape factor Z/S = 1.5; yield moment M_y = σ_y·S, fully-plastic moment M_p = σ_y·Z. `support` maps the collapse moment to a point load: 'cantilever' (M = P·L) or 'simply_supported' (central, M = P·L/4). With `load_n` the applied moment and its margins to M_y / M_p (and the regime: elastic / partially_plastic / collapsed) are returned. A perfectly-plastic FEM solve caps the surface stress at σ_y and loses equilibrium at M_p; linear theory climbs past both — that contrast is the gate. Escalate to `fem_set_nonlinear_material` for non-rectangular sections or partial-plasticity fields. Returns {support, S_elastic_mm3, Z_plastic_mm3, shape_factor, yield_mpa, yield_moment_nmm, plastic_moment_nmm, yield_load_n, collapse_load_n, applied_moment_nmm, margin_to_yield, margin_to_collapse, regime, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數load_nsupportmaterialwidth_mm必填height_mm必填length_mm必填workspaceyield_mpa

elastica_deflection

Elastica Deflection

Exact large-deflection cantilever tip — Bisshopp–Drucker elastica (NO solver) — the closed-form twin the *NLGEOM CalculiX solve is gated against. Section: width_mm+height_mm (solid rectangle, I = b·h³/12, load transverse to height_mm) or explicit i_mm4. E from youngs_gpa or a Materials-DB `material`. Load parameter α = P·L²/(E·I); the tip slope solves the elliptic-integral elastica. Linear theory δ/L = α/3 over-predicts the transverse tip and ignores the axial draw-in — the elastica captures both, and `nonlinear_over_linear` is the divergence the solve must reproduce. Valid for tip slope < ~80° (α ≲ 3.5); beyond that escalate to a follower-load `fem_set_nonlinear_material` solve. Returns {alpha, tip_slope_deg, tip_disp_mm (transverse), tip_x_mm (axial projection), axial_drawin_mm, linear_tip_mm, nonlinear_over_linear, youngs_mpa, I_mm4, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數i_mm4load_n必填materialwidth_mmheight_mmlength_mm必填workspaceyoungs_gpa

hertz_contact

Hertz Contact

Exact Hertzian point-contact peak pressure (NO solver) — the screening twin of a frictional *CONTACT PAIR solve. Sphere of `radius_mm` on a flat (default) or on a second sphere `radius2_mm` (negative for a conforming socket). Each body's elastics from youngs#_gpa+poisson# or a Materials-DB `material#`; body 2 defaults to body 1. Reduced modulus 1/E* = (1−ν₁²)/E₁ + (1−ν₂²)/E₂, effective radius 1/R = 1/R₁ + 1/R₂; contact radius a = (3FR/4E*)^(1/3), peak pressure p₀ = 3F/(2πa²) = 1.5× mean, approach δ = a²/R. Half-space theory: valid while a ≪ R and p₀ below first sub-surface yield (~1.6·σ_y) — past that escalate to the nonlinear `fem_set_nonlinear_material` contact path. Returns {e_star_mpa, effective_radius_mm, contact_radius_mm, peak_pressure_mpa, mean_pressure_mpa, approach_mm, a_over_R, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數load_n必填poisson1poisson2material1material2radius_mm必填workspaceradius2_mmyoungs1_gpayoungs2_gpa

laminate_properties

Laminate Properties

Effective stiffness, thermal warp, and first-ply failure of a bonded multi-layer composite stack (NO solver) — the closed-form screening twin a layered multi-material CalculiX `fem_run` static solve is gated against (e.g. 1 metal layer + 1 plastic layer, or any `[(material, thickness), …]`). `layers` is the stack bottom→top; each entry is a mapping with a `thickness` (mm) and either a corpus `material` name or explicit `E`/`youngs_mpa`/ `youngs_gpa` (+ optional `nu`/`poisson`, `yield_mpa`, `cte`/`cte_per_k`, `density_kg_m3`, `thermal_conductivity_w_mk`); explicit values override the card. `width_mm` scales EI / first-ply. Optional `delta_T` (K) gives the bimetal thermal curvature; `force_n` (in-plane, total across width) and/or `moment_nmm` (about the neutral axis) give the first-ply margin. Computes the in-plane modulus (Voigt rule-of-mixtures parallel, Reuss series through-thickness); the transformed-section neutral axis, EI_eff, and flexural modulus E_flex = 12·EI/(b·h³); the CLT A/B/D matrices per unit width (B ≠ 0 ⇒ bending–extension coupling / warp warning); mass-averaged ρ, stiffness-weighted in-plane CTE, series/parallel thermal conductivity; the transformed-section bimetal curvature (= Timoshenko's two-layer formula exactly, also reported); and per-layer extreme-fibre stress → margin to yield → governing layer + load to first yield. A single-material stack reduces to that material's E / EI; a symmetric stack gives B = 0; ΔT = 0 or zero CTE-mismatch gives zero curl. Escalate to a layered `fem_run` solve for thick stacks, anticlastic curvature, free-edge interlaminar stress, or non-isotropic plies. Returns {n_layers, width_mm, total_thickness_mm, layers, E_inplane_mpa, E_through_mpa, neutral_axis_mm, EI_eff_nmm2, E_flex_mpa, A_matrix, B_matrix, D_matrix, coupling_ratio, asymmetric, rho_eff_kg_m3, cte_eff_per_k, k_through_w_mk, k_inplane_w_mk, delta_T, therma

輸入參數layers必填delta_Tforce_nwidth_mmworkspacemoment_nmm

monopole_sphere

Monopole Sphere

Exact pulsating (monopole) sphere radiated power + far-field pressure (NO solver) — the closed-form twin the Bempp exterior-acoustics BEM radiation solve (`acoustic_radiation_submit`) is gated against. A sphere of radius `a_m` vibrating with uniform surface normal velocity `u_amp` at `freq_hz` radiates W = (ρc/2)|U|²(4πa²)(ka)²/(1+(ka)²) and, at range `r_m`, |p(r)| = ρc|U|·ka/√(1+(ka)²)·(a/r) — both EXACT. The radiation efficiency σ=(ka)²/(1+(ka)²) → 0 (poor sub-wavelength radiator) as ka→0 and → 1 as ka→∞. `rho`/`c` default to air at 20 °C. A BEM Neumann (velocity) solve must reproduce W and |p(r)|. Returns {a_m, freq_hz, k_per_m, ka, u_amp, rho, c, radiation_efficiency, radiated_power_w, surface_pressure_abs, r_m, farfield_pressure_abs, farfield_pressure_x_r, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數ca_m必填r_mrhou_ampfreq_hz必填workspace

rigid_sphere_scattering

Rigid Sphere Scattering

Exact rigid-sphere plane-wave scattering far-field form function via the Mie series (NO solver) — the closed-form twin the Bempp exterior-acoustics BEM scattering solve (`acoustic_radiation_submit`, problem='scattering') is gated against. For compactness `ka` and scattering angle `theta_deg` (from the forward direction; 180° is backscatter), f∞(θ) = (2/ika)·Σₙ(2n+1)[−j'ₙ(ka)/h'ₙ(ka)]· Pₙ(cosθ) — a rigorous spherical-harmonic sum (Neumann ∂p/∂r=0 on the sphere) truncated past convergence (fidelity='exact'). The backscatter |f∞(π)| → 1 in the geometric (ka≫1) limit and rises through the resonance region. A BEM scattered far field must land on |f∞(θ)|. Returns {ka, theta_deg, a_m, form_function_abs, form_function_re, form_function_im, backscatter_abs, n_terms, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數ka必填a_mtheta_degworkspace

acoustic_radiation_submit

Acoustic Radiation Submit

Exterior-acoustics boundary-element solve on Bempp, asynchronous (OFF the MCP channel) — the real-field twin of the analytic `monopole_sphere` / `rigid_sphere_scattering` oracles. Bempp is MIT but needs meshio>=4 (clashing with solidspy's meshio==3 in the shared venv), so it is run ONLY out-of-process via ankusdrive/bempp_runner.py under a dedicated .venv-bempp; degrades to {ok:false, reason, install} when no bempp venv resolves. `problem`='radiation' (default): a pulsating (monopole) sphere of radius `a_m`, uniform surface velocity `u_amp` at `freq_hz`, into air (`rho`,`c`), mesh size `h` (fraction of a). The result's `radiated_power_w` / `farfield_pressure_x_r` vs the monopole_sphere oracle (ratio≈1) IS the gate. `problem`='scattering': a rigid sphere insonified by a unit plane wave; sweep `ka_list`, report the far-field form function at `theta_deg` angles (`h_per_wl` elements/wavelength) — gated against the rigid_sphere_scattering Mie oracle. `problem`='mesh_solve': a radiation solve on a REAL FreeCAD `model` (handle), tessellated to a surface mesh here and fed to the BEM engine (`scale_to_m` mm→m, `linear_deflection` mesh tolerance). Returns the degradation dict, or {job_id, status, cache_hit}; poll job_result for {ok, ka, radiated_power_w, farfield_pressure_x_r, surface_pressure_abs_mean, n_elements, wall_s} (radiation/mesh_solve) or {results:[{ka, form_function_abs{}, backscatter_abs, n_elements}]} (scattering).

輸入參數cha_mr_mrhomodelu_ampfreq_hzka_listproblemtimeouth_per_wl另有 4 個

waveguide_cutoff

Waveguide Cutoff

Exact rectangular-waveguide cutoff frequency (NO solver) — the closed-form twin the openEMS FDTD full-wave solve (`em_fullwave_submit`) is gated against. Broad wall `a_mm`, narrow wall `b_mm` (default a/2, WR convention). `mode` is 'TE<m><n>'/'TM<m><n>'. f_c(m,n) = (c/2√εᵣ)·√((m/a)²+(n/b)²); dominant TE10 reduces to the EXACT f_c = c/(2a√εᵣ). Below f_c the guide is evanescent (axial β imaginary, nothing transmits), above it propagates with guided wavelength λ_g = 2π/β. With a probe `freq_ghz` the regime (propagating / evanescent), k, β, λ_g and (below cutoff) the exact attenuation α = √(k_c²−k²) are returned — α is what the FDTD solve's field probes are gated against (`alpha_ratio`). Returns {mode, m, n, a_mm, b_mm, eps_r, cutoff_hz, cutoff_ghz, kc_per_m, next_mode_cutoff_ghz, single_mode_band_ghz, probe_freq_ghz, regime, k_per_m, beta_per_m, alpha_per_m, guided_wavelength_mm, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數a_mm必填b_mmmodeeps_rfreq_ghzworkspace

dipole_resonance

Dipole Resonance

Thin centre-fed half-wave dipole first resonance (NO solver, banded) — the closed-form twin the openEMS FDTD S11 antenna sweep (`em_fullwave_submit`) is gated against. Give EXACTLY ONE of `length_mm` (→ resonant frequency) or `freq_ghz` (→ resonant length). End-effect shortening k = `shortening` makes the resonant length a little under λ/2: L = k·λ, f_r = k·c/L (k≈0.48 textbook; ≈0.475 typical wire). Because k tracks the length/diameter ratio this is a ±band correlation (fidelity='banded', ~±3% over k∈[0.46,0.49]); an FDTD S11 sweep must put its first resonance inside [freq_lo, freq_hi] (or [length_lo, length_hi]). Returns {given, shortening, half_wavelength_mm, resonant_length_mm, resonant_freq_ghz, freq_lo_ghz, freq_hi_ghz, length_lo_mm, length_hi_mm, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數freq_ghzlength_mmworkspaceshortening

em_fullwave_submit

EM Fullwave Submit

Full-wave FDTD EM solve on openEMS, asynchronous (OFF the MCP channel) — the real-field twin of the analytic `waveguide_cutoff` / `dipole_resonance` oracles. openEMS is GPL-3.0 and is run ONLY out-of-process via ankusdrive/em_fullwave_gpl_runner.py; degrades to {ok:false, reason, install} when no openEMS venv resolves. `problem`='waveguide_sweep' (default): hollow rectangular guide, broad wall `a_mm`/narrow wall `b_mm` (default a/2), length `length_mm` (default 120), TE10 port at each end. Sweep `f_start_ghz`..`f_stop_ghz` (default 4..10 GHz — straddling the WR-90 cutoff 6.56 GHz) in `n_freq` points, `nrts` max timesteps, `cells_per_wl` mesh density, `eps_r` fill, `end_criteria` energy stop (1e-6). THE GATE IS `alpha_ratio`≈1: voltage probes along the guide read the SOLVED field's decay rate below cutoff at `decay_f_ratio`·f_c (default 0.7) against the exact α = sqrt((π/a)² − k²); it degrades under a coarse mesh or a truncated `nrts`. `alpha_ratio` is null with a `decay_note` when the guide is too short for the probe window. `fc_ratio` (half-power crossing vs c/2a) is only a PORT-SETUP CHECK: openEMS's analytic port β zeroes every sub-cutoff sample, so it reads the frequency grid, not the field. A degenerate mesh/length (port blocks overlapping) returns {ok:false, error}. `problem`='dipole_s11': centre-fed thin dipole (`length_mm`, `gap_mm`, `radius_mm`, each resolved by its own mesh lines), sweep S11, report first resonance. Mesh is `mesh_res_mm`, else λ(`mesh_f_ghz`, default f_stop) / `cells_per_wl` (default 30) — pin it to vary the sweep window alone. Returns the degradation dict, or {job_id, status, cache_hit}; poll job_result for {ok, fc_analytic_ghz, freq_ghz[], s21_db[], transmission_norm[], alpha_fdtd, alpha_exact, alpha_ratio, decay_freq_ghz, decay_probe_z_mm[], decay_fit_rms_np, fc_crossing_ghz, fc_ratio, evanescent_mean, propagating_mean, mesh_res_mm,

輸入參數a_mmb_mmnrtseps_rgap_mmn_freqproblemtimeoutlength_mmradius_mmworkspacef_stop_ghz另有 6 個

fsi_plate_deflection

FSI Plate Deflection

Exact small-deflection tip/centre deflection of a uniform-pressure-loaded thin plate strip (NO solver) — the closed-form twin the coupled OpenFOAM→CalculiX FSI solve (`fsi_pressure_plate_submit`) is gated against. The wetted strip is `width_mm`×`length_mm`; the fluid pressure `pressure_pa` (Pa) acts normal to it, giving the line load q = pressure·width. Section is the solid rectangle I = width·thickness³/12 unless an explicit `i_mm4` is given. E from `youngs_gpa` or a Materials-DB `material`. `support`='cantilever' (clamped one edge): δ_tip = q·L⁴/(8·E·I), root moment q·L²/2, reaction q·L; 'clamped- clamped': centre δ = q·L⁴/(384·E·I), reaction q·L/2 each. Valid while δ ≲ thickness (small-deflection); past that escalate to an NLGEOM follower-pressure ccx solve. Returns {support, pressure_pa, line_load_n_per_mm, total_load_n, I_mm4, tip_disp_mm, root_moment_nmm, reaction_n, max_stress_mpa, youngs_mpa, slenderness, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數i_mm4supportmaterialwidth_mm必填length_mm必填workspaceyoungs_gpapressure_pa必填thickness_mm必填

fsi_interface_balance

FSI Interface Balance

Partitioned wet-interface force balance (NO solver) — the FSI analogue of the optics energy-balance gate. The fluid presses a uniform `pressure_pa` over the `length_mm`×`width_mm` strip, so the total traction is F = pressure·area; a converged partitioned solve must hand the solid exactly this load and the solid's support reactions must carry it (Newton's third law across the coupling surface). Pass the measured `fluid_force_n` (∮p·dA over the OpenFOAM wet patch) and/or `solid_reaction_n` (Σ ccx reaction at the clamp); the relative residual |F_fluid − R_solid|/F_fluid is the conservation error. With neither supplied it returns the reference analytic load (residual 0) the real solve closes on. Returns {area_mm2, reference_load_n, fluid_force_n, solid_reaction_n, residual_n, relative_residual, balanced, fidelity, escalate_to}.

輸入參數width_mm必填length_mm必填workspacepressure_pa必填fluid_force_nsolid_reaction_n

fsi_channel_pressure

FSI Channel Pressure

Fully-developed plane-channel pressure drop Δp = 12·μ·U·L/h² (NO solver) — the *fluid* load that physically sources the pressure-loaded-plate FSI anchor. For laminar flow between parallel plates a gap `gap_mm` apart with mean velocity `velocity_m_s` over length `length_mm`, the exact plane-Poiseuille wall pressure drop feeds `fsi_plate_deflection`/`fsi_interface_balance` as `pressure_pa`. The Reynolds number Re = ρ·U·h/μ flags when the laminar (exact) assumption holds (Re ≲ 1400). Default fluid is water at 20 °C (μ=1e-3 Pa·s, ρ=1000). Returns {pressure_pa, pressure_drop_pa, reynolds, regime, velocity_m_s, wall_shear_pa, fidelity, valid_range_ok, warnings, escalate_to}.

輸入參數gap_mm必填mu_pa_slength_mm必填rho_kg_m3workspacevelocity_m_s必填

fsi_pressure_plate_submit

FSI Pressure Plate Submit

Partitioned fluid-structure-interaction solve on the preCICE OpenFOAM↔ CalculiX stack, asynchronous (OFF the MCP channel) — the real coupled-field twin of the analytic `fsi_plate_deflection` / `fsi_interface_balance` oracles. A flexible flap clamped at a channel floor deflects under the flow: OpenFOAM (pimpleFoam) writes the wet-interface Force, ccx_preCICE returns the Displacement, and preCICE drives the implicit coupling to convergence each time window. preCICE is LGPL-3.0 and the two heavy solvers run ONLY as subprocesses; degrades to {ok:false, reason, install, stack} when the stack is absent (build via scripts/install-solvers.sh fsi). Physics knobs: `inlet_velocity_m_s`, `nu_m2_s`, `rho_kg_m3` (fluid), `youngs_pa`/`poisson`/`density_kg_m3` (solid), `end_time_s`/`time_window_s`/ `max_iterations` (coupling). Geometry/mesh come from the validated vendored template (no FreeCAD touch). Returns the degradation dict, or {job_id, status, cache_hit}; poll `job_result` for {ok, time_windows, tip_disp_m, tip_history, coupling_converged, case_dir} — the tip displacement is the field the `fsi_plate_deflection` oracle gates.

輸入參數nu_m2_spoissontimeoutrho_kg_m3workspaceyoungs_paend_time_sdensity_kg_m3time_window_smax_iterationsinlet_velocity_m_s

molding_screen

Molding Screen

Injection-molding screen (NO solver): one-term cooling time (exact given α, t_cool = s²/(π²α)·ln(8·(T_melt−T_mold)/(π²·(T_eject−T_mold))) — the t ∝ s² design lever) + the spiral-flow fill check (fill_ok when flow_length ≤ (L/t-limit)·wall — chart correlation, ±30 %). `material` picks per-polymer defaults (ABS | PP | PC | PA66 | POM | HDPE | PS), each individually overridable; with all temps + alpha_mm2_s explicit no material is needed. A cooling-only call returns fidelity='exact'; adding flow_length_mm makes the headline answer fidelity='correlation', band_pct=30 (cooling stays exact). When a flow_length_mm is given the headline check is a chart correlation, so escalate_to='molding_fill_submit' (the openInjMoldSim VOF fill solve, #105); a cooling-only call needs no solver and returns escalate_to=None. Returns {material, wall_thickness_mm, t_melt_c, t_mold_c, t_eject_c, alpha_mm2_s, cooling_time_s, flow_length_mm, flow_ratio, flow_ratio_limit, fill_ok, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數materialt_melt_ct_mold_ct_eject_cworkspacealpha_mm2_sflow_length_mmflow_ratio_limitwall_thickness_mm必填

moldability_screen

Moldability Screen

Moldability DFx screen (NO solver, NO geometry) — fast analytic gate combining two checks molders reason about first: (1) WALL-THICKNESS QUALITY — is the nominal wall in the resin's recommended moldable band, and is the section uniform enough (uniformity_ratio = t_max/t_min; warn >2, fail >3) to avoid sink/warp; thick-lobe samples (> sink_factor·nominal, k≈1.5) flagged; cooling tied to the thickest wall (t ∝ s²). (2) SHRINKAGE — first-order from the resin CTE: S_linear = alpha·ΔT, S_vol ≈ 3·S_linear, cavity_scale_factor = 1/(1−S_linear); semicrystalline resins (PP/PE/PA/POM/PLA/HDPE/LDPE) flag model_underpredicts and carry a published_shrinkage_pct. Pass ``wall_samples`` (local wall thicknesses, mm) and/or ``nominal_mm``, plus ``material``. Degrades gracefully when the corpus lacks the (issue #106) recommended-wall / mold-shrinkage / crystallinity fields. Low-fidelity gate: escalate_to='molding_fill_submit'. Returns {thickness:{…}, shrinkage:{…}, material, pass, score, fidelity, band_pct, warnings, escalate_to}.

輸入參數materialworkspacefail_rationominal_mmwarn_ratioalpha_per_ksink_factort_ambient_ct_solidify_cwall_samples

drop_impact

Drop Impact

Drop/impact screen by exact energy balance (NO solver). Give crush_distance_mm (available cushion/crumple stroke) -> deceleration, OR deceleration_limit_g (fragility spec) -> required stroke — exactly one. G_avg = h/d exactly (mass cancels); g_peak = pulse_factor·G_avg with `pulse` bounding the shape: 'constant' (ideal crush, 1×) | 'linear_spring' (elastic, 2×) | 'half_sine' (π/2×). v = √(2gh). mass_g only adds peak_force_n and energy_j. fidelity='exact'; where the part is stressed, or what an edge/corner strike changes, is escalate_to='impact_dynamics_submit'. Returns {drop_height_mm, impact_velocity_m_s, pulse, pulse_factor, crush_distance_mm, g_avg, g_peak, pulse_duration_ms, deceleration_limit_g, required_crush_mm, energy_j, peak_force_n, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數pulsemass_gworkspacedrop_height_mm必填crush_distance_mmdeceleration_limit_g

bar_impact

Bar Impact

St-Venant bar impact, exact (NO solver): a uniform bar striking a rigid wall end-on. Face stress σ = ρ·c₀·v₀ with c₀ = √(E/ρ) — mass, area and length cancel, so only a slower strike or a softer/lighter material lowers it; contact lasts 2L/c₀ and the bar leaves stress-free at the strike speed (restitution 1). Past the yield velocity v_y = σ_y/(ρ·c₀) the face yields and a bilinear material (`tangent_mpa`) caps at σ_y + ρ·c_p·(v₀ − v_y), c_p = √(E_t/ρ). E and ρ from `youngs_gpa`/`density_kg_m3` or a Materials-DB `material` (which also supplies yield). `area_mm2` adds force_n. The closed-form twin impact_dynamics_submit is gated against; also the quick ceiling on what any strike at this speed can do to this material. Uniaxial-stress (slender bar) theory. Returns {wave_speed_m_s, stress_mpa, elastic_stress_mpa, force_n, contact_duration_ms, rebound_velocity_m_s, yield_velocity_m_s, plastic, plastic_wave_speed_m_s, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數area_mm2materiallength_mm必填workspaceyield_mpayoungs_gpatangent_mpavelocity_m_s必填density_kg_m3

impact_dynamics_submit

Impact Dynamics Submit

Transient DROP / IMPACT dynamics — the solve `drop_impact` escalates to. Meshes `body`, flies it at `velocity_m_s` (or the free-fall speed of `drop_height_mm` — exactly one) along `direction` into a fixed rigid floor through penalty contact, and integrates the motion with CalculiX `*DYNAMIC`. Async: returns a job — poll job_result. Degrades to {ok:false, reason, install} when ccx is absent. `direction` is the way the part TRAVELS: '-z' (default), '+x', … or any 3-vector — [-1,-1,-1] is a corner drop (the floor turns, the part is not re-meshed). Material from `material` or explicit `youngs_mpa`/`poisson`/`density_kg_m3` (required, no defaults — the answer scales with √(E·ρ)). `yield_mpa` arms the stress criterion; `plastic=True` (+ `tangent_mpa`, bilinear) lets it yield instead. `deceleration_limit_g` arms the fragility criterion. `method`: 'implicit' (default; HHT-α, ccx steps adaptively — right for ms-scale drops) | 'explicit' (central difference at the stable step — for stress-wave events ≲ 100 µs; first-order tets). `time_step_s` on an implicit run switches to a FIXED step: several times faster where contact comes on smoothly, but ccx stops if an increment diverges (a flat face landing all at once does). `duration_s` defaults to free flight + 10 wave round trips along the drop — enough for a stiff body; a compliant one needs more, and the gate says so. `samples` is the single output cadence ccx allows (force history AND stress frames; default sized to the mesh). `contact`: 'auto' (default) | 'face' | 'node' — measured: ccx's face-to-face penalty never engages a corner strike (the part falls through) and its node-to-face one locks up on a broad flat landing, so 'auto' uses face contact down to a 45° edge and node contact for anything sharper. `engagement_lag_mm` reports how far the strike point sank before the contact pushed back. Validated against the exact St-Venant

輸入參數body必填gap_mmmethodcontactgravityplasticpoissonsamplesmaterialdirectionworkspaceyield_mpa另有 11 個

thermal_lumped

Thermal Lumped

Lumped first-order transient warm-up (no mesh). ΔT_ss = P/(h·A), τ = m·c_p/(h·A), T(t) = T_amb + ΔT_ss·(1−e^(−t/τ)). c_p is an explicit value/quantity-string ('900 J/kg/K') or read from `material`. Get h_conv from the `h_estimate` correlation screen rather than guessing. With duration_s the temperature + fraction-of-steady reached are returned. A radiation screen flags when the steady-state radiative HTC exceeds h_conv. Returns {t_ambient_c, delta_t_steady_k, t_steady_c, time_constant_s, t_final_c, reached_steady_pct, h_rad_w_m2k, radiation_significant}.

輸入參數c_ph_conv必填mass_g必填power_w必填area_mm2必填materialworkspaceduration_semissivityt_ambient_c

thermal_transient_1d

Thermal Transient 1D

Analytic 1-D plane-wall transient conduction (one-term Heisler series), valid for Fourier ≳ 0.2 — the closed-form transient the Elmer thermal_transient solve is gated against, and the *distributed* (spatial-gradient) answer the lumped screen only approximates. A wall of half-thickness L cools/heats toward ambient by surface convection: Bi = h·L/k, Fo = α·t/L², α = k/(ρ·cₚ). Pass `alpha_m2_s`, or k+rho+cp, or a `material` (Materials DB: thermal_conductivity/Density/specific_heat); get h_conv from the `h_estimate` correlation screen rather than guessing. As Bi→0 the body is isothermal and this collapses to the lumped exponential exp(−t/τ) (cross-checked via t_center_lumped_c / lumped_agrees). Returns {biot, fourier, eigenvalue_1, c1, t_center_c, t_surface_c, t_center_lumped_c, time_constant_s, one_term_valid, lumped_agrees}.

輸入參數kcprhoh_conv必填materialworkspacealpha_m2_sduration_s必填t_ambient_ct_initial_chalf_thickness_mm必填

thermal_transient_submit

Thermal Transient Submit

Transient thermal FEM via Elmer, asynchronous. Requires ElmerSolver (apt `elmerfem-csc` / conda); when absent this returns {ok:false, reason, install} rather than raising. Three modes: - **Build the analytic-slab case** (no solver case prep needed): pass the plane-wall transient — `half_thickness_mm`, `h_conv` (W/m²K), `duration_s`, and either `k`+`rho`+`cp` (SI) or a `material` name, with optional `t_initial_c` / `t_ambient_c` and mesh/step counts `n_elements` / `n_steps`. The handler writes the 1-D conduction case (symmetry at the centre, convection at the surface), runs ElmerSolver, and returns the centre/surface temperatures — the same plane-wall BVP `thermal_transient_1d` solves analytically, so the two are directly comparable (the kickoff's relative gate). - **Solve a real FreeCAD solid — the geometry bridge**: pass a `body` handle plus `convection_faces` (1-based indices into the solid's faces; those faces get the `h_conv`/`t_ambient_c` convective BC, every other face is adiabatic), the physics (`h_conv`, `duration_s`, `k`+`rho`+`cp` or `material`), and an optional `char_length_mm` Gmsh element size and `element_order` ('1st'|'2nd'). The solid is Gmsh-meshed and solved as a true 3-D body (ElmerGrid + ElmerSolver); the result's {t_max_c, t_min_c} are the interior/convective-surface temperatures (for a slab-like body, directly gateable against thermal_transient_1d). Prefer `element_order='2nd'` for a sharp transient — quadratic tets resolve the wall gradient accurately even on a coarse mesh. - **Run a prepared `case_dir`** containing its own `.sif` + mesh. Returns the degradation dict, or {job_id, status, cache_hit}; poll job_result for {ok, returncode, solver, case_dir, stdout_tail} plus, for the slab case, {t_center_c, t_surface_c, n_steps_written}, for a body {t_max_c, t_min_c, nodes, tets} (or {scalars_final} for a prepared c

輸入參數kcprhosifbodyh_convn_stepscase_dirmaterialworkspaceduration_sn_elements另有 6 個

thermal_radiation_submit

Thermal Radiation Submit

Diffuse-gray radiation FEM via Elmer, asynchronous — the radiation sibling of thermal_transient_submit. Requires ElmerSolver + the ViewFactors binary (apt `elmerfem-csc` / conda); when absent this returns {ok:false, reason, install} rather than raising. Two modes: - **Build the two-plate enclosure case** (no case prep): pass `t1_c`, `t2_c` (°C) and the two surface emissivities `emissivity_1`/`emissivity_2` (default 0.8). The handler writes a 2-D pair of parallel plates radiating across an unmeshed vacuum gap, runs ViewFactors then ElmerSolver, and extracts the net radiative exchange — directly gated against the exact two infinite parallel plates oracle q = σ(T₁⁴−T₂⁴)/(1/ε₁+1/ε₂−1) (`oracle_ratio` ≈ 1). Mesh/geometry knobs: `width_m`, `gap_m`, `plate_thickness_m`, `n_x`, `k_plate`. - **Run a prepared `case_dir`** containing its `.sif` + mesh (ViewFactors is run first when no factor file is present). Returns the degradation dict, or {job_id, status, cache_hit}; poll job_result for {ok, returncode, solver, case_dir, stdout_tail} plus, for the plate case, {flux_w_m2, q_net_w, two_plate_flux_w_m2, oracle_ratio, t1_c, t2_c, emissivity_1, emissivity_2} (or {scalars_final} for a prepared case).

輸入參數n_xsift1_ct2_cgap_mk_platewidth_mcase_dirworkspaceemissivity_1emissivity_2plate_thickness_m

thermal_composite_wall

Thermal Composite Wall

Exact series thermal-resistance network of a plane composite wall (NO solver) — the classic overall-U calculation and the conjugate-heat-transfer family's closed-form oracle. `layers` is the in→out list of solid layers, each {thickness_mm, k | material} (k in W/m·K, or a Materials-DB name); `h_in`/`h_out` are optional convection film coefficients (W/m²K). Per unit area R = 1/h_in + Σ tᵢ/kᵢ + 1/h_out, U = 1/R, q = U·(t_in − t_out), and every surface/interface temperature follows exactly. Returns {u_w_m2k, r_total_m2k_w, q_w_m2, q_w, layer_resistances_m2k_w, interface_temps_c (inner surface → outer surface), t_in_c, t_out_c, area_m2}.

輸入參數h_inh_outlayers必填t_in_c必填area_m2t_out_c必填workspace

cht_channel_submit

CHT Channel Submit

Conjugate heat transfer via Elmer (P3 M6 frontier), asynchronous — ONE solve spanning a plug-flow fluid channel AND a conducting solid wall coupled at their shared interface. Requires ElmerSolver; when absent this returns {ok:false, reason, install} rather than raising. Builds the two-body channel (constant outer `flux_w_m2`, inlet Dirichlet `t_in_c`, all else adiabatic) whose gates are exact WITHOUT a Nusselt correlation: the outlet bulk temperature follows the energy balance q″·L = ṁ·c_p·ΔT and the solid-layer drop is q″·t/k. Defaults are the live-validated water channel (cell Péclet ≈ 9 — the builder rejects > 25, where stabilized advection visibly leaks the energy balance). Also accepts a prepared `case_dir`. Returns the degradation dict or {job_id, status, cache_hit}; poll job_result for {ok, t_outlet_mean_c, t_out_exact_c, energy_balance_ratio (≈1, ±3%), dt_solid_k, dt_solid_exact_k, solid_drop_ratio (≈1), pe_cell, case_dir}.

輸入參數nxsift_in_ck_fluidk_solidcase_dircp_fluidlength_mny_fluidny_solidflux_w_m2rho_fluid另有 4 個

cht_graetz_submit

CHT Graetz Submit

Flow-coupled Graetz channel via Elmer (SIMULATION_NEXT B4), asynchronous — the TRUE Nusselt validation that upgrades cht_channel_submit's plug flow: FlowSolve computes the real laminar profile and HeatSolver rides on it (Convection = Computed) between two isothermal walls. Requires ElmerSolver; when absent this returns {ok:false, reason, install} rather than raising. Two gates: the solved parabola's u_max/u_mean ≡ 3/2 exactly, and the developed mixing-cup decay d ln(T_wall−T_bulk)/dx fitted over the second half of the channel yields Nu, gated against the Graetz eigenvalue Nu_T = 7.5407 (parallel plates, constant wall temperature) — a slug profile would give π² = 9.87, so the gate also proves the profile coupling is real. This closes the loop with the `h_estimate` correlation screen. The writer polices Re < 400, development lengths inside the first 45 %, and cell Péclet ≤ 25. Also accepts a prepared `case_dir`. Returns the degradation dict or {job_id, status, cache_hit}; poll job_result for {ok, u_max_over_mean (≈1.5), nu_fit, nu_exact, nu_ratio (≈1, ±10 %), nu_slug, reynolds, prandtl, pe_cell, case_dir}.

輸入參數nxnysifgap_mt_in_ck_fluidcase_dircp_fluidlength_mmu_fluidt_wall_crho_fluid另有 2 個

acoustic_fem_submit

Acoustic FEM Submit

Acoustic FEM via Elmer HelmholtzSolve (SIMULATION_NEXT Tier B1), asynchronous — the higher-order twin of `acoustic_screen`, gated against its exact closed forms. Requires ElmerSolver; when absent this returns {ok:false, reason, install} rather than raising. kind='duct': a closed duct driven p=1 at x=0, rigid at x=L, at f = kL·c/(2πL) (keep `kl` off the quarter-wave resonances) — the rigid-end pressure has the exact oracle 1/cos(kL), so `p_end_ratio` ≈ 1 machine-tight. kind='cavity': a rigid lx_m × ly_m cavity excited by a corner Wave Flux source, swept ±span_pct% around the exact (mode_nx, mode_ny) eigenfrequency in n_steps Scanning steps; the in-phase corner-probe response flips sign through resonance, and the 1/A zero-crossing gives `f_solved_hz` with `mode_ratio` ≈ 1 (<0.1%). Also accepts a prepared `case_dir`. Returns the degradation dict or {job_id, status, cache_hit}; poll job_result for duct {ok, p_end_re, p_end_exact, p_end_ratio (≈1), p_mean_ratio, frequency_hz, case_dir} | cavity {ok, f_solved_hz, f_exact_hz, mode_ratio (≈1), mode, case_dir}.

輸入參數klnxnysifkindlx_mly_mc_m_smode_nxmode_nyn_stepscase_dir另有 4 個

harmonic_response

Harmonic Response

Exact SDOF harmonic frequency response (NO solver) — the FRF screen and the oracle the Elmer `harmonic_response_submit` sweep is gated against. Bridges `beam_modal` (f_n) and `random_vibration` (Q = 1/(2ζ)): with r = f/f_n, |H| = 1/√((1−r²)²+(2ζr)²), phase = atan2(2ζr, 1−r²), peak amplification Q = 1/(2ζ√(1−ζ²)) at f_peak = f_n·√(1−2ζ²), half-power bandwidth ≈ 2ζ·f_n. With `frequency_hz` the response at that drive is returned; `static_deflection_mm` scales it to an absolute amplitude_mm. fidelity='exact'; ζ ≥ 1/√2 has no peak (flagged). Escalate to `harmonic_response_submit` for a real meshed FRF (multi-mode, geometry-true). Returns {natural_frequency_hz, damping_ratio, q_factor, f_peak_hz, half_power_bandwidth_hz, frequency_ratio, amplification, phase_deg, amplitude_mm, fidelity, band_pct, valid_range_ok, warnings, escalate_to}.

輸入參數workspacefrequency_hzdamping_ratio必填natural_frequency_hz必填static_deflection_mm

harmonic_response_submit

Harmonic Response Submit

Harmonic forced response (FRF) via Elmer StressSolve Harmonic Analysis (SIMULATION_NEXT Tier B2), asynchronous — a plane-stress cantilever driven by a harmonic tip traction, swept one quasi-static point + n_sweep points across ±span_pct% of its first resonance, with Rayleigh β tuned to `damping_ratio` at f₁. Requires ElmerSolver; when absent this returns {ok:false, reason, install} rather than raising. Three gates from the in-phase (real) response: `f1_ratio` — Re(H) = 0 exactly AT resonance, so the swept tip response's sign-flip locates f₁ vs the Euler-Bernoulli `beam_modal` closed form (within ~2%, plane-stress vs beam theory); `static_ratio` — the quasi-static point vs the exact tip compliance F·L³/(3EI) (within ~5%); `q_ratio` — max|Re|/static vs Q/2 = 1/(4ζ), the exact SDOF light-damping identity (within ~15%, sweep-sampled). Cross-links `harmonic_response` (the SDOF oracle) and `random_vibration` (same Q). Also accepts a prepared `case_dir`. Returns the degradation dict or {job_id, status, cache_hit}; poll job_result for {ok, f1_solved_hz, f1_eb_hz, f1_ratio, static_solved_m, static_exact_m, static_ratio, peak_over_static, q_factor, q_ratio, frf:[[f_hz, tip_re_m]], case_dir}.

輸入參數nxnysifn_sweeppoissoncase_dirheight_mlength_mspan_pctworkspaceyoungs_patraction_pa另有 2 個

版本歷史

3
  1. v0.5.7最新Oct 30, 2026
  2. v0.5.6Sep 23, 2026
  3. v0.5.5Sep 16, 2026

評分

已滿足適用檢查項的 90%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分
  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具必需15 分
  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分