AnkusDrive
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| ANKUSDRIVE_CONFIG | No | Override the path to the persistent config file (default: ~/.config/ankusdrive/config.toml or %APPDATA%\ankusdrive\config.toml). | |
| ANKUSDRIVE_ELMER_PATH | No | Path to the Elmer solver executable, used by thermal/CHT simulation families. | |
| ANKUSDRIVE_FREECADCMD | No | Override the auto-discovered path to the freecadcmd binary. | |
| ANKUSDRIVE_MAX_WORKSPACES | No | Maximum number of named workspaces in the workspace pool. | 4 |
| ANKUSDRIVE_OPENFOAM_BASHRC | No | Path to the OpenFOAM etc/bashrc to source, used by CFD/FSI/injection-molding simulation families. | |
| ANKUSDRIVE_WORKSPACE_IDLE_S | No | Idle timeout in seconds before an unused workspace is reaped. | 900 |
| ANKUSDRIVE_OPTICS_GPL_PYTHON | No | Path to a Python interpreter that can import the GPL KrakenOS optics engine for non-sequential tracing. |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| pingA | Check that the FreeCAD worker is alive. Returns 'pong' on success. |
| restart_workerA | 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: , freecad: [...]}. |
| use_workspaceA | 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. This is how concurrent agents share one MCP server without clobbering each other's documents: each agent calls use_workspace with a unique name once, up front. 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: , freecad: [...], workspaces: [names]}. |
| list_workspacesA | 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. Returns {current, max, idle_reap_s, workspaces: [...]}. |
| close_workspaceA | Shut down a workspace's worker and free its pool slot. All of that workspace's documents, unsaved changes, and handles are lost. Closing the workspace you are currently in returns you to the "default" workspace. The default workspace can be closed too (its worker respawns clean on next use). Returns {closed: , workspace: , current: }. |
| versionA | Return FreeCAD and bundled Python versions from the worker. |
| new_documentB | Create a new FreeCAD document and make it active. Returns {doc: }. |
| open_documentA | Open an existing .FCStd file, make it active. Returns doc name and object list. |
| save_documentA | 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. |
| list_objectsA | List objects in the active document. Returns [{name, type, label}, ...]. |
| add_primitiveA | 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}: |
| add_gearA | 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. |
| add_rackA | 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.25module, tooth height = 2.25module, 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 = modulepi),
module, teeth, tooth_height (2.25module mm), length (teethmodulepi mm)}.
A spur gear MESHES with this rack when their |
| add_sprocketA | 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 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 |
| add_pulleyA | 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. |
| add_springA | 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 = Gd^4 / (8D^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). |
| add_fastenerA | 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".
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. |
| add_bearingA | 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:
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; |
| oring_grooveA | 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_section0.75 (~25% squeeze, clamped to a 20-30% band), groove_width = cross_section1.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_outer_diameter
sizes the radial space the groove occupies. |
| chamfer_edgesA | Chamfer (bevel) specific edges of a shaped Part object — the direct-shape counterpart to fillet_edges. handle: handle of the object to chamfer (e.g. 'box_1', a boolean result). edges: non-empty list of edge references. Each may be a tag ('e_...' from list_edges, preferred), an 'EdgeN' string, or a bare 1-based integer index. size: symmetric chamfer leg distance in mm (applied equally to both faces meeting at the edge, i.e. dist1 = dist2 = size). Must be > 0. Default 1.0. name: label for the resulting feature object. Default 'Chamfer'. per_edge: add the edges one at a time, validating after each, instead of in a single apply. Slower; for geometry known to be blend-hostile. allow_partial: accept a partial result instead of aborting. Off by default. The base object is hidden (consumed into the chamfer feature). Validated exactly like fillet_edges (issue #283) — Shape.isValid(), unchanged solid count, no growth of the tight bounding box — before a handle is issued. Returns {handle, name, volume (mm^3), edges (resolved 1-based indices actually chamfered), 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 from the document and BlendCheckFailed is raised naming the offending edges — never a handle to corrupt geometry. |
| shell_solidA | Hollow a raw Part solid into a thin-walled shell (the direct-shape
counterpart to handle: handle of the solid to hollow (e.g. a box/cylinder from add_primitive, or any shaped Part::Feature). faces: NON-EMPTY list of the faces to REMOVE — these become the shell's openings. Each entry is a face tag (f_..., from list_faces/query_faces, preferred and edit-stable), a 'FaceN' string, or a 1-based integer index. thickness: wall thickness in mm, must be > 0. The wall is grown INWARD, so the part's outer dimensions are preserved. The consumed input solid is hidden (its geometry now lives in the shell). Returns {handle (starts 'shell_'), name, volume (mm^3 of the resulting walls), wall_thickness (mm), removed_faces (list of 1-based face indices that were opened)}. Raises if faces is empty, thickness <= 0, an index is out of range, or the offset is too large to produce a valid shell. |
| add_threadA | Generate a REAL helical ISO-style 60-degree thread as a static solid. Unlike 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, orderable, catalog}. Studding is bought by the bar and cut, so any
length up to the longest stock bar reads as |
| engrave_textA | Engrave (cut) or emboss (add) extruded text onto a planar face of a solid. The text is rendered in a system TrueType font, extruded, laid flat on the chosen face centred on its centroid, then booleaned into the host solid. handle: the host solid to mark. face: the planar face to put the text on — a stable f_* tag (preferred), a 'FaceN' index string, or an int. Must be a flat (planar) face. Get a tag from list_faces / query_faces. text: the string to render (non-empty). size: cap height of the text in mm (default 5.0). depth: extrusion/engraving depth in mm (default 0.5). Engrave recesses the text this far below the surface; emboss raises it this far above. mode: 'engrave' (default) cuts the text into the solid (removes material); 'emboss' fuses raised text onto the surface (adds material). position: optional [u, v] in-face offset in mm from the face centroid, along the text's local X (u) and Y (v) axes. Omit to centre on the face. font: optional absolute path to a .ttf/.ttc font file. If omitted, common macOS fonts are auto-probed (Arial, then Helvetica). If none is found and none is supplied, the call raises RuntimeError — pass an explicit path. name: label for the resulting solid (default 'Text'). Returns {handle, name, volume, text, mode, depth} where volume is the mm^3 of the resulting solid (less than the input for engrave, more for emboss). The host solid is consumed/hidden and replaced by the returned handle. |
| add_ribA | Add a reinforcing rib/web inside a PartDesign Body by thickening an OPEN sketch profile into a wall that fuses with the body's surrounding material. Args: body: handle of the PartDesign Body (from make_body) to add the rib to. sketch: handle of a sketch holding an OPEN spine (a single line, arc, or connected polyline) that defines where the rib runs. Must NOT be a closed loop. The sketch's attachment plane sets the rib's orientation. thickness: rib wall thickness in mm (> 0). midplane: if True (default) the wall is centered on the spine, growing thickness/2 to each side; if False it grows from one side. reversed: flip the extrusion sense (use if the rib lands on the wrong side of its sketch plane). name: object label. Returns a dict: {handle (starts 'rib_'), name, volume (the whole Body's Shape.Volume in mm^3 after the rib — strictly greater than before the rib, since a rib only adds material), thickness}. Fallback behaviour the caller should know: FreeCAD's native PartDesign::Rib type is unavailable in AnkusDrive's headless runtime, so the rib is built as an equivalent midplane PartDesign::Pad — the open spine is offset by +/-thickness/2 into a closed footprint and padded across the body so it reaches the surrounding walls. For the usual straight or smoothly-curved spine this matches a Rib; very intricate spines may differ from the native tool. Raises ValueError if the profile is closed/empty/degenerate or thickness <= 0, and RuntimeError if the rib adds no material (spine does not span between walls). |
| transformA | Move and/or rotate an existing object in place — first-class replacement for hand-poking an object's Placement via set_property. handle: object to move (any object with a Placement: primitive, body, feature). translate: [x, y, z] translation in mm (default no translation). rotate_axis: rotation axis as a 3-vector [x, y, z] (need not be unit length; default [0, 0, 1], the Z axis). angle: rotation about rotate_axis in DEGREES (default 0 = no rotation). relative: True (default) composes this move ONTO the object's current placement (incremental); False sets it as the ABSOLUTE placement, discarding the object's prior placement. The same object is moved — NO new handle is created. The rotation is applied about the object's local origin (combine with translate to pivot elsewhere). Returns {handle, name, placement: {base:[x,y,z] mm, axis:[x,y,z], angle_deg}} describing the object's resulting placement. |
| scale_shapeA | 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 |
| copy_shapeA | 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 '_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. |
| measure_distanceA | Minimum distance between two entities, in mm. The workhorse measurement tool: lets a blind agent verify gaps, clearances, and contact. Args:
a: handle of the first object.
b: handle of the second object.
a_ref: optional sub-shape selector on Measures the minimum (closest-approach) distance, so distance_mm = 0 means the two entities touch or interpenetrate. This does NOT report overlap volume -- use min_clearance / interference_check for penetration depth. Returns a dict (no handle; this is a measurement): distance_mm: float -- minimum gap in mm (0.0 when touching/intersecting). point_on_a: [x, y, z] mm -- closest point on a (or its sub-shape). point_on_b: [x, y, z] mm -- closest point on b (or its sub-shape). touching: bool -- True when distance_mm < 1e-7. |
| measure_angleA | 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:
Units: degrees. Returns:
|
| bounding_boxA | Axis-aligned bounding box (AABB) of a shaped object. All lengths in mm, in world coordinates. This is a measurement — it returns numbers, not a new object, and does not modify the model. KNOWN QUIRK (issue #284): min/max/size are FreeCAD/OCC's ANALYTIC box, which is an UPPER bound, not the true extent. OCC boxes a trimmed face using its untrimmed carrier surface, so a planar cut through fillets, chamfers, lofts or a sphere can report several mm of material that is not there — a real case had a trim plane at X=-32.0 reported as X=-36.7. Before you conclude a part is the wrong size, check "verified" (and pass tight=True): the analytic box over-estimating a correct part looks exactly like a wrong part. handle: the object to measure. oriented: if True, also compute the tightest box at any orientation (the oriented bounding box, OBB) and return it under "oriented"; if the build can't compute it, "oriented" is null. Default False. This is about ORIENTATION, not tightness — it comes from the same analytic geometry and inherits the same over-estimate. tight: if True, also tessellate the shape and return the mesh-derived box under "tight" — the trustworthy numbers when the analytic box over-estimates. Opt-in because tessellation is not free (~1.4s on a 200mm plate with 60 filleted holes). Default False. deflection: mesh chord tolerance in mm for tight=True. Default diagonal/2000 (floor 0.001mm); larger is coarser and faster. Returns a dict:
min [x,y,z] mm — lower corner of the analytic AABB (upper bound)
max [x,y,z] mm — upper corner of the analytic AABB (upper bound)
size [x,y,z] mm — extents (max - min) along X, Y, Z
center [x,y,z] mm — AABB center point
diagonal float mm — space-diagonal length of the AABB
oriented null, or {size:[x,y,z] mm, center:[x,y,z] mm, diagonal: mm} when
oriented=True and supported — the minimum-volume box at the
shape's best orientation (size is its three edge lengths).
verified how far min/max above can be trusted:
"exact" — proven tight (the shape's own vertices reach all
six faces of the analytic box).
"mesh_agrees" — tight=True found no disagreement beyond the mesh
tolerance.
"unverified" — unproven, the usual verdict on a curved part.
Treat min/max/size as an upper bound only, and
re-run with tight=True to measure.
"over_estimate" — tight=True proved the analytic box overshoots.
Use "tight"; min/max/size are wrong-big.
tight null unless tight=True, else {min, max, size, center, diagonal,
deflection, triangles} measured off the mesh. Accurate to about
|
| min_clearanceA | Closest approach between two solids — the measured gap, richer than the
binary interference_check. 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 |
| check_shapeA | Check a shaped object's geometry validity and topology before you build on it. Inspection only — measures, returns no handle, mutates nothing, and does NOT auto-repair. Use it as a guard after booleans/sweeps/imports to confirm you have one clean watertight solid. Note: a watertight solid can still have a BLOCKED or LEAKY enclosed-flow path — watertightness says the shell is closed, not that an internal channel is unobstructed and leak-free. For ducts/manifolds/adapters use check_airtight_path(inlet, outlet) to verify the flow path. handle: the object to inspect. Returns a dict (volumes in mm3): valid (bool) OCC topology/geometry is sound watertight_solid (bool) exactly one solid AND valid AND closed — the 'safe to keep building' verdict shape_type (str) e.g. 'Solid', 'Shell', 'Compound', 'Wire' closed (bool) no free boundary edges solids (int) number of solids (want 1 for a part) shells (int) number of shells faces (int) number of faces edges (int) number of edges volume_mm3 (float) total volume (0 for open/2D shapes) is_null (bool) the shape is empty check (str) present only when valid is False — diagnostics were printed to the worker log check_error (str) present only if the diagnostic pass itself raised |
| check_airtight_pathA | 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 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 |
| classify_face_sidesA | 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 |
| section_viewA | Cut a solid with a plane and return the cross-section it exposes. This is the best way to "see inside" a part blind: it measures the cut area and its extent, and can optionally emit the section outline as a new object for rendering/export. Units: mm (lengths), mm^2 (areas). handle: the solid to slice (a AnkusDrive handle). plane: "XY", "XZ", or "YZ" (world datum planes) OR a datum-plane handle. World normals follow FreeCAD: XY -> +Z, XZ -> -Y, YZ -> +X. A datum handle uses its local +Z as the cutting normal. offset: shift of the cutting plane along its normal, in mm (default 0 = the plane through the world origin / datum origin). E.g. plane="XY", offset=10 cuts at z=10. emit_profile: when True, add a Part::Feature holding the section wires to the document, register it, and return its handle (raises if the plane misses the shape). Default False = measure only, no new geometry. name: object name for the emitted profile (only used when emit_profile=True). Does not modify the input geometry. Returns a dict: plane: str (echoed), offset_mm: float (echoed), normal: [x, y, z] unit cutting-plane normal, section_area_mm2: float — total area of the closed cross-section wires, wire_count: int — number of section wires found (0 means the plane misses the shape), closed_wire_count: int — how many of those wires are closed, bbox: {min:[x,y,z], max:[x,y,z], size:[dx,dy,dz]} of the section, or None when the plane misses the shape, handle: str — handle of the emitted profile (ONLY when emit_profile=True), name: str — its FreeCAD object name (ONLY when emit_profile=True). |
| list_facesB | 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. |
| list_edgesA | 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. |
| query_facesB | Filter faces by a structured predicate. Returns matching descriptors. Predicate fields (all optional, ANDed):
Example: {"type": "planar", "normal_dir": [0, 0, 1], "centroid_max": "z"} finds the topmost +Z-facing face. |
| resolve_faceA | 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). |
| resolve_edgeC | Resolve an edge tag to the current EdgeN index. Errors on miss or ambiguity. |
| make_bodyB | Create a PartDesign Body. Subsequent sketches/features go inside it. Returns {handle, name}. |
| make_datum_planeB | 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. |
| make_sketchB | 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}. |
| add_sketch_geometryA | Append geometric primitives to a sketch. items is a list of dicts:
|
| add_sketch_constraintB | 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. |
| close_sketchD | Recompute and report DOF status. Returns {geometry_count, constraint_count, open_vertices, fully_constrained}. |
| padC | Pad a sketch by |
| pocketA | Subtract a pad of through ('wall'|'body'): preferred over through_all. 'wall' ray-casts the
body to find the first exit boundary and cuts exactly one wall thick —
correct for solids (one wall = full thickness) AND shelled bodies. 'body'
is the legacy ThroughAll; on a shelled body it punches through every wall
and ruins the cavity. Implies direction='into_body'. Result carries
wall_depth_mm so the caller can verify.
direction (preferred over |
| revolveC | Revolve a sketch around a body origin axis ('X'|'Y'|'Z') by |
| partdesign_filletA | PartDesign Fillet on edges of a feature in a Body. 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. |
| partdesign_chamferA | PartDesign Chamfer on edges of a feature in a Body. 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. |
| holeA | Drill a parametric Hole from a sketch (one or more circles). sketch: handle of a sketch placed on a face of an existing body feature.
depth_type: 'Dimension' (use |
| list_thread_optionsA | 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: [...]}. |
| linear_patternC | 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. |
| polar_patternC | 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. |
| mirroredB | 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. |
| loftB | 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. |
| sweepA | 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'. |
| helixA | 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. |
| thicknessA | 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'. |
| draftB | 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. |
| list_documentsA | List all open documents: [{name, label, file_path, dirty, active, object_count}, ...]. |
| set_active_documentA | Switch the active document by name (the value returned from new_document/open_document). |
| close_documentA | 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}. |
| transaction_openA | 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. |
| transaction_commitA | Commit the most recent open transaction; changes are kept. |
| transaction_abortA | Roll back the most recent open transaction. Implementation note:
FreeCAD 1.1 headless |
| add_sketch_externalA | 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). |
| get_objectC | Dump a handle's properties + shape stats. Useful when no dedicated tool exposes what you need. |
| set_propertyB | Set a single property by name on an object. Coerces lists → Vector for Vector properties; other values pass through. |
| verify_featureA | 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 |
| set_visibilityA | 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. |
| fillet_edgesA | Fillet edges of a shaped Part object. Every result is validated before a handle comes back (issue #283): this OCC build's fillet is edge- and order-sensitive enough to produce corrupt geometry with no exception at all — a 20 mm cube filleted on all 12 edges at r=11 returns one solid with a LARGER volume and a 13 mm larger bounding box. The checks are Shape.isValid(), an unchanged solid count, and no growth of the tight bounding box (a fillet only removes or holds the envelope). per_edge: skip the single-shot apply and add the edges one at a time, validating after each. Slower (one recompute per edge); for geometry already known to be blend-hostile. allow_partial: accept a partial result instead of aborting. Off by default. Returns {handle, name, volume (mm^3), edges (the 1-based indices actually filleted), checks {valid, solids, envelope_ok, envelope_growth_mm, envelope_tol_mm}, mode ('batch' | 'per_edge'), partial}. When partial is True the reply also carries skipped_edges and a warnings entry saying the solid is NOT the part that was asked for. On failure the handler retries per edge to find the culprits, removes the failed feature from the document and raises BlendCheckFailed naming the offending edges and the subset that does fillet cleanly. It never returns a handle to corrupt geometry, and never quietly drops a fillet unless allow_partial was asked for. |
| boolean_opA | Boolean operation on two existing objects, referenced by their handles. op: 'cut' (base minus tool), 'fuse' (union), or 'common' (intersection). base, tool: handles returned from add_primitive (e.g. 'box_1', 'cylinder_1'). strict: raise instead of warning on a degenerate cut (see below). Returns {handle, volume, removed_volume, volume_ratio}, plus |
| export_shapeB | 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 first shaped object in the active document. |
| run_scriptA | Escape hatch: execute Python in the worker with App/Part/ObjectsFem in scope. Set auto_register (default True): any new shape-bearing object the script
creates is automatically registered into the handle table. The result
includes a Returns {result, registered}. |
| register_handleA | 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}. |
| mass_propertiesA | 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. |
| make_assemblyB | Create an App::Part container to hold linked parts. Returns {handle, name}. |
| add_partA | Add a part to an assembly via App::Link. source is one of: {"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": "", "parent": "<link-name|handle>", "parent_iface": ""}. Frames come from publish_interface. |
| list_assembly_partsB | List parts of an assembly: name, type, linked-target name, position, volume. |
| interference_checkA | Pairwise interference: compute volume of intersection between every pair of parts. Returns [{a, b, interference_mm3}, ...] descending by volume. Empty list = no interference. |
| bom_extractA | 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 ( |
| standard_part_designateA | 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 |
| designation_checkA | 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 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. |
| catalog_searchA | Browse the off-the-shelf catalog: which standard components actually EXIST, in which sizes, in which stocked lengths. Call it while designing, before you commit geometry to a number — it is the difference between a design somebody can build and one that needs a special. The dimensional corpora (threads/bearings/stock) tell you what a part measures. This one tells you whether it is a thing you can buy. Nothing else in AnkusDrive knows that an ISO 4762 M4×12 is a stocked item and an M4×13 is not. Every argument is an optional filter: family screw | set_screw | nut | washer | retaining_ring | pin | bearing | oring | threaded_rod standard a product standard or alias — "ISO 4762" or "DIN 912" kind the product's kind tag (socket_head_cap_screw, nyloc_nut, ...) size thread designation, nominal mm, shaft/bore mm, or AS568 dash length an exact stocked length, or a min_length/max_length window (mm) grade property class / material ("8.8", "A2-70", "NBR70") drive hex_socket | hex limit max rows (default 50) Offline, deterministic, zero network. Returns {ok, count, truncated, items, standards, fidelity, captured, market,
not_covered}. Each item is {standard, name, family, kind, drive, size, size_kind,
lengths (the stocked ladder, narrowed to any length filter), length_count,
grades, length_measured, designation?}. Availability is a curated snapshot of a
MARKET (see |
| catalog_nearestA | 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}. |
| catalog_checkA | Is this exact part something you can buy off the shelf? Pass a canonical 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 —
|
| envelope_checkA | 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. |
| publish_interfaceA | 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}. |
| annotate_faceA | 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)}. |
| list_face_rolesA | 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) |
| declare_intentA | 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. |
| verify_intentA | 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 |
| verify_contractA | 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: / feature: / intent. |
| component_contract_checkA | 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 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: (published + sane + in tol), performance_spec: (declared as briefed) and performance: (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 Returns {handle, ok, checks:[{check, passed, detail}], reasons:[...],
skipped:[{check, reason}], performance?} — ok True iff every check passed (skips
never move it); reasons is the failing checks' details. A part that declares no
performance contract gets no performance rows, empty |
| interface_align_checkA | 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. |
| validate_manifestA | 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 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. |
| merge_assemblyA | 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": {"": {"file": "rel/part.FCStd", "object": ""?, "envelope": {"min":[...],"max":[...]}?}}, "instances": [{"component":"", "name":""?, "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 |
| assembly_lockA | 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 .lock.json. Returns {lockfile, components}. |
| assembly_lock_checkA | 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}. |
| make_drawing_pageC | Create a TechDraw page using a built-in A4 landscape template by default. template: optional absolute path to a .svg template. |
| add_projection_groupB | Add a multi-view projection group of |
| export_drawingB | 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}. |
| add_dimensionA | Add dimension(s) to a drawing page. Modes (pick one):
|
| add_annotationA | 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}. |
| add_feature_noteA | 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.
|
| set_title_blockA | Populate the drawing's title block. FreeCAD's default template is a bare sheet, so AnkusDrive composes its own block in the bottom-right corner on SVG/PDF export. Scale, sheet size, units, and part name are auto-derived from the page; the fields here override or add to them (a machinist needs material + scale + units to cut from the sheet). Calling this opts the page into rendering the block. Returns {handle, name, fields}. |
| drawing_gateA | Manufacturing-completeness gate for a drawing page: does the placed dimension set fully and non-redundantly reconstruct the part? A green render is not a manufacturable drawing — this validates the drawing itself, the way the geometry-realizes-declaration gate validates an assembly. Reads the real solid + the placed dimensions and accounts degrees of freedom, process-aware: a 'prismatic' (milled/plate) part must locate each hole by X/Y from a datum and size the block W×H×T; a 'turned' part is concentric, so a step needs only Ø + axial length. process='auto' infers it from the geometry. Returns {ok, violations, slots_total, slots_covered, process, features,
dimensions, enumerated_features, datum_faces, section_recommended}.
Datum-origin discipline turns on automatically when the part has faces annotated role='datum' (annotate_face): a location dimension not measured from a datum face is then flagged no_datum. Set datums_declared=True to force the check on even without annotated datums. require_ballooned=True additionally demands that every characteristic carries an
inspection balloon (see balloon_drawing) — the requirement a release flow imposes
when the drawing must ship with an inspection plan. Unballooned characteristics
become |
| fit_pageA | 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. |
| drawing_legibilityA | 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 |
| add_gdt_calloutA | Place a GD&T feature control frame on a drawing page. Declaring geometric tolerance ON THE DRAWING (rather than only checking a measurement with gdt_check) is what makes it inspectable: the frame renders as a real compartmented symbol, and inspection_plan / fai_report read it back as a characteristic with its own balloon and measurement method. control: an ASME Y14.5 geometric characteristic — the same vocabulary gdt_check
accepts: flatness, straightness, circularity, cylindricity, profile_line,
profile_surface, perpendicularity, parallelism, angularity, position,
concentricity, runout, total_runout.
zone: tolerance zone in mm (rendered with a Ø for the diametral controls —
position, concentricity, circularity, cylindricity).
datums: the ordered datum reference frame, e.g. ["A", "B", "C"]. A control with
datums is CMM work; a datum-free form control is surface-plate work, and the
inspection plan picks the instrument accordingly.
feature: optionally the enumerated feature id (drawing_gate's
Returns {handle, name, control, zone, datums, text}. |
| balloon_drawingA | Number every characteristic on a drawing page with an inspection balloon — each dimension, feature control frame, and feature note gets a numbered circle beside it, rendered on SVG/PDF export. This is the print a quality engineer actually works from, and the key inspection_plan and fai_report row against. Balloon numbers are an IDENTITY, not an ordinal. They are persisted on the FreeCAD objects, so: re-running on an unchanged page reassigns nothing; adding a dimension appends the next number rather than renumbering the print; and a deleted dimension RETIRES its number instead of passing it to a different feature — an inspection record written against balloon 7 can never come to mean something else. Pass renumber=True to deliberately discard the numbering and start from 1 (which invalidates any inspection record already written). Call it after the dimensions are placed and fit_page has run. Returns
{count, balloons, assigned, kept, retired, next_balloon}; |
| inspection_planA | The characteristic list for a drawing page as data: every dimension, feature control frame, and feature note, ballooned, with nominal, limits, and a suggested measurement method per row. The method follows the tolerance rather than a guess: the gauge-maker's Returns {ok, characteristics, count, by_method, unmeasurable, retired,
next_balloon, fidelity, band_pct, basis}. ok=False means a characteristic cannot
be inspected as drawn — an untoleranced size the inspector has no limits to
accept or reject against (code no_tolerance), or a band finer than any instrument
on its ladder (code no_instrument) — with |
| fai_reportA | First-article inspection report for a drawing page, shaped like AS9102 Rev B Form 3. Each ballooned characteristic becomes a row carrying the AS9102 fields (Char No. / Reference Location / Characteristic Designator / Requirement / Results / Designed-Qualified Tooling / Nonconformance Number / Notes) plus its limits, the suggested measurement method, and a computed status. results: balloon number -> measured value; each row is then accepted or rejected
against its limits. For a THIS IS NOT A CERTIFIED AS9102 SUBMISSION — it reproduces the Form 3 field layout so a real form can be filled from it, and says so on every artifact it writes. Returns {ok, columns, rows, summary, disclaimer, part, rev, plan_ok, unmeasurable, path?, size?, format?}; ok=False means at least one characteristic measured out of limits. |
| add_thumbnailA | Place a small isometric pictorial of the part in the top-right corner of the sheet — the "glance" reference a machinist uses to grok the 3-D shape before reading the orthographic views — IF it fits there without crowding the existing views and dimensions. It is a real TechDraw isometric projection rendered through the same path as the other views (a vector line drawing, not a raster), scaled to fit a reserved top-right box and pinned to that corner (fit_page leaves it put, and it is never dimensioned or counted by the manufacturability gate). Best-effort: when the top-right corner is already occupied it returns {placed: False, reason} rather than overlapping content. Call it AFTER placing the views and dimensions (and after fit_page) so "fits" is judged against the final layout. Returns {placed, box, scale?, view?, reason?}. |
| add_section_viewA | Add a cross-section view when the part has internal features the outline / hidden-line views convey ambiguously — a counterbore, a blind hole/bore, or a pocket. The need is judged automatically from the real solid (the same feature enumeration the manufacturability gate uses); the cut runs lengthwise through such a feature so its bore profile and depth read directly, and the view is placed in clear space beside the existing views. auto (default True): add the section ONLY if the part actually has hidden internal geometry; otherwise return {added: False, recommended: False}. Set auto=False to force a section regardless. process: 'auto' (default) | 'prismatic' | 'turned' — how features are enumerated. Returns {added, recommended, reasons, feature_ids, view?, normal?, origin?}.
(drawing_gate also reports |
| render_viewB | 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}. |
| render_viewsB | Render multiple views of a single object. Returns {views: {view_name: {png_base64,...}}}. Default views: ['iso', 'top', 'front']. |
| render_fem_resultsA | 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}. |
| render_photorealA | Photorealistic render of a shaped object via the FreeCAD Render workbench (an external renderer, e.g. POV-Ray) — a presentation-quality "nice picture", unlike render_view's fast software-rasterized preview. Requires the Render addon and a renderer binary to be installed (see docs/RENDER_WORKBENCH.md); raises with install guidance otherwise. Renders in an isolated temporary document, so the live model is never modified. view: 'iso' | 'top' | 'bottom' | 'front' | 'back' | 'left' | 'right' | 'side'. material: optional Render material library card — e.g. 'Gold', 'Glass', 'Aluminium', 'GlossyPlastic', 'RoughPlastic', 'Iron', 'Brass'. Omitted gives a neutral default material; an unknown name raises with the full list. Returns {png_base64, png_path, renderer, view, material, width, height}. Presentation-only: output is not bit-reproducible, so it is kept out of the reliability/golden tests. External renders can take seconds to minutes, so this call uses an extended worker timeout. |
| render_photoreal_submitA | Start a photorealistic render asynchronously; returns immediately with {job_id, status} instead of blocking for the whole render. Use this (rather than render_photoreal) for renders that may take a long time — heavy materials/renderers, large images — so the worker stays responsive. The external renderer runs in the background; poll render_job(job_id) until status is 'done' (then it returns the PNG) or 'failed'. Same arguments as render_photoreal; requires the Render addon + a renderer binary (see docs/RENDER_WORKBENCH.md). |
| render_jobA | 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. |
| render_capabilitiesA | Report which photoreal renderers are usable right now, and whether the FreeCAD Render addon imports — so you can pick a working renderer for render_photoreal instead of discovering availability by trial and error. Takes no arguments. Resolves each renderer's binary exactly as render_photoreal would (ANKUSDRIVE__PATH env override -> FreeCAD prefs -> PATH -> per-OS install dirs), but renders nothing and changes no settings. Returns {addon_importable (bool), default_renderer ('Povray'), platform,
available (sorted list of ready renderer names for the |
| solve_capabilitiesA | 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__PATH env -> PATH -> per-OS install dirs; a pip-wheel solver by importability. It executes nothing and installs nothing.
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 |
| setup_statusA | The machine-readable form of Read-only and side-effect-free: nothing is executed or installed and no
environment is mutated ( 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), solvers: {available, unwired, prepared_case_only, solvers:
{name: {..., install_hint | wire_hint}}, families: {family: {solvers, available,
unwired, prepared_case_only, any_available}}, extras}} —
|
| fem_new_analysisC | Create a Fem::FemAnalysis container. Returns {handle, name}. |
| fem_set_solverB | 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. |
| fem_set_materialB | 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. |
| fem_add_constraintC | 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. |
| fem_modalA | 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. |
| fem_modal_resultsB | Extract natural frequencies from a completed modal run. Returns {frequencies_hz: [...], modes: [{mode, frequency_hz, max_displacement_mm}, ...]}. |
| contact_setupA | 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). 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}. |
| fem_set_nonlinear_materialA | Attach an elastoplastic ( Give the post-yield curve either as Returns {handle, name, hardening, yield_points, n_points, solver_material_nonlinear, solver_geometric_nonlinear, ramp_increments}. |
| fem_bucklingC | 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. |
| fem_buckling_resultsB | Extract buckling load multipliers from a completed buckling run. Returns {buckling_factors: [...], modes: [{mode, factor}, ...]}. |
| fem_thermal_resultsA | Extract temperature field summary from a completed thermal run. Returns {temperatures_c: {min, max, mean}, top_n_hot_nodes: [...]}. |
| fem_mesh_refinementA | 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. |
| fem_meshB | Create a Gmsh mesh of char_length: max characteristic element length in mm. 0 = let Gmsh pick. 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}. |
| fem_runB | Run the CalculiX solver on an analysis. Blocks until the solve finishes.
|
| fem_resultsB | Extract summary results from an analysis. Returns {max_vonmises_mpa, max_displacement_mm, max_displacement_vector, top_stress_nodes: [{node, vonmises_mpa, displacement_mm}, ...]}. |
| fem_result_probeA | Probe FEM results at a specific location, instead of only the global
max + top-N that Pick exactly one mode:
field: 'auto' (default — every field present in the result) | 'vonmises' | 'displacement' | 'temperature'. 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?:{...}}. |
| fem_cantilever_demoA | 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. |
| material_getA | 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. |
| material_selectA | Ashby-style selection: filter the corpus, then rank survivors. criteria keys are min_/max_ (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. |
| material_listA | List available materials, optionally filtered to one category ('aluminum' | 'steel' | 'titanium' | 'magnesium' | 'polymer' | 'glass'). Returns {count, category, materials:[{name, category}, ...]} sorted by name. |
| fluid_propsA | Thermophysical properties of a fluid at (T, P) from CoolProp's equation of
state (issue #100). |
| bolted_joint_checkA | 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=KFd (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}. |
| bearing_lifeA | 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). |
| spring_checkA | 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 |
| gear_ratingA | Rate spur-gear tooth bending (Lewis): sigma=Ft/(bmY). 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);
|
| belt_driveB | 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}. |
| press_fit_stressA | 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
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}. |
| seal_checkA | 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}. |
| chain_driveA | Rate an ANSI roller-chain drive (ASME B29.1). Rated at the lower of the
link-plate-fatigue (HP1=0.004N1^1.08n1^0.9P^(3-0.07P), low speed) and
roller-impact (HP2=1000KrN1^1.5P^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}. |
| weld_groupA | 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=Tr/J from the
eccentric moment about the weld centroid, added vectorially at the worst end.
required_leg = f_r/(0.707allowable); 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}. |
| tolerance_stackupA | 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
|
| fit_checkA | 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). |
| fit_classA | 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). |
| gdt_checkA | 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}. |
| fatigue_checkA | 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 The S-N line runs from (1e3, |
| fracture_checkA | 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. |
| wear_estimateA | 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}. |
| creep_flagA | 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}. |
| h_estimateA | Screening convection coefficient h (NO solver) — the honest h_conv to feed
thermal_lumped / thermal_transient_1d / a convection BC, instead of a guess.
This is a focusing estimate, not a gate: fidelity='correlation' with band_pct
the literature scatter (±15–20 %). Escalate to the conjugate solve
|
| acoustic_screenA | Closed-form acoustics screen (NO solver). 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}. |
| plate_checkA | Handbook bending of a uniformly loaded flat plate (NO solver) — the
"do I need FEM at all?" screen. 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}. |
| beam_bucklingA | Exact column-buckling screen (Euler + Johnson, NO solver) — the closed-form
twin the CalculiX 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}. |
| plastic_collapseA | Exact plastic-hinge collapse of a solid rectangular beam (NO solver) — the
closed-form twin the perfectly-plastic CalculiX solve
( 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}. |
| elastica_deflectionA | 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 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}. |
| hertz_contactA | Exact Hertzian point-contact peak pressure (NO solver) — the screening twin
of a frictional CONTACT PAIR solve. Sphere of 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}. |
| laminate_propertiesA | 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
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 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, thermal_curvature_per_mm, radius_of_curvature_mm, timoshenko_curvature_per_mm, applied_force_n, applied_moment_nmm, kappa_applied_per_mm, axial_strain, layer_stresses, first_ply, fidelity, band_pct, valid_range_ok, warnings, escalate_to}. |
| monopole_sphereA | Exact pulsating (monopole) sphere radiated power + far-field pressure (NO
solver) — the closed-form twin the Bempp exterior-acoustics BEM radiation solve
( 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}. |
| rigid_sphere_scatteringA | 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 ( 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}. |
| acoustic_radiation_submitA | Exterior-acoustics boundary-element solve on Bempp, asynchronous (OFF the MCP
channel) — the real-field twin of the analytic
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). |
| waveguide_cutoffA | Exact rectangular-waveguide cutoff frequency (NO solver) — the closed-form
twin the openEMS FDTD full-wave solve ( 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, guided_wavelength_mm, fidelity, band_pct, valid_range_ok, warnings, escalate_to}. |
| dipole_resonanceA | Thin centre-fed half-wave dipole first resonance (NO solver, banded) — the
closed-form twin the openEMS FDTD S11 antenna sweep ( 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}. |
| em_fullwave_submitA | Full-wave FDTD EM solve on openEMS, asynchronous (OFF the MCP channel) — the
real-field twin of the analytic
Returns the degradation dict, or {job_id, status, cache_hit}; poll job_result for {ok, fc_analytic_ghz, freq_ghz[], s21_db[], transmission_norm[], fc_crossing_ghz, fc_ratio, evanescent_mean, propagating_mean, n_cells, wall_s} (waveguide) or {freq_ghz[], s11_db[], resonance_ghz} (dipole). |
| fsi_plate_deflectionA | 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 ( 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}. |
| fsi_interface_balanceA | Partitioned wet-interface force balance (NO solver) — the FSI analogue of
the optics energy-balance gate. The fluid presses a uniform Returns {area_mm2, reference_load_n, fluid_force_n, solid_reaction_n, residual_n, relative_residual, balanced, fidelity, escalate_to}. |
| fsi_channel_pressureA | 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 Returns {pressure_pa, pressure_drop_pa, reynolds, regime, velocity_m_s, wall_shear_pa, fidelity, valid_range_ok, warnings, escalate_to}. |
| fsi_pressure_plate_submitA | Partitioned fluid-structure-interaction solve on the preCICE OpenFOAM↔
CalculiX stack, asynchronous (OFF the MCP channel) — the real coupled-field
twin of the analytic Physics knobs: |
| molding_screenA | 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 %). 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}. |
| moldability_screenA | 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 Returns {thickness:{…}, shrinkage:{…}, material, pass, score, fidelity, band_pct, warnings, escalate_to}. |
| drop_impactA | 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 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}. |
| thermal_lumpedA | 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 |
| thermal_transient_1dA | 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 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}. |
| thermal_transient_submitA | Transient thermal FEM via Elmer, asynchronous. Requires ElmerSolver (apt
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 case). |
| thermal_radiation_submitA | Diffuse-gray radiation FEM via Elmer, asynchronous — the radiation sibling of
thermal_transient_submit. Requires ElmerSolver + the ViewFactors binary (apt
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). |
| thermal_composite_wallA | 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. 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}. |
| cht_channel_submitA | 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 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}. |
| cht_graetz_submitA | 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 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}. |
| acoustic_fem_submitA | Acoustic FEM via Elmer HelmholtzSolve (SIMULATION_NEXT Tier B1),
asynchronous — the higher-order twin of kind='duct': a closed duct driven p=1 at x=0, rigid at x=L, at f = kL·c/(2πL)
(keep 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}. |
| harmonic_responseA | Exact SDOF harmonic frequency response (NO solver) — the FRF screen and the
oracle the Elmer 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}. |
| harmonic_response_submitA | 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 Three gates from the in-phase (real) response: 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}. |
| em_skin_depthA | Exact AC skin depth (NO solver) — δ = √(2/(ω·μ₀·μ_r·σ)) plus the per-square
surface resistance R_s = 1/(σ·δ); fields/current decay e^(−x/δ) into the
conductor (~95% of induction heating deposits within 1.5·δ). σ from an explicit
Returns {skin_depth_m, skin_depth_mm, surface_resistance_ohm, angular_frequency_rad_s, conductivity_s_m, mu_r}. |
| em_dc_resistanceA | Exact DC resistance of a uniform conductor (NO solver) — R = L/(σ·A), the
closed-form anchor the Elmer em_conduction_submit gate reproduces to machine
precision. With Returns {resistance_ohm, conductivity_s_m, length_m, area_m2, current_a?, joule_w?}. |
| em_fieldA | Exact magnetostatic field of the two canonical sources (NO solver):
Returns {b_t, b_mt, …} (the field in tesla and millitesla). |
| em_conduction_submitA | DC current conduction via Elmer's StatCurrentSolver (P3 M6 frontier),
asynchronous. Requires ElmerSolver; when absent this returns {ok:false, reason,
install} rather than raising. Builds a rectangular strip with Returns the degradation dict or {job_id, status, cache_hit}; poll job_result for {ok, current_a, joule_w, effective_resistance_ohm, resistance_exact_ohm, current_exact_a, resistance_ratio, case_dir}. |
| em_induction_submitA | AC skin effect / induction via Elmer's harmonic 2-D magnetodynamics (P3 M6
frontier), asynchronous. Requires ElmerSolver; when absent this returns
{ok:false, reason, install} rather than raising. Builds a conductor slab
Returns the degradation dict or {job_id, status, cache_hit}; poll job_result for {ok, skin_depth_exact_m, decay_length_m, phase_length_m, decay_ratio, phase_ratio, case_dir}. |
| em_induction_heating_submitA | Coupled induction heating via Elmer (SIMULATION_NEXT B5), asynchronous —
completes Two gates: joule_power_ratio — the solved eddy-current power vs the exact
deep-slab dissipation P″ = R_s·|H₀|²/2 = ω²σA₀²δ/4 (from the shipped
Returns the degradation dict or {job_id, status, cache_hit}; poll job_result for {ok, eddy_power_w_m, p_total_exact_w_m, joule_power_ratio (≈1), t_mean_final_k, dt_mean_exact_k, energy_balance_ratio (≈1), skin_depth_m, case_dir}. |
| cfd_pipe_flowA | Analytic straight-pipe pressure drop (NO solver) — the fast internal-flow screen
and the exact gate the OpenFOAM cfd_internal_flow solve is checked against. Laminar
(Re<2300) is Hagen–Poiseuille Δp = 128·μ·L·Q/(π·D⁴) with its D⁴ scaling — exact;
turbulent uses smooth-pipe Blasius, or Colebrook–White when Returns {reynolds, regime, velocity_m_s, flow_rate_m3_s, friction_factor, colebrook_friction_factor, relative_roughness, pressure_drop_pa, wall_shear_pa, hagen_poiseuille_pa, laminar, fidelity, band_pct, escalate_to}. |
| cfd_internal_flow_submitA | Internal-flow CFD (pressure drop) via OpenFOAM or SU2, asynchronous. Requires an
OpenFOAM (apt/conda) or SU2 binary; when none resolves this returns {ok:false,
reason, install} rather than raising. The pipe and geometry-bridge modes need
OpenFOAM specifically — they emit OpenFOAM dictionaries — but
Returns the degradation dict, or {job_id, status, cache_hit}; poll job_result. Pipe/body cases: {ok, returncode, pressure_drop_pa (developed), pressure_drop_inlet_pa, hagen_poiseuille_pa?, hp_ratio?, n_cells, case_dir}; RANS pipe adds {dpdx_pa_m, dpdx_colebrook_pa_m, colebrook_ratio, y_plus_estimate, band_pct}. Prepared case: {ok, returncode, solver, application, case_dir, kind, stdout_tail}. |
| declare_performanceA | Record a quantitative PERFORMANCE spec on a part so it can be re-proved after every edit — the performance twin of declare_intent, which only covers geometry. Persists in the .FCStd as a JSON property bag; one contract per part, re-declaring replaces it. Each requirement is metric-agnostic — the contract layer only orchestrates, so the metric is whatever the named tool already returns, and "Cd ≤ 0.30", "Δp ≤ 50 Pa", "first mode ≥ 200 Hz" and "ΔT ≤ 40 K" are the same machinery:
Returns {handle, contract: {requirements: [...]}, n_requirements}. Raises ValueError on a malformed requirement, naming the offending one. |
| verify_performanceA | Prove (or fail to prove) every requirement declared with declare_performance — the step that turns "a solver printed 0.29" into a claim with a band and a provenance. A verdict has THREE states.
Trust is part of the measurement, not a footnote: Solver-tier measurements are asynchronous, so this returns EITHER the finished verdict (screen-only, or everything already decided) or {job_id, status, pending, results} — poll job_result for the completed verdict. Never raises on a failing requirement; a failure is a row. Every verdict is also RECORDED on the part, stamped with a geometry signature of
the shape it measured (#261). That record is what merge_assembly,
substitutability_check and component_contract_check consult, since a gate has to
answer synchronously and this may not have: an in-flight solve records rows the
gates read as Returns {handle, tier, ok, n_requirements, passed, failed, indeterminate, escalate, results: [{name, tier, metric, state, measured, limit, band_pct, worst_case, best_case, margin, margin_pct, detail, trust_reasons?, screen?, job_id?}]}. |
| study_submitA | Sweep parameters over a sampled design space and record the WHOLE search as a table — the DOE primitive between the parametric layer and the solver catalog. Without it, exploring a design space means hand-rolling
Caching IS resumability: identical points hash to the same job content key, so
re-submitting a study after a crash, or widening its grid, re-runs only what is new —
Returns EITHER the finished table or {job_id, status, points, pending}; poll
job_result for the completed table. A point that failed to build or measure is a row
with |
| optimize_submitA | Vary parameters until the spec is met, then say whether it was PROVEN — the step that closes the design-to-spec loop. Everything else in this family measures; this searches. Two things make it different from a generic minimizer, and both come from the performance-contract layer underneath it: A constraint verdict has three states. Convergence is not proof. A simplex can settle on a point that clears its limit
by 2 % while its own grid-convergence band is 5 % wide — noise with a favourable
sign.
The search is a bounded Nelder-Mead — derivative-free because there is no adjoint
through a CFD solve — so every evaluation is a real measurement. Shape optimization. Pass a Geometry-driven candidates are built on the MAIN thread through the worker's
work queue, because FreeCAD's document API is not thread-safe and this search runs
as a background job. That queue is drained once per incoming request, so a shape
search only advances while you are polling Returns {job_id, status}; poll job_result for {ok, proven, stop_reason, best_params, best_value, objective: {name, metric, sense, value, band_pct}, constraints: [{name, state, measured, limit, band_pct, margin, margin_pct, detail, trust_reasons?}], phases: [{tier, n_evals, best_params, best_value, converged, reason}], history: [{i, tier, params, value, score, feasible, cached}], n_evals, n_cached, budget, variables, warnings}. |
| grid_convergenceA | Grid Convergence Index — how much of a solved number is the MESH (no solver, milliseconds). Give it the same quantity solved on 2-3 systematically refined meshes, FINEST FIRST, and it fits the observed order of convergence, Richardson-extrapolates to zero cell size, and returns the percentage band inside which the mesh-independent answer lies. Roache's GCI as codified in ASME V&V 20. This is the honest Describe the meshes with either Watch three fields before quoting the band: Returns {n_levels, values, cell_sizes, refinement_ratios, observed_order, order_used, order_clamped, extrapolated_value, gci_pct, gci_coarse_pct, band_pct, relative_error_pct, monotonic, asymptotic_ratio, safety_factor, converged_fit, fidelity, warnings}. |
| cfd_mesh_independence_submitA | Solve the same CFD case at 2-3 refined meshes and report the Grid Convergence Index — asynchronous, one job for the whole ladder. Requires OpenFOAM; degrades to {ok:false, reason, install} when it does not resolve. Answers the question no single solve can: is this number a property of the flow, or of the mesh? On geometry with no analytic twin that band is the only error bar available, and it is what turns "a solver produced 0.31" into "0.31 ± 2 %". Two families, dispatched like their single-solve twins:
The COARSEST level is the mesh a plain submit would have built and the study refines
from there (the tunnel's default cell is already the coarsest that resolves the body
at all). Cost therefore grows as the cube of Returns the degradation dict, or {job_id, status, cache_hit}; poll job_result for {ok, family, metric, levels: [{label, value, cell_size_m, n_cells, converged, returncode, case_dir}], grid_convergence: {observed_order, extrapolated_value, gci_pct, monotonic, asymptotic_ratio, order_clamped, warnings, ...}, band_pct, extrapolated_value, hagen_poiseuille_pa (pipe family), warnings}. |
| cfd_body_dragA | Analytic EXTERNAL-flow drag screen (NO solver, milliseconds) — the external twin of cfd_pipe_flow, and the banded oracle the wind-tunnel solve cfd_external_flow_submit(model=…) is checked against. Use this FIRST to narrow a design space; escalate to the solve only for the shapes that survive. Three families:
Fidelity: sphere/cylinder are correlations with band_pct 10/15; the table is band_pct 20 and only valid for Re ≈ 1e4–1e6 on a shape that genuinely matches. Returns {cd, drag_force_n, frontal_area_m2, dynamic_pressure_pa, velocity_m_s, fidelity, band_pct, escalate_to} plus {reynolds, regime, valid_range_ok, warnings} for sphere/cylinder — or {shapes: {name: cd}} for shape='list'. |
| cfd_external_flow_submitA | External-flow CFD (drag/lift) via OpenFOAM or SU2, asynchronous. Requires an
OpenFOAM (apt/conda) or SU2 binary; when none resolves this returns {ok:false,
reason, install} rather than raising. The two case-BUILDING modes below need
OpenFOAM specifically — they emit OpenFOAM dictionaries; SU2 only ever runs a
Returns the degradation dict, or {job_id, status, cache_hit}; poll job_result. Body mode: {ok, returncode, cd, cl, cm, drag_force_n, drag_pressure_n, drag_viscous_n, lift_force_n, force_total_n, moment_total_nm, force_drift_pct, n_force_samples, reynolds, reference_length_m, frontal_area_m2, frontal_area_source, moment_reference_m (the bbox centre moments are taken about, not the global origin), blockage_ratio, base_cell_m, converged, gated, warnings, case_dir}. Flat plate: {ok, returncode, reynolds_l, cd, cf_solved, cf_blasius, blasius_ratio, drag_force_n, drag_momentum_n, drag_blasius_n, n_cells, case_dir}; RANS plate swaps the gate fields for {cf_solved (momentum), cf_mixed_ref, cf_mixed_ratio, cf_turbulent_ref, cf_wall_corrected, y_plus_estimate, band_pct}. Prepared case: {ok, returncode, solver, application, case_dir, kind, stdout_tail}. |
| molding_fill_submitA | Injection-molding FILL (+ optional PACK/COOL) solve, asynchronous — the higher-
fidelity twin
Backend: when the openInjMoldSim binary resolves (GPL-3.0, a modified
compressibleInterFoam on OpenFOAM-7 .org — Cross-WLF + 2-domain Tait), the worker
generates and runs an OF7-org case from the params below (or runs a prepared
openInjMoldSim (OF7) params: Packing/cooling (openInjMoldSim only): set Net mold shrinkage (the cavity-sizing number; issue #116) — distinct from
pack_gate's raw-PVT densification. The fill_pack result also models the packing-feed
make-up (melt fed at the hold pressure until the gate freezes at the Tait no-flow
transition; only the uncompensated post-gate-freeze densification is net shrinkage)
and gates it against the resin's published linear band. Asymmetric per-wall cooling (#134): set Drive the interFoam path with cavity + process params:
Returns the degradation dict, or {job_id, status, cache_hit}; poll job_result. interFoam result: {ok, returncode, backend, case_dir, expected_fill_time_s, flow_length_ratio, fill:{filled_fraction, filled_cell_fraction, front_x_frac, last_to_fill_x_frac, max_pressure_pa}, gate:{pass, score, fidelity:"solve", band_pct, short_shot, fill_time_s, max_pressure_pa, pressure_ok, warnings}}. |
| molding_warpage_submitA | Injection-molding WARPAGE / residual distortion, asynchronous — the FEM
thermo-elastic post-step of the cooling solve (GitHub issue #113 Part B; the
higher-fidelity twin of the #104 CTE shrinkage screen). Answers will the part bow
out of flat once it cools and is ejected. Requires the The physics: a moulding shrinks as it cools (CTE); uniform shrinkage just makes
it smaller, but differential shrinkage warps it. The dominant driver is the
asymmetric frozen-in through-thickness temperature field at ejection (an unbalanced
cooling layout, one mould half hotter, a rib on one face). Pass that as
Backend: CalculiX ( Coupled cooling hand-off (issue #116): instead of hand-passing Params: Fidelity caveat: a one-way, linear-elastic, loose coupling — it ignores
viscoelastic stress relaxation, flow-induced anisotropy, and the packing-pressure
residual; it captures the dominant differential-shrinkage warp and its direction,
not a calibrated absolute. Returns the degradation dict, or {job_id, status, cache_hit}; poll job_result for {ok, returncode, solver, case_dir, nodes, tets, warp_axis, span_mm, thickness_mm, dT_through_k, dT_source, analytic_bow_mm, gate} where gate is {pass (on flatness), score, fidelity:"solve", band_pct, max_warp_mm, flatness_tol_mm, warp_per_span, max_disp_mm, warp_faithful, analytic_bow_mm, warnings}. |
| random_vibrationA | Random-vibration response off a modal run (Miles' equation; closed-form, no
external solver). Provide either Returns {rms_g, first_mode_hz, dominant_mode_hz, q, psd_band_hz, miles_grms_g, modes: [{mode, frequency_hz, psd_g2_hz, contribution_g, in_band}], rms_stress_mpa, three_sigma_stress_mpa, pass}. |
| beam_modalA | Exact Euler-Bernoulli natural frequencies of a uniform rectangular beam — the
closed-form modal oracle (no solver), and the band the CalculiX Returns {boundary, n_modes, frequencies_hz, beta_l, first_mode_hz, youngs_gpa, density_kg_m3, area_mm2, I_mm4, slenderness}. |
| dfm_checkA | Screen a part for manufacturability against a pull/tool axis. Give a
hand-built Sheet metal: a Returns {process, pull_axis, min_wall_mm, draft_violations, undercut_faces,
min_wall_violations, score, pass} — plus n_faces + wall_thickness_stats on the
handle path, and a |
| optics_raytraceA | Ray-trace a bundle through a dielectric optical model with rayoptics
(asynchronous-free; needs no FreeCAD geometry). Requires the rayoptics wheel
(the
Returns the degradation dict, or {ok, backend:'rayoptics', rayoptics_version, n_rays, n1, n2, critical_angle_deg, efficiency, leakage_fraction, absorbed_fraction, tir_fraction, energy_balance, oracle_max_dev_deg, exit_distribution:[{angle_deg,intensity}], hotspot_locations}. |
| optics_lens_designA | First-order + spot analysis of a SEQUENTIAL optical system with optiland
(MIT, in-process). Requires the
Returns the degradation dict, or {ok, backend:'optiland', optiland_version, efl_mm, bfl_mm, fno, n_surfaces, rms_spot_um:[per field], oracle_efl_mm, oracle_dev_pct}. |
| optics_lens_optimizeA | Optimize a SEQUENTIAL optical system with optiland's optimizer (MIT,
in-process) — the capability rayoptics lacks. Requires the
Returns the degradation dict, or {ok, backend:'optiland', converged, n_fev, before:{efl_mm,rss}, after:{efl_mm,rss}, surfaces:[optimized]}. |
| optics_solid_traceA | NON-SEQUENTIAL ray trace through a real solid (STL mesh) with a refractive
index — the lane for molded optical parts (light-pipes, prisms, lenses). Backed
by KrakenOS, which is GPL-3.0 and is run ONLY in a subprocess (the parent never
imports it — same arm's-length isolation as the GPL Elmer/OpenFOAM binaries).
Requires the Geometry: pass a Returns the degradation dict, or {ok, backend:'KrakenOS (subprocess-isolated, GPL-3.0)', n_launched, n_valid, valid_fraction, mean_turn_deg, max_turn_deg, rays:[{valid, exit_dir, turn_deg}], stl_path, paths?:[[[x,y,z],...],...]}. |
| granular_screenA | Closed-form granular/powder-mechanics oracles — banded correlations, NO
external solver (the FreeCAD-free analytic twins the YADE DEM solve is gated
against). These are correlations, not exact theory, so each returns
fidelity='correlation' + an honest [low, high] band; the band IS the oracle.
Dispatch on 'packing' (regime='random_close'|'random_loose'|'fcc'[, coordination]) — monodisperse sphere solid-volume fraction φ. RCP ≈ 0.637 (band 0.60–0.66), the random pile a real settle must hit, well below the crystalline FCC/HCP 0.7405. 'beverloo' (outlet_m, particle_d_m[, bulk_density_kg_m3 | material]) — flat-bottom hopper discharge W = C·ρ·√g·(D−k·d)^2.5 [kg/s]; flow ∝ outlet to the 2.5 power, independent of fill height. 'beverloo_exponent' (outlet1_m, flow1_kg_s, outlet2_m, flow2_kg_s [, particle_d_m]) — recover the log-log flow exponent from two (outlet, flow) points; granular 2.5 (band 2.2–2.8) vs Torricelli 2.0. 'repose' (friction_coeff[, saturation]) — poured-pile repose angle θ ≈ atan(μ) + ±25% band. 'repose_monotone' (mu_low, repose_low_deg, mu_high, repose_high_deg) — the steeper-with-friction monotonicity gate. SI units (m, kg/m³, kg/s, degrees). Escalate to dem_pack_submit / dem_flow_submit (the real YADE solve) for polydisperse mixes, non-spherical grains, cohesion, or geometry this monodisperse idealization can't see. |
| dem_pack_submitA | Pour N monodisperse spheres into a box and settle them under gravity with the REAL YADE discrete-element engine, then measure the random close-packing fraction φ of the settled bed — gated against granular_screen('packing') (RCP band 0.60–0.66, well below the crystalline 0.7405). YADE is GPL-3.0 and is run ONLY in a subprocess (the parent never imports it — same arm's-length isolation as the GPL Elmer/OpenFOAM binaries). Runs OFF the MCP channel via a background job, so a multi-second settle never blocks the worker.
|
| dem_flow_submitA | Discharge spheres from a flat-bottomed hopper box through a central orifice
with the REAL YADE discrete-element engine and measure the steady mass-flow
rate — gated against granular_screen('beverloo') (flow ∝ outlet^2.5). Submit
two
|
| optics_moldability_checkA | Moldability screen for a part against a single pull axis — geometric, no
solver. Resolves the
Returns {process, pull_axis, n_faces, undercut_faces, draft_violations, min_wall_violations, wall_thickness_stats:{min_mm,mean_mm,max_mm,n}, score, pass}. |
| moldability_checkA | Geometry-aware moldability DFx screen — resolves the
Returns the moldability_screen verdict {thickness:{…}, shrinkage:{…}, pass, score, fidelity, band_pct, warnings, escalate_to} plus {n_faces, n_wall_samples}. |
| dfa_checkA | Grade an assembly (Boothroyd-Dewhurst-lite). assembly_efficiency = theoretical_min/(part_count+fastener_count) (theoretical_min = unique_part_count or 1); assembly_score scales that by a handling penalty from insertion_axes/ symmetry and decreases monotonically as part/fastener count rises. The grade is an ordinal index for comparing variants (fidelity='correlation', band_pct=None) — rank with it, don't gate on the absolute value. Returns {part_count, fastener_count, insertion_axes, handling_difficulty, assembly_efficiency, assembly_score, symmetry_score, fidelity, band_pct}. |
| pack_checkA | Check a part against a shipping carton + compute billable weight.
part_bbox_mm/carton_mm are [l,w,h] mm; |
| cost_estimateA | Per-unit cost rollup (Design for Cost). material_cost = volume·density·price ·(1+scrap) from the Materials DB (process: cnc | fdm | casting | injection). process_cost = amortized setup + per-process machine time; tooling amortized over quantity, so unit_cost falls as quantity rises. The machine-time table is order-of-magnitude (fidelity='correlation', band_pct=100) — trust the ratios between processes/quantities, not the absolute dollars; material_cost alone is exact given its inputs.
Two optional inputs sharpen it. Returns {material_cost, process_cost, tooling_amortized, unit_cost, mass_kg, fidelity, band_pct, breakdown:{…, density_kg_m3, price_usd_kg, density_basis, price_basis, machine_time_hr, machine_time_basis, base_machine_time_hr, tolerance_class, tolerance_factor, tolerance_applied, tolerance_basis}}. Errors on a material with no usable density/price and no override, an unknown process/tolerance class, or a non-positive volume/quantity/machine time. |
| tolerance_cost_checkA | Price a tolerance scheme against the process that has to hold it — the missing link between tolerance_stackup ("what tolerance works") and cost_estimate ("what does it cost"). Per toleranced dimension: its ISO 286 IT grade as a FLOAT (a band between IT6
and IT7 reports 6.4, not 7), the cheapest machining operation that holds that
grade naturally (drilling ~IT11, milling ~IT10, turning ~IT9, reaming ~IT7,
grinding ~IT6), a relative cost index normalised to 1.0 at
fidelity='correlation', band_pct=50 — the RATIOS are defensible, the absolute index is dimensionless and is not money. Returns {process, links:[{name, nominal_mm, band_mm, it_grade, cost_index, verdict, cheapest_operation, natural_it, note}], n_links, total_cost_index, mean_cost_index, flagged, pass, fidelity, band_pct, basis, escalate_to}. |
| suggest_looseningA | The loosest tolerance that works: which links can give up tolerance for the biggest cost saving while the stack still passes. Greedy, one IT grade at a time — every trial loosening is re-verified against tolerance_stackup's seeded Monte-Carlo cpk before it is committed, so nothing it suggests can fail the spec. Candidates are ranked by the tolerance_cost_check curve, so the tightest (most expensive) links get opened first. Loosening preserves each link's MEAN, so the stack's nominal does not move. Two honest refusals: a chain that does not already meet
|
| cnc_machinability_checkA | 3-axis CNC machinability screen off a live solid — pure geometry, NO CAM engine (no toolpath, no gouge check, no holder collision). Counts SETUPS from a tool-approach census: for every machined face, which of ±X/±Y/±Z can both address it (the normal does not point away — a wall parallel to the tool axis is milled by the cutter's periphery) and reach it (a ray from the face escapes the solid, the same caster the moldability undercut check uses), reduced to a minimum cover. Faces lying on the stock envelope are excluded: they are billet surfaces, and counting them would quote six setups for a plain block. Findings: 'undercut' (a machined face no principal approach reaches — 5-axis, a
special cutter, or a redesign), 'deep_pocket' (depth/(2·corner radius) past
max_l_over_d — the corner radius caps the cutter and it cannot reach),
'small_radius' (an internal corner below the smallest cutter quoted, including a
SHARP planar corner reported as radius 0, which no rotating tool can produce),
'thin_wall'. Returns {setups, setup_directions, coverage, machined_faces, stock_faces, machined_area_mm2, min_internal_radius_mm, max_l_over_d_seen, undercut_faces, findings:[{code, severity, feature, detail}], warnings, score, pass, fidelity, band_pct, basis, escalate_to='cnc_time_estimate', limitations, n_faces}. |
| cnc_time_estimateA | Machining time for a live solid from a material-removal-rate model — the
honest MRR is per material class (aluminium 60, steel 12, stainless 6, titanium 2.5
cm³/min …) — ratios that track the standard machinability ratings amplified by
the depth-of-cut headroom a soft alloy allows on the same spindle. Stock-envelope
faces are excluded from the finishing area: facing a billet is not finishing a
pocket. fidelity='correlation', band_pct=50 — the RSS of MRR scatter (±40 %) and
cut-time utilisation scatter (±25 %), against the flat table's ±100 %; supply a
measured Returns {machine_time_min, machine_time_hr, roughing_min, finishing_min, cutting_min, setup_min_total, removed_volume_mm3, stock_volume_mm3, removal_fraction, machined_area_mm2, setups, setups_basis, material_class, material_basis, mrr_cm3_min, finish_cm2_min, utilisation, tolerance, bbox_mm, part_volume_mm3, fidelity, band_pct, basis, warnings, next}. |
| slice_estimateA | First-order FDM slice estimate (analytic, NO slicer needed; see slice_gcode_submit for the real PrusaSlicer CLI). mass_g = volume·density (Materials DB); deposited = volume·(wall_fraction + infill·(1−wall_fraction)) so at 100% infill filament_g == mass_g; layer_count = ceil(bbox height/layer_height); print_time from nozzle volumetric flow.
Returns {mass_g, filament_g, deposited_volume_mm3, layer_count, print_time_min, infill_fraction}. Errors on a material with no density and no override, a negative volume, a bbox shorter than [x,y,z], or a non-positive layer height. |
| slice_gcode_submitA | Slice a real part with the PrusaSlicer CLI, asynchronous — the external-CLI
upgrade of the analytic slice_estimate: real perimeters, infill patterns,
Pass a Returns the degradation dict or {job_id, status, cache_hit}; poll job_result for {ok, gcode_path, filament_mm, filament_cm3, filament_g, print_time_s, print_time_text, layer_count, config (the slicer's echoed settings), analytic?, deposited_ratio?}. |
| mechanism_kinematicsA | Closed-form planar mechanism kinematics — exact, NO external solver (the static
pre-check / gate for mechanism_simulate_submit). Pick
Returns the per-mechanism dict above. Raises on an unknown mechanism or a link set that cannot close. |
| mechanism_simulate_submitA | Simulate a rigid-link mechanism's DYNAMICS with PyBullet, asynchronously (the
MBD family; requires the
Returns immediately. If PyBullet is absent: {ok:false, reason, install, mobility_dof, n_links}. Otherwise {job_id, status, cache_hit, mobility_dof}; poll job_result(job_id) for {trajectories, orientations (per-link world quaternion, sampled with trajectories), max_torques, collisions_through_motion (with the sim time of each contact), reachable_envelope {bbox_mm}, mobility_dof}. |
| topology_optimize_submitA | Minimum-compliance topology optimization (in-house SIMP; NO external solver),
asynchronous because each iteration solves an FE system. Optimizes a 2-D
rectangular design domain ( Returns immediately {job_id, status, cache_hit}; poll job_result for {density
(2-D: nely×nelx grid; 3-D: nelz×nely×nelx voxel field, density[k][j][i] with j=0
at the bottom — this IS geometry), mass_fraction (==keep_fraction), compliance,
compliance_initial, iterations, converged, gray_fraction, solver (3-D: which
linear-solve backend ran)}. Threshold + voxel→solid back in the modeller with
|
| topology_to_solidA | Reconstruct a FreeCAD solid from a topology-optimization density field — the
modeller-side close of the loop opened by Returns {handle, name, volume (mm³), solid_cells, total_cells, mass_fraction (== solid_cells/total_cells — must be ≤ keep_fraction within one cell), n_solids (disjoint bodies; >1 means a split load path), threshold, nelx, nely, nelz (None for 2-D), bbox_mm}. Gate it with mass_properties (mass ≤ keep_fraction·original) and interference_check against keep-out regions, per SIMULATION_EXAMPLES §5. |
| async_demo_submitA | Reference async long-solve: launch a job that runs OFF the MCP channel and return immediately, so a multi-minute solve never blocks the worker. (This demo just computes for duration_s then returns a deterministic result; a real FEM/CFD solve plugs into the same facility — see ankusdrive/jobs.py.) Returns {job_id, status, cache_hit}; poll with job_status / job_result. A re-submit with identical (duration_s, value) is a content-hash cache hit (no recompute). |
| job_statusA | Lightweight poll of any async job (from an *_submit tool). Returns {job_id, kind, status: 'running'|'done'|'failed', elapsed_s, meta} (+ error when failed) WITHOUT the result payload — cheap to call in a loop. |
| job_resultA | Fetch an async job's outcome. Returns {job_id, kind, status, elapsed_s, result (when done) | error (when failed)}; while running neither is set. discard=True frees a terminal job (and its cache entry) once you have it. |
| job_listA | List every async job this worker session. Returns {count, jobs:[{job_id,
kind, status, elapsed_s}]} in submit order, plus main_thread_queue:{queued, ran,
failed, drains, pending} — the diagnostic for a shape optimization that looks
stuck. Those builds run on the worker's main thread, and that queue is drained
once per request: |
| recipe_listA | List every registered part recipe — named, parameterized, declared-input build templates (issue #136). Returns {schema, count, recipes} where each recipe maps to {doc, required, optional, emits}; the cheap directory to browse before picking and parameterizing a recipe with recipe_schema / recipe. |
| recipe_schemaA | Return one recipe's declared INPUT SCHEMA — its driving parameters with
type/unit/default/range. Returns {schema, recipe, doc, inputs:[{name, type,
unit?, default?, min?, max?, required, choices?, doc?}], emits}. |
| recipe_validateA | Validate a recipe reference {recipe, inputs} WITHOUT building it — the cheap front door (mirrors validate_manifest). Catches an unknown recipe, a missing required input, and a wrong-typed / out-of-range / bad-unit / unknown input. Returns {ok, problems} — ok is True iff problems is empty. Run before building or merging to reject a malformed parameterization before geometry is spent. |
| recipeA | Build a registered part recipe into the active document (issue #136): "regenerate with new parameters" = "re-run the recipe." Validates {recipe, inputs} against the declared schema (typed units + ranges) at the door, then runs the deterministic build — geometry + publish_interface + declare_intent. Returns {recipe, schema, inputs, handle, name, interfaces, intent, part}. |
| items_validateA | Validate an items.json registry (the PLM item/document/file split, #140 C1). Checks the schema stamp ("ankusdrive.items/1"), each item record's shape, that part numbers are unique, and the held reserved rev/lifecycle fields. An item is the logical part (part_number + rev + lifecycle + metadata), distinct from its file artifact(s); part numbers are non-significant + sequential, with meaning in queryable metadata. registry: path to the items.json sidecar. Returns {ok, problems, schema, count} — ok is True iff problems is empty. |
| items_resolveA | Resolve an item-reference (an item id) to its artifact file(s) against an items.json registry — identity, not a bare path, so renaming/moving a file updates the item's files[] without breaking references. Returns {ok, files}, or {ok:false, problems} on a dangling reference (an unknown item id). |
| items_newB | Allocate a non-significant sequential part number and register a new item in an items.json sidecar (created if absent), writing it back. The reserved rev / lifecycle fields are seeded with held defaults (the #141 state machine, not C1). registry: path to the items.json sidecar (created if it does not exist). item: the new item's stable logical id (what manifests reference). files: optional list of artifact paths the item maps to. metadata: optional free-form, queryable attributes (where "meaning" lives). Returns {part_number, item, registry}. |
| items_check_manifestA | Reference-integrity guard (#140 C1): check every item-reference in a manifest ({"item":""} on a component/instance) resolves against an items.json registry. Returns {ok, problems} — a dangling item-ref is caught before a merge, removing the "a part is its filename" fragility. |
| get_interfaceA | Read back a single published interface FRAME by name from a component (issue #139). The reference-by-name primitive feature templates ride on: an unpublished name fails loudly. Returns {handle, name, frame}. |
| feature_listA | List every registered FEATURE TEMPLATE — the PowerCopy/UDF analog of a part recipe: a reusable feature with declared reference-geometry inputs (a frame, an f_/e_ tag, an axis) plus scalar parameters, stamped onto a host by name (issue #139). Returns {schema, count, templates} where each maps to {doc, refs, required, optional, emits}; the directory to browse before picking one with feature_schema / feature_instantiate. |
| feature_schemaB | Return one feature template's declared REF+INPUT SCHEMA — its reference geometry (name/kind) and its scalar parameters (type/unit/default/range). Returns {schema, template, doc, refs:[{name, kind, required, doc?}], inputs:[{name, type, unit?, default?, min?, max?, required, choices?, doc?}], emits}. An unknown name fails loudly. |
| feature_validateA | Validate a feature instantiation {template, refs, inputs} WITHOUT building it — the cheap structural front door (mirrors recipe_validate). Catches an unknown template, an unknown/missing required reference, a malformed reference value, and every scalar-input failure (missing required, out of range, wrong type, bad unit, unknown key). A tag that doesn't resolve against real geometry is caught at feature_instantiate time. Returns {ok, problems}. |
| feature_instantiateA | Stamp a registered FEATURE TEMPLATE onto a host body at reference geometry supplied BY NAME (PowerCopy/UDF, issue #139) — e.g. a mounting_boss onto a published seat frame, or a bolt_pattern onto an f_ face tag. Validates {refs, inputs} at the door, resolves each reference against the host's CURRENT geometry (an f_ tag / interface name that doesn't resolve fails loudly), then runs the deterministic build — geometry + publish_interface + declare_intent. template: registered template name (feature_list to browse). host: the handle of the body to stamp onto. refs: reference inputs by name — an interface name, an f_/e_ tag, or a literal {origin, z_axis?, x_axis?} frame, per the template's declared ref kinds. inputs: scalar parameters (typed + unit-bearing + range-checked). Returns {template, schema, host, refs, inputs, handle, name, interfaces, intent}. |
| family_validateA | Validate a variant-family design table (issue #138, B1) — a row x column table where row = a variant (keyed by a size designator) and column = a recipe parameter / feature-flag / material. Loads CSV or JSON and checks the recipe, mode, key column, duplicate/missing size keys, and every per-row recipe-door value; each problem names the row+column. Returns {ok, problems}. table: path to the family table (.csv or .json). |
| family_materializeA | Materialize a whole variant family from ONE design table (issue #138, B1) — "make all the gears" becomes a table, not a loop. For each row, in table order, builds the part with its recipe (A1, #136) and allocates one item + one sequential part number (C1, #140). Subsumes standard-part catalogs: a bearing catalog is a family table keyed by designation, sourced from the ISO corpus. Two modes (both supported): "instances" (each variant its own released file + part number) and "configurations" (variants share one artifact). Builds into the active document. Returns {schema, family, recipe, mode, key, count, rows, registry}. table: path to the family table (.csv or .json). registry: optional items.json path (created if absent, written back). mode: optional override of the table's mode (instances|configurations). |
| substitutability_checkA | Liskov-substitutability gate — Form/Fit/Function as code (§7.1, #147). Take an assembly that gates green with variant A in a slot, swap in variant B (a different family row, or any part claiming the same interface), and re-run merge_assembly + all the gates. Still green ⇒ B is interchangeable with A — by construction a compatible (MINOR/PATCH) change ⇒ revise the existing part number; a gate now fails ⇒ the swap broke Form/Fit/Function ⇒ a new part number. Purely deterministic (no API, no judgment); the substitutability test #138 (B1 families) and #146 (the interface registry) call. manifest: path to a manifest that gates green with variant A in Function, not just Form and Fit (#261): if the swapped-in variant declares a
PERFORMANCE contract (#226), it is part of the comparison. A contract measured as
NOT met breaks the Returns {schema, slot, variant, baseline_ok, swap_ok, substitutable (True | False | null), verdict ('substitutable' | 'not_substitutable' | 'baseline_not_green' | 'performance_unproven'), broken_gates (the NAMED gate(s) the swap broke), broken (gate→violations), classification (compatibility/semver/decision), performance? (only when a component declares a contract), reports}. |
| lifecycle_editableA | The cheap "is this editable?" check a builder runs before writing (#141 C2). An item is editable only in lifecycle state in_work; in_review, released and obsolete are frozen (released = immutable, the API-stability guarantee). registry: path to the items.json sidecar. item: the item id to check. Returns {ok, editable, state}. |
| lifecycle_transitionA | Move an item through the lifecycle state machine in_work -> in_review -> released -> obsolete, guarded by a transition table (#141 C2). An illegal edge (e.g. skipping review, or re-opening a released item in place) is rejected loudly; releasing stamps the item's first revision and freezes it. registry: path to the items.json sidecar (written back on success). item: the item id to transition. to: the target lifecycle state. actor / note: optional provenance recorded in the item's transition log. Returns {ok, state, rev}, or {ok:false, problems} on an illegal transition. |
| lifecycle_classify_changeA | The deterministic Form/Fit/Function predicate (#141 C2): compare an item's before/after attributes and decide rename-vs-revise. A change touching a Form/Fit/Function (public, interface-defining) attribute breaks interchangeability => "new_part_number" (allocate a new number); a change to only internal/hidden attributes is interchangeable => "revise" (bump the revision, same part number). before / after: attribute objects (the item's interface-defining + internal attributes, before and after the proposed change). extra_f3: optional map of extra attribute name -> F3 leg ("form"/"fit"/ "function") for domain-specific interface attributes. Returns the verdict {disposition, f3, changed, f3_changed, categories, reason} where disposition is "revise" / "new_part_number" / "noop". |
| lifecycle_apply_changeA | Apply a change to a RELEASED item, dispatching on the F3 predicate (#141 C2)
— the sanctioned way to change a frozen part. An F3-preserving change opens a
new revision on the SAME part number (rev A->B, back to in_work); an
F3-breaking change allocates a NEW part number as a new item (new_item
required) carrying a registry: path to the items.json sidecar (written back on success). item: the released item id being changed. after: the proposed new metadata (attribute object). new_item: id for the new item when the change is F3-breaking. extra_f3 / actor / note: optional, as in lifecycle_classify_change / lifecycle_transition. Returns {ok, disposition, item, part_number, rev, ...}, or {ok:false, problems} (e.g. the item is not released, or an F3-break lacks new_item). |
| scaffold_projectA | Lay out a well-formed project in one call (issue #143 / D1) — promote the MULTI_AGENT.md §3/§7 directory convention to a primitive. Creates the convention directories (components/, .dp_lib/), an item registry (items.json, #140), a seed assembly manifest from the supplied components/instances, and the project.json container that ties them together. The result loads clean and merge_assembly consumes it unchanged once its components resolve. base_dir: the project root directory (created if absent). name: the project id (naming-convention checked). components/instances/shared_parameters: the assembly manifest content. master: optional component id to record as the master/skeleton single-source-of- truth slot (the lean interface-geometry skeleton children mate against). items: optional {item_id: {files?, metadata?}} to seed the item registry with. Returns the layout {project_file, manifest, registry, components_dir, lib_dir, lockfile, master, dirs}. |
| project_validateA | Validate a project.json container (issue #143 / D1): the schema stamp ("ankusdrive.project/1"), the naming convention on the project name, the conventional path fields, and — relative to the project directory — that the referenced manifest + item registry exist, the components directory is present, and a named master/skeleton is a real component of the assembly manifest. project: path to the project.json container. Returns {ok, problems, schema, name} — ok is True iff problems is empty. |
| project_check_referencesA | Reference-integrity guard (issue #143 / D1) — catch a broken cross-file reference before a merge (the chronic PDM failure mode). Loads a project's assembly manifest + item registry and checks every reference resolves: a moved / renamed / missing component file, a dangling item-ref (an unknown id, or a resolved file missing on disk), an instance naming an unknown component, and naming-convention violations. project: path to the project.json container. Returns {ok, problems} — ok is True iff every reference is live, so a broken reference is reported here instead of as a cryptic merge failure. |
| project_resolve_manifestA | Resolve a project's item-ref components to file components and write a merge-
ready manifest (issue #143 / D1, the deferred #140 seam). Each component naming an
project: path to the project.json container. out: output manifest path (defaults to .resolved.json next to it). Returns {path, lowered} — the written path and the lowered manifest object; a dangling item-ref fails loudly. |
| where_usedA | Where-used / impact analysis (issue #142, C3; MULTI_AGENT.md §9): traverse the
lockfile lockfile: path to the lockfile (the JSON assembly_lock wrote). item: the item / component id to query. direct: if true, also surface only the immediate mates separately. Returns {item, where_used (the full transitive blast radius), direct (the §9 immediate-neighbour layer)} — an unknown item fails loudly, never a silent empty set. |
| change_impactA | The where-used impact report for a set of changed items over a lockfile graph
(issue #142, C3) — surfaces the §9 lockfile: path to the lockfile (the §9 dependency graph). changed: the list of changed item / component ids (an ECO's affected set). Returns {changed, stale (the §9 immediate re-dispatch consumers), where_used (the full transitive blast radius), ok (true iff nothing is impacted)}. |
| eco_validateA | Validate an ECO (engineering change order) record (issue #142, C3) — the cheap front door. Checks the schema stamp, a non-empty id + affected item set, a present disposition, and an effectivity carrying exactly one of date|serial|revision. eco: the ECO object. Returns {ok, problems} — ok is True iff problems is empty. |
| eco_createA | Build an ECO change-order record (issue #142, C3) — turn a change into a record, not a silent mutation (the diff IS the change order, mapping onto a git commit/PR). Optionally compute its where-used impact over a lockfile in the same call, so the blast radius travels with the record. id: the ECO id (e.g. "ECO-0001").
affected: the list of changed item / component ids.
disposition: the change disposition (use_as_is | rework | scrap | revise | ...).
effectivity: a dict with exactly one of date|serial|revision (when it takes
effect).
title / note: optional human description recorded on the record.
interface_change: true if a published interface moved (the §9 stale trigger).
lockfile: optional path — when given, the result embeds an Returns the ECO object (with |
| baseline_createA | Pin a labeled, immutable BASELINE (issue #142, C3) — a {item: revision + content fingerprint} snapshot over an items.json registry (a git-tag / lockfile over the item graph) for reproducible rebuilds. The fingerprint pins the artifact BYTES, so a rebuild is verifiable byte-for-byte. label: the baseline label (e.g. "v1.0"). registry: path to the items.json sidecar. items: optional subset of item ids to pin (default: every item). base_dir: artifact root for fingerprinting (defaults to the registry's directory). note: optional description. out: optional path — write the git-diffable baseline sidecar there. Returns the baseline object. Deterministic: same state -> identical bytes. |
| baseline_verifyA | Verify a rebuild against a pinned baseline (issue #142, C3) — the reproducible- rebuild gate. Re-resolves every pinned item from the current registry + artifacts and checks it still matches the pinned rev AND content fingerprint; a drifted input (changed bytes, a bumped rev, a vanished item) is caught, never silently accepted. baseline: path to the baseline sidecar. registry: path to the current items.json sidecar. base_dir: artifact root for fingerprinting (defaults to the registry's directory). Returns {ok, label, drifted (item/field/expected/actual), missing} — ok is True iff the rebuild reproduces the baseline exactly. |
| release_packageA | Produce the vendor/RFQ deliverable bundle for one item at one revision — STEP + drawings + BOM + inspection package + a checksummed manifest — gated by the item's lifecycle state and stamped with its ECO. Every piece of this exists as its own tool. What this adds is the guarantee that ties them together: the STEP, the PDF, the BOM and the title block all describe the SAME revision of the SAME item. That is the whole point of a release, and it is enforced BEFORE a single file is written:
A refused release writes NOTHING — no half-populated directory a build script could mistake for a package. registry / item: the items.json sidecar and the item id being released.
out_dir: where the bundle is written (created if absent).
kinds: any of "step", "drawing_pdf", "drawing_svg", "drawing_dxf", "bom_csv",
"inspection", "manifest_json". Default step/drawing_pdf/drawing_dxf/
bom_csv/manifest_json. Determinism: the same item at the same revision produces a BYTE-IDENTICAL package. The exporters' wall-clock header stamps (the STEP FILE_NAME timestamp above all) are scrubbed to a fixed epoch, so a re-released package is diffable by checksum. Returns {ok, dir, item, part_number, rev, lifecycle, eco, material, draft, watermark, flavor, kinds, dropped_kinds, implied_kinds, pages, files:[{name, kind, bytes, blake2b}], manifest, manifest_path, verify, problems}. ok=False means the gate refused and nothing was written; each problem carries {code, where, field, expected, actual, reason}. |
| sheet_baseA | Start a sheet-metal part: the base flange, a closed straight-sided profile
extruded to profile: [[x, y], ...] in the XY plane, implicitly closed. sketch: alternatively a handle to a closed, planar, straight-sided sketch — on any plane. Arcs are REJECTED rather than silently faceted, because a faceted flat pattern is a wrong flat pattern. material: any Materials-DB name (e.g. "Steel-A36", "AL6061-T6", "SS304"); it selects the K-factor and minimum-bend-radius corpus rows. IMPORTANT — the profile is the flat face TANGENT TO TANGENT, not the outside dimension. Bends grow OUTWARD from the profile boundary, exactly as a base flange behaves in any sheet-metal CAD, so a U-channel of 100 mm outside width with R = t = 2 starts from a 92 mm profile. The bend model lives alongside the handle for the life of the worker session, like every other handle: unfolding is a property of the FEATURE TREE, not of the fused solid, so a part reopened from disk in a new session is a solid rather than a sheet part. Cutting one (boolean_op cut, e.g. to drill it) keeps it a sheet part; fusing arbitrary material onto it does not. Returns {handle, name, volume, thickness_mm, material, profile, area_mm2}. |
| sheet_flangeA | Bend a flange off a free edge of a sheet part. edge: a stable e_* tag from list_edges (or 'EdgeN'/int) on a STRAIGHT free edge
of a flat region — a bend line is the intersection of two planes, so an arc
cannot carry one and is rejected.
angle_deg: the bend angle, i.e. the deviation from flat, so 90 is a right-angle
flange. Must be in (0, 180].
inner_radius_mm: inside bend radius; defaults to the material thickness.
direction: 'up' (toward the region's outward normal) or 'down'.
length_from: what Consumes the input handle (it is hidden, having become part of the result). Returns {handle, name, feature, kind, volume, angle_deg, inner_radius_mm, leg_tangent_mm, length_from, direction, span_mm, thickness_mm}. |
| sheet_tabA | Extend a sheet part with a coplanar tab — a flat ear off a free edge with no bend at all (a mounting lug, a weld tab, a snap-off). Mechanically it is a zero-angle flange and shares that code path exactly:
Returns the same dict sheet_flange does (angle_deg 0, inner_radius_mm 0). |
| sheet_hemA | Fold a hem back on itself — the 180-degree return that stiffens a free edge and buries the sharp cut line so the part is safe to handle. kind: 'closed' (inside radius t/2, gap t) or 'open' (radius t, gap 2t). radius_mm / gap_mm: override the style directly; the gap between the returned leg and the parent is exactly 2R, so gap wins as radius = gap/2. length_mm is ALWAYS the return leg measured from the end of the bend: a 180-degree bend has no virtual apex to dimension to — the outside surfaces are parallel and never meet — so an 'outer' dimension would be infinite. For the same reason a hem reports a bend allowance but no bend deduction, and sheet_check screens it as a two-hit hem (bend, then flatten in a hemming die) exempt from the air-bend radius and flange rules. A teardrop hem wraps past 180 degrees and is out of scope. Returns the same dict sheet_flange does, plus {hem_kind, gap_mm}. |
| sheet_unfoldA | Develop a sheet part into its flat pattern — the blank the part is cut from — and report every bend. The flat pattern is derived from the bend tree, not reverse-engineered out of the fused solid, so it is exact rather than fitted: each bend contributes its bend allowance BA = angle·(R + K·t), the arc length of the neutral fibre. k_factor: pins K for every bend. bend_table: a shop's own measured rows [{thickness_mm, inner_radius_mm, angle_deg, allowance_mm | deduction_mm}] — a matching row OUTRANKS the chart, because the shop's press is the ground truth for the shop's press. Without either, K comes from a press-brake corpus keyed by material and r/t. WHICHEVER IT IS, IT IS ECHOED BACK per bend as k_factor + k_source: a flat length whose K you cannot see is a number you cannot check. build: also create the flat blank as a real solid (holes included) at Fidelity: 'exact' only when EVERY bend's K was supplied or table-derived — BA
given K is pure arithmetic. One corpus-defaulted bend makes the development a
'correlation' with band_pct, and Returns {ok, handle?, name?, volume?, outline, holes, bend_lines, bends,
regions, flat_size, flat_bbox, flat_area_mm2, blank_area_mm2,
blank_volume_mm3, thickness, material, fidelity, band_pct, developed_band_mm,
warnings} — |
| sheet_refoldA | Fold a flat pattern back up and check it reproduces the part — the other half of the unfold gate. This does NOT replay the feature model. It reads the flat pattern back: each leg length is measured off the flat outline, walking outward from the bend's attachment past its reported bend allowance to the far edge of that region. So a wrong allowance, angle or bend direction lands the refolded solid somewhere the original is not, and this reports the disagreement instead of hiding it. handle: a sheet part to unfold and then refold.
flat: alternatively a sheet_unfold report, to refold a development produced
elsewhere (or a deliberately corrupted one, to prove the check bites).
compare: the handle to check against; defaults to Note the round trip that is meaningful and the one that is not: refold-vs-folded must match, but flat-vs-folded VOLUME must not, and does not. Bending preserves neutral-fibre length, not material volume — a bend sector's true volume is angle·t·(R + t/2)·w while its flat footprint is angle·(R + K·t)·t·w, and those agree only at K = 0.5. Returns {handle, name, volume_mm3, bbox, bends, compare?} where compare is {handle, matches, volume_mm3, volume_error_pct, bbox_max_error_mm, tolerance}. |
| sheet_flat_exportA | Write the flat pattern as a LAYERED DXF — the file a laser, punch or press-brake shop actually quotes and cuts from. This is the deliverable the whole sheet-metal family exists to produce. Three layers, because a flat pattern without them is not a shop deliverable:
CUT carries the closed outer profile and every hole; BEND_UP and BEND_DOWN carry
one centreline per bend, so the operator reads the fold direction off the print
rather than inferring it. DXF R12 ASCII in millimetres, written directly rather
than through TechDraw — a flat pattern is not a drawing view and does not want a
sheet frame, a scale or a title block around it. Takes the same k_factor / bend_table arguments as sheet_unfold, since the outline it writes IS the development. Returns {ok, path, size, layers, entities, flat_size, blank_area_mm2, bends (with bend_allowance_mm, bend_deduction_mm, k_factor and k_source per bend), fidelity, band_pct, warnings}. |
| sheet_checkA | Press-brake manufacturability screen for a sheet part — four rules, each with the number it came from: min_bend_radius — an inside radius below the material's minimum (a corpus value per material: 3t for 6061-T6, 1t for A36, 0.5t for annealed 1100) cracks the outer fibre. min_flange_length — an outer leg under 4t + R has no die shoulder to sit on and dives into the vee. hole_to_bend — a hole whose EDGE is nearer the bend tangent than 2t + R draws into an oval. Holes are read off the real solid, not declared. refold_collision — two features that occupy the same space once folded, found by actually intersecting them rather than by a rule. A hem is screened as a two-hit hem (bend, then flatten) and exempted from the air-bend radius and flange rules, which would otherwise fail every hem ever drawn. A flat pattern whose feature footprints overlap is a finding too, not a warning dropped on the floor. An unrecognised material degrades to a bend-class fallback WITH an info finding saying so, rather than skipping the rule silently. min_flange_t / hole_to_bend_t: override the thresholds (multiples of thickness). fidelity='correlation' with band_pct=None — these are press-brake rules of thumb, thresholds for ranking and gating rather than measured predictions. Returns {ok, findings, fail_count, rules, min_bend_radius_mm, min_bend_radius_source, flat_size, blank_area_mm2, k_factors, thickness_mm, material, fidelity, band_pct}. Each finding carries {code, severity, message} plus the measured value and the limit it missed. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
| diagnose_setup | Diagnose this AnkusDrive install and guide the user through finishing it. |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
| setup_resource | The current setup/health report (the human-readable `ankusdrive doctor` checklist): FreeCAD + every solver family with per-item fixes. Regenerated on every read; side-effect-free (no FreeCAD boot). |
TDQS
Scored across 281 tools
Despite the enormous surface, most tools declare a clearly distinct purpose, and descriptions go out of their way to draw boundaries (render_view vs render_photoreal vs render_photoreal_submit vs render_job; screen vs *_submit twin pairs; check vs verify gates). A few clusters genuinely blur—moldability_screen/moldability_check/optics_moldability_check, dfm_check/cnc_machinability_check, and the many *_check/*_validate gates—but the text usually disambiguates them.
Names are uniformly snake_case with a predictable verb_noun convention (add_*, make_*, list_*, get_*, check_*, verify_*, declare_*, *_submit, *_results) plus consistent family prefixes (fem_*, cfd_*, em_*, sheet_*, items_*, optics_*). Minor deviations exist—single-word domain verbs (pad, hole, pocket, loft) and a mixed verify_/check_ verb style—but the pattern stays readable.
281 tools is an extreme surface, roughly 5–6x the 50-tool threshold the rubric treats as a total mismatch, and far beyond what any agent can navigate without heavy filtering. Even granting the genuinely broad engineering domain (CAD, FEM, CFD, optics, EM, DEM, molding, sheet metal, PLM), the count is overwhelming and unearns its keep at this scale.
The surface is exceptionally complete: full CRUD-style CAD/PartDesign modeling, mesh/FEM solvers across modal, buckling, thermal, nonlinear and contact, CFD, FSI, acoustics, EM, DEM, molding, sheet-metal unfold/refold/DXF, DFM, cost, drawing and manufacturability gates, plus a full PLM layer (items, lifecycle, ECO, baseline, release, project scaffolding). It is hard to identify any obvious missing operation or dead end.