Mechanical Engineering Agent — PRD & Architecture
Comprehensive specification for the ME Agent using open-source tools (FreeCAD, CalculiX, OpenFOAM)
| Status: Draft | Phase: Phase 2 (v0.4-0.6) | Discipline: #3 Mechanical Engineering |
Table of contents
- 1. Problem Statement & Vision
- 2. Open-Source Tool Stack
- 3. Architecture
- 4. Data Model (Pydantic Schemas)
- 5. Feature Prioritization (MoSCoW)
- 6. Integration Points
- 7. Implementation Phases (14 Weeks)
- 8. Success Metrics & KPIs
- 9. Risk Matrix
- 10. Material Library (Reference)
- 11. Reference: FreeCAD Enclosure Script
- 12. Glossary
- Related Documentation
1. Problem Statement & Vision
1.1 The Bottleneck
Mechanical engineering is a critical bottleneck in hardware product development:
| Pain Point | Current State | Impact |
|---|---|---|
| Enclosure design | 1-3 week manual design cycles | Delays EVT gate by 2-4 weeks |
| Simulation licenses | $30K-$100K/year for Ansys/SOLIDWORKS | Prohibitive for startups and small teams |
| Tolerance analysis | Manual spreadsheet-based, error-prone | 15-20% of prototyping failures traced to tolerance stack-up errors |
| FEA validation | Requires specialist CAE engineer | $150-$200/hr contractor rate, 1-2 week lead time |
| Material selection | Tribal knowledge, inconsistent documentation | Suboptimal choices discovered late in DVT |
1.2 Value Proposition
The ME Agent transforms mechanical engineering from a bottleneck into an accelerator:
| Metric | Before | After | Improvement |
|---|---|---|---|
| Enclosure design cycle | 1-3 weeks | 2-4 hours | ~97% reduction |
| FEA validation | 3-5 days (outsourced) | 10-30 minutes | ~99% reduction |
| Simulation license cost | $30K-$100K/year | $0 (open-source) | 100% reduction |
| Tolerance analysis | 1-2 days manual | 5 minutes automated | ~99% reduction |
| Material selection | Ad-hoc, days | 2 minutes, documented | Consistent + traceable |
1.3 User Personas
Persona 1: Hardware Engineer (Primary)
- Has mechanical intuition but limited CAD/FEA time
- Wants: “Generate an IP65 enclosure for this PCB with thermal vents”
- Needs: Parametric STEP files, validated FEA results, tolerance reports
Persona 2: Startup Founder / Product Lead
- Non-specialist making early-stage mechanical decisions
- Wants: “Will this enclosure survive a 1m drop test?”
- Needs: Pass/fail answers with safety factors, material recommendations
Persona 3: MetaForge Orchestrator (Machine-to-Machine)
- Upstream agents (EE, SYS) triggering ME workflows automatically
- Wants: Structured JSON inputs/outputs, deterministic execution
- Needs: Pydantic-validated schemas, digital thread integration, artifact storage
2. Open-Source Tool Stack
The ME Agent replaces proprietary CAD/CAE tools with a fully open-source stack:
2.1 Tool Replacement Matrix
| Replaces | With | Version | Rationale |
|---|---|---|---|
| SOLIDWORKS / Fusion 360 | FreeCAD 0.21+ | 0.21+ (Part, PartDesign, FEM workbenches) | Python scripting API, headless mode via freecadcmd, STEP/STL/IGES export, parametric modeling, FEM workbench |
| Ansys Mechanical FEA | CalculiX 2.21+ | 2.21+ | Abaqus-compatible .inp file format, static/modal/thermal FEA, integrates with FreeCAD FEM workbench, GPL licensed |
| Ansys Fluent / CFX | OpenFOAM v11+ | v11+ (ESI-OpenCFD) | CLI-driven with dictionary-file configuration, snappyHexMesh for meshing, buoyantSimpleFoam for thermal/flow, Apache 2.0 licensed |
2.2 Tool Capability Comparison
graph LR
subgraph "Proprietary Stack"
A1[SOLIDWORKS<br/>$4K-$8K/yr]
A2[Ansys FEA<br/>$30K-$50K/yr]
A3[Ansys CFD<br/>$30K-$50K/yr]
end
subgraph "Open-Source Stack"
B1[FreeCAD<br/>$0 / LGPL]
B2[CalculiX<br/>$0 / GPL]
B3[OpenFOAM<br/>$0 / Apache 2.0]
end
A1 -.->|replaced by| B1
A2 -.->|replaced by| B2
A3 -.->|replaced by| B3
style A1 fill:#e74c3c,color:#fff
style A2 fill:#e74c3c,color:#fff
style A3 fill:#e74c3c,color:#fff
style B1 fill:#27ae60,color:#fff
style B2 fill:#27ae60,color:#fff
style B3 fill:#27ae60,color:#fff
2.3 FreeCAD Headless Execution
FreeCAD supports headless operation via freecadcmd, enabling fully automated CAD generation:
# Execute a Python script headlessly (no GUI)
freecadcmd -c "exec(open('generate_enclosure.py').read())"
# Or via module execution
freecadcmd script.py -- --width 80 --depth 60 --height 25
Key APIs used by the ME Agent:
Part.makeBox(),Part.makeCylinder()— primitive geometryPart.Shape.cut(),.fuse(),.common()— boolean operationsPart.Shape.exportStep()— STEP exportMesh.export()— STL exportFemMeshGmsh— mesh generation for FEAimportOBJ,importSTL— format conversion
3. Architecture
3.1 System Context
flowchart TB
subgraph "MetaForge Control Plane"
GW[Gateway Service]
OR[Orchestrator]
end
subgraph "ME Agent System"
MA[MechanicalAgent]
LLM[LLM Provider Layer]
TR[Tool Registry]
end
subgraph "Tool Adapters"
FC[FreeCADAdapter]
CX[CalculiXAdapter]
OF[OpenFOAMAdapter]
end
subgraph "External Tools"
FCD[freecadcmd<br/>Headless CAD]
CCX[ccx<br/>FEA Solver]
OFM[OpenFOAM<br/>CFD Solver]
end
subgraph "Data Layer"
N4J[Neo4j<br/>Digital Thread]
MIO[MinIO<br/>Artifact Storage]
end
GW --> OR
OR --> MA
MA --> LLM
MA --> TR
TR --> FC
TR --> CX
TR --> OF
FC --> FCD
CX --> CCX
OF --> OFM
MA --> N4J
MA --> MIO
style MA fill:#9b59b6,color:#fff
style LLM fill:#3498db,color:#fff
style FC fill:#e67e22,color:#fff
style CX fill:#e67e22,color:#fff
style OF fill:#e67e22,color:#fff
3.2 Multi-Provider LLM Layer
The ME Agent uses the shared LLMProvider abstraction defined in lib/llm/. This layer provides a unified interface over multiple LLM backends.
Location: lib/llm/
// lib/llm/types.ts — Unified types across providers
interface ToolDefinition {
name: string;
description: string;
parameters: JSONSchema;
}
interface ToolCall {
id: string;
name: string;
arguments: Record<string, unknown>;
}
interface ToolResult {
tool_call_id: string;
content: string;
is_error?: boolean;
}
interface Message {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string | ContentBlock[];
tool_calls?: ToolCall[];
tool_results?: ToolResult[];
}
interface CompletionOptions {
model?: string;
tools?: ToolDefinition[];
temperature?: number;
maxTokens?: number;
responseFormat?: { type: 'json_object' | 'text' };
stop?: string[];
}
interface LLMResponse {
content: string;
tool_calls?: ToolCall[];
usage: { prompt_tokens: number; completion_tokens: number };
model: string;
finish_reason: 'stop' | 'tool_use' | 'length';
}
interface LLMChunk {
content?: string;
tool_call?: Partial<ToolCall>;
finish_reason?: string;
}
// lib/llm/provider.ts — Provider interface
interface LLMProvider {
name: string;
complete(messages: Message[], options?: CompletionOptions): Promise<LLMResponse>;
stream(messages: Message[], options?: CompletionOptions): AsyncIterable<LLMChunk>;
}
// lib/llm/anthropic-provider.ts
import Anthropic from '@anthropic-ai/sdk';
class AnthropicProvider implements LLMProvider {
name = 'anthropic';
private client: Anthropic;
constructor(config: { apiKey: string }) {
this.client = new Anthropic({ apiKey: config.apiKey });
}
async complete(messages: Message[], options?: CompletionOptions): Promise<LLMResponse> {
const response = await this.client.messages.create({
model: options?.model ?? 'claude-sonnet-4-5-20250929',
max_tokens: options?.maxTokens ?? 4096,
temperature: options?.temperature ?? 0.3,
system: this.extractSystem(messages),
messages: this.mapMessages(messages),
tools: options?.tools?.map(t => this.mapTool(t)),
});
return this.mapResponse(response);
}
// Maps MetaForge ToolDefinition → Anthropic tool_use format
private mapTool(tool: ToolDefinition): Anthropic.Tool {
return {
name: tool.name,
description: tool.description,
input_schema: tool.parameters,
};
}
async *stream(messages: Message[], options?: CompletionOptions): AsyncIterable<LLMChunk> {
const stream = this.client.messages.stream({
model: options?.model ?? 'claude-sonnet-4-5-20250929',
max_tokens: options?.maxTokens ?? 4096,
messages: this.mapMessages(messages),
});
for await (const event of stream) {
yield this.mapChunk(event);
}
}
}
// lib/llm/openai-provider.ts
import OpenAI from 'openai';
class OpenAIProvider implements LLMProvider {
name = 'openai';
private client: OpenAI;
constructor(config: { apiKey: string }) {
this.client = new OpenAI({ apiKey: config.apiKey });
}
async complete(messages: Message[], options?: CompletionOptions): Promise<LLMResponse> {
const response = await this.client.chat.completions.create({
model: options?.model ?? 'gpt-4o',
temperature: options?.temperature ?? 0.3,
max_tokens: options?.maxTokens ?? 4096,
messages: this.mapMessages(messages),
tools: options?.tools?.map(t => this.mapTool(t)),
response_format: options?.responseFormat,
});
return this.mapResponse(response);
}
// Maps MetaForge ToolDefinition → OpenAI function_calling format
private mapTool(tool: ToolDefinition): OpenAI.ChatCompletionTool {
return {
type: 'function',
function: {
name: tool.name,
description: tool.description,
parameters: tool.parameters,
},
};
}
}
// lib/llm/factory.ts
type ProviderType = 'anthropic' | 'openai';
interface LLMConfig {
provider: ProviderType;
apiKey: string;
defaultModel?: string;
}
class LLMProviderFactory {
static create(config: LLMConfig): LLMProvider {
switch (config.provider) {
case 'anthropic':
return new AnthropicProvider({ apiKey: config.apiKey });
case 'openai':
return new OpenAIProvider({ apiKey: config.apiKey });
default:
throw new Error(`Unknown LLM provider: ${config.provider}`);
}
}
}
3.3 ME Agent Architecture
Location: agents/mechanical/
// agents/mechanical/mechanical-agent.ts
import { Agent, AgentConfig, ExecutionContext, AgentResult } from '@metaforge/core';
import { MechanicalInputSchema, MechanicalOutputSchema } from './schemas';
class MechanicalAgent implements Agent {
name = 'mechanical-agent';
version = '1.0.0';
description = 'Mechanical engineering: CAD generation, FEA, CFD, tolerance analysis, material selection';
private llm: LLMProvider;
private tools: ToolRegistry;
async initialize(config: AgentConfig): Promise<void> {
this.llm = config.llmProvider;
this.tools = config.toolRegistry;
}
registerTools(registry: ToolRegistry): void {
registry.register(new FreeCADAdapter());
registry.register(new CalculiXAdapter());
registry.register(new OpenFOAMAdapter());
}
async execute(context: ExecutionContext): Promise<AgentResult> {
const input = MechanicalInputSchema.parse(context.input);
const taskType = this.classifyTask(input);
switch (taskType) {
case 'enclosure_generation':
return await this.generateEnclosure(input);
case 'fea_stress':
return await this.runStressFEA(input);
case 'fea_modal':
return await this.runModalFEA(input);
case 'fea_thermal':
return await this.runThermalFEA(input);
case 'cfd_thermal':
return await this.runCFDThermal(input);
case 'tolerance_analysis':
return await this.runToleranceAnalysis(input);
case 'material_selection':
return await this.selectMaterial(input);
case 'mechanical_bom':
return await this.generateMechanicalBOM(input);
case 'dfa_analysis':
return await this.runDFAAnalysis(input);
default:
throw new Error(`Unknown task type: ${taskType}`);
}
}
validateInput(input: unknown): ValidationResult {
const result = MechanicalInputSchema.safeParse(input);
return result.success
? { valid: true }
: { valid: false, errors: result.error.issues };
}
validateOutput(output: unknown): ValidationResult {
const result = MechanicalOutputSchema.safeParse(output);
return result.success
? { valid: true }
: { valid: false, errors: result.error.issues };
}
}
3.3.1 Task Types
| # | Task Type | Description | Primary Tool | Output Artifacts |
|---|---|---|---|---|
| 1 | enclosure_generation |
Generate parametric enclosure from PCB constraints | FreeCAD | .FCStd, .step, .stl |
| 2 | fea_stress |
Static stress analysis under mechanical loads | CalculiX | stress map, safety factor, .inp |
| 3 | fea_modal |
Natural frequency / modal analysis | CalculiX | mode shapes, frequencies |
| 4 | fea_thermal |
Steady-state / transient thermal FEA | CalculiX | temperature map, heat flux |
| 5 | cfd_thermal |
Computational fluid dynamics for airflow / cooling | OpenFOAM | velocity/temperature fields |
| 6 | tolerance_analysis |
Worst-case and RSS tolerance stack-up | None (analytical) | tolerance report JSON |
| 7 | material_selection |
Select material from library with rationale | None (database) | material spec JSON |
| 8 | mechanical_bom |
Generate mechanical BOM from CAD model | FreeCAD | BOM CSV/JSON |
| 9 | dfa_analysis |
Design for Assembly scoring and feedback | None (analytical) | DFA report JSON |
3.3.2 Enclosure Generation Workflow
sequenceDiagram
participant OR as Orchestrator
participant MA as ME Agent
participant LLM as LLM Provider
participant FC as FreeCADAdapter
participant CX as CalculiXAdapter
participant S as Storage (MinIO)
OR->>MA: Execute(enclosure_generation, constraints)
MA->>LLM: "Generate FreeCAD Python script for enclosure"
Note over LLM: LLM receives PCB dims, mounting holes,<br/>thermal constraints, IP rating, material
LLM-->>MA: FreeCAD Python script
MA->>MA: Validate script (AST check, sandbox rules)
MA->>FC: executeScript(script)
FC->>FC: freecadcmd (headless)
FC-->>MA: .FCStd + .step + .stl
MA->>MA: Extract geometry metadata
MA->>CX: runStressFEA(mesh, loads)
CX-->>MA: stress results, safety factor
alt Safety factor < 1.5
MA->>LLM: "Revise: wall too thin, SF=1.2"
LLM-->>MA: Revised script
MA->>FC: executeScript(revised)
end
MA->>S: Store artifacts (.step, .stl, FEA report)
MA-->>OR: AgentResult {artifacts, metadata, traces}
3.4 Tool Adapters
3.4.1 FreeCADAdapter
Location: tools/freecad-adapter.ts
interface FreeCADAdapter extends ToolAdapter {
name: 'freecad';
version: string;
capabilities: ['parametric_modeling', 'step_export', 'stl_export', 'mesh_generation', 'boolean_ops'];
// Detection
detect(): Promise<ToolInstall | null>;
// Script execution (headless)
executeScript(script: string, args?: Record<string, string>): Promise<FreeCADResult>;
// Export operations
exportSTEP(modelPath: string, outputPath: string): Promise<string>;
exportSTL(modelPath: string, outputPath: string, meshParams?: MeshParams): Promise<string>;
// Mesh generation (for FEA)
generateMesh(modelPath: string, params: MeshParams): Promise<MeshResult>;
// Model inspection
getModelInfo(modelPath: string): Promise<ModelInfo>;
getBoundingBox(modelPath: string): Promise<BoundingBox>;
// Health
healthCheck(): Promise<HealthStatus>;
}
interface FreeCADResult {
exitCode: number;
stdout: string;
stderr: string;
outputFiles: string[]; // paths to generated files
modelInfo?: ModelInfo;
}
interface MeshParams {
algorithm: 'gmsh' | 'netgen';
maxElementSize: number; // mm
minElementSize: number; // mm
order: 1 | 2; // linear or quadratic elements
}
interface MeshResult {
meshFile: string; // path to .inp or .unv mesh file
nodeCount: number;
elementCount: number;
qualityMetrics: {
minAngle: number;
maxAspectRatio: number;
jacobianRatio: number;
};
}
interface ModelInfo {
volume: number; // mm³
surfaceArea: number; // mm²
boundingBox: BoundingBox;
centerOfMass: [number, number, number];
bodyCount: number;
}
interface BoundingBox {
min: [number, number, number];
max: [number, number, number];
dimensions: [number, number, number]; // width, depth, height in mm
}
Implementation sketch:
class FreeCADAdapter implements ToolAdapter {
name = 'freecad';
version = '1.0.0';
async detect(): Promise<ToolInstall | null> {
const searchPaths = [
'/usr/bin/freecadcmd',
'/usr/local/bin/freecadcmd',
'/snap/bin/freecadcmd',
'/Applications/FreeCAD.app/Contents/MacOS/FreeCADCmd',
'C:\\Program Files\\FreeCAD 0.21\\bin\\FreeCADCmd.exe',
];
for (const path of searchPaths) {
try {
const result = await exec(`${path} --version`);
const version = this.parseVersion(result.stdout);
if (semver.gte(version, '0.21.0')) {
return { found: true, version, path };
}
} catch { /* not found at this path */ }
}
return null;
}
async executeScript(script: string, args?: Record<string, string>): Promise<FreeCADResult> {
const scriptPath = await this.writeScript(script); // temp file
const argStr = args
? Object.entries(args).map(([k, v]) => `--${k} ${v}`).join(' ')
: '';
const result = await exec(
`freecadcmd ${scriptPath} -- ${argStr}`,
{ timeout: 120_000, cwd: this.workDir } // 2 min timeout
);
return {
exitCode: result.exitCode,
stdout: result.stdout,
stderr: result.stderr,
outputFiles: this.findOutputFiles(this.workDir),
};
}
async exportSTEP(modelPath: string, outputPath: string): Promise<string> {
const script = `
import FreeCAD
import Part
doc = FreeCAD.openDocument("${modelPath}")
Part.export(doc.Objects, "${outputPath}")
doc.close()
`;
await this.executeScript(script);
return outputPath;
}
async generateMesh(modelPath: string, params: MeshParams): Promise<MeshResult> {
const script = `
import FreeCAD
import Fem
import femmesh.femmesh2mesh
doc = FreeCAD.openDocument("${modelPath}")
shape = doc.Objects[0].Shape
femmesh = doc.addObject("Fem::FemMeshShapeNetgenObject", "FEMMesh")
femmesh.Shape = doc.Objects[0]
femmesh.MaxSize = ${params.maxElementSize}
femmesh.MinSize = ${params.minElementSize}
femmesh.SecondOrder = ${params.order === 2 ? 'True' : 'False'}
doc.recompute()
# Export as .inp for CalculiX
import feminout.importInpMesh
feminout.importInpMesh.export([femmesh], "${modelPath.replace('.FCStd', '.inp')}")
doc.close()
`;
const result = await this.executeScript(script);
const meshFile = modelPath.replace('.FCStd', '.inp');
return {
meshFile,
nodeCount: this.extractNodeCount(result.stdout),
elementCount: this.extractElementCount(result.stdout),
qualityMetrics: this.extractMeshQuality(result.stdout),
};
}
}
3.4.2 CalculiXAdapter
Location: tools/calculix-adapter.ts
interface CalculiXAdapter extends ToolAdapter {
name: 'calculix';
version: string;
capabilities: ['static_stress', 'modal_analysis', 'thermal_steady', 'thermal_transient'];
// Detection
detect(): Promise<ToolInstall | null>;
// FEA operations
runStaticStress(config: StaticStressConfig): Promise<StressResult>;
runModalAnalysis(config: ModalConfig): Promise<ModalResult>;
runThermalAnalysis(config: ThermalConfig): Promise<ThermalResult>;
// Low-level
execute(inpFile: string): Promise<CalculiXResult>;
parseResults(frdFile: string): Promise<FEAFieldData>;
}
interface StaticStressConfig {
meshFile: string; // .inp mesh file from FreeCAD
material: MaterialProperties;
loads: Load[];
constraints: BoundaryCondition[];
}
interface StressResult {
maxVonMises: number; // MPa
maxDisplacement: number; // mm
safetyFactor: number; // yield_strength / max_stress
stressField: string; // path to .frd results file
criticalLocations: CriticalLocation[];
passed: boolean; // safetyFactor >= minSafetyFactor
}
interface ModalConfig {
meshFile: string;
material: MaterialProperties;
constraints: BoundaryCondition[];
numModes: number; // number of modes to extract (typically 6-10)
}
interface ModalResult {
frequencies: number[]; // Hz, natural frequencies
modeShapes: string; // path to .frd mode shape data
participationFactors: number[];
passed: boolean; // no modes within excitation frequency range
}
interface ThermalConfig {
meshFile: string;
material: MaterialProperties;
heatSources: HeatSource[];
convection: ConvectionBC[];
radiation?: RadiationBC[];
analysisType: 'steady_state' | 'transient';
timeStep?: number; // seconds (for transient)
totalTime?: number; // seconds (for transient)
}
interface ThermalResult {
maxTemperature: number; // °C
minTemperature: number; // °C
temperatureField: string; // path to .frd results
heatFlux: number; // W/m²
thermalResistance: number; // °C/W
hotspots: Hotspot[];
passed: boolean; // maxTemp <= maxAllowable
}
interface Load {
type: 'force' | 'pressure' | 'gravity' | 'centrifugal';
magnitude: number;
direction?: [number, number, number];
surface?: string; // surface set name
}
interface BoundaryCondition {
type: 'fixed' | 'displacement' | 'symmetry';
surface: string; // surface set name
values?: [number, number, number];
}
interface HeatSource {
type: 'volume' | 'surface';
power: number; // Watts
location: string; // body or surface set name
}
interface ConvectionBC {
surface: string;
htc: number; // W/(m²·K), heat transfer coefficient
ambientTemp: number; // °C
}
interface Hotspot {
location: [number, number, number];
temperature: number; // °C
nearestComponent?: string;
}
Implementation sketch:
class CalculiXAdapter implements ToolAdapter {
name = 'calculix';
version = '1.0.0';
async detect(): Promise<ToolInstall | null> {
try {
const result = await exec('ccx -v');
const version = this.parseVersion(result.stdout);
return { found: true, version, path: await which('ccx') };
} catch {
return null;
}
}
async runStaticStress(config: StaticStressConfig): Promise<StressResult> {
// 1. Generate .inp file with material, loads, BCs
const inpContent = this.buildStaticInp(config);
const inpPath = path.join(this.workDir, 'stress_analysis.inp');
await writeFile(inpPath, inpContent);
// 2. Execute CalculiX
const result = await exec(`ccx -i ${inpPath.replace('.inp', '')}`, {
timeout: 300_000, // 5 min timeout
cwd: this.workDir,
});
// 3. Parse .frd results file
const frdPath = inpPath.replace('.inp', '.frd');
const fieldData = await this.parseResults(frdPath);
return {
maxVonMises: fieldData.maxVonMises,
maxDisplacement: fieldData.maxDisplacement,
safetyFactor: config.material.yieldStrength / fieldData.maxVonMises,
stressField: frdPath,
criticalLocations: fieldData.criticalLocations,
passed: (config.material.yieldStrength / fieldData.maxVonMises) >= 1.5,
};
}
private buildStaticInp(config: StaticStressConfig): string {
return `
*HEADING
MetaForge ME Agent - Static Stress Analysis
*INCLUDE, INPUT=${config.meshFile}
*MATERIAL, NAME=MATERIAL1
*ELASTIC
${config.material.youngsModulus}, ${config.material.poissonsRatio}
*DENSITY
${config.material.density}
*SOLID SECTION, ELSET=EALL, MATERIAL=MATERIAL1
*STEP
*STATIC
*BOUNDARY
${this.formatBoundaryConditions(config.constraints)}
*CLOAD
${this.formatLoads(config.loads)}
*NODE FILE
U
*EL FILE
S
*END STEP
`.trim();
}
}
3.4.3 OpenFOAMAdapter
Location: tools/openfoam-adapter.ts
interface OpenFOAMAdapter extends ToolAdapter {
name: 'openfoam';
version: string;
capabilities: ['cfd_thermal', 'flow_simulation', 'mesh_generation'];
// Detection
detect(): Promise<ToolInstall | null>;
// Case management
setupCase(config: CFDCaseConfig): Promise<string>; // returns case directory path
// Meshing
generateMesh(caseDir: string, geometry: string): Promise<OpenFOAMMeshResult>;
// Solving
runSolver(caseDir: string, solver: OpenFOAMSolver): Promise<CFDResult>;
// Post-processing
extractResults(caseDir: string): Promise<CFDFieldData>;
}
type OpenFOAMSolver = 'buoyantSimpleFoam' | 'simpleFoam' | 'buoyantPimpleFoam';
interface CFDCaseConfig {
solver: OpenFOAMSolver;
geometry: {
stlFile: string; // enclosure geometry as STL
inlets?: Surface[];
outlets?: Surface[];
walls?: Surface[];
};
thermalSources: ThermalSource[];
fluidProperties: FluidProperties;
meshSettings: {
baseCellSize: number; // mm
refinementLevels: number;
boundaryLayers: number;
};
}
interface ThermalSource {
name: string;
type: 'fixedTemperature' | 'fixedHeatFlux';
value: number; // °C or W/m²
surface: string; // patch name
}
interface FluidProperties {
fluid: 'air' | 'water' | 'custom';
density?: number; // kg/m³
viscosity?: number; // Pa·s
thermalConductivity?: number; // W/(m·K)
specificHeat?: number; // J/(kg·K)
beta?: number; // 1/K, thermal expansion coefficient
}
interface CFDResult {
converged: boolean;
iterations: number;
residuals: { [field: string]: number };
maxVelocity: number; // m/s
maxTemperature: number; // °C
avgTemperature: number; // °C
heatTransferRate: number; // W
pressureDrop?: number; // Pa
results: CFDFieldData;
}
interface CFDFieldData {
temperatureField: string; // path to OpenFOAM field data
velocityField: string;
pressureField: string;
hotspots: Hotspot[];
}
Implementation sketch:
class OpenFOAMAdapter implements ToolAdapter {
name = 'openfoam';
version = '1.0.0';
async detect(): Promise<ToolInstall | null> {
try {
const result = await exec('simpleFoam -help');
// OpenFOAM prints version in the banner
const version = this.parseVersion(result.stdout);
return { found: true, version, path: process.env.WM_PROJECT_DIR || '' };
} catch {
return null;
}
}
async setupCase(config: CFDCaseConfig): Promise<string> {
const caseDir = path.join(this.workDir, 'cfd_case');
await mkdir(caseDir, { recursive: true });
// Create OpenFOAM directory structure
await mkdir(path.join(caseDir, '0')); // initial conditions
await mkdir(path.join(caseDir, 'constant')); // mesh + properties
await mkdir(path.join(caseDir, 'system')); // solver settings
// Write dictionary files
await this.writeTransportProperties(caseDir, config.fluidProperties);
await this.writeFvSchemes(caseDir);
await this.writeFvSolution(caseDir, config.solver);
await this.writeControlDict(caseDir, config.solver);
await this.writeInitialConditions(caseDir, config);
await this.writeSnappyHexMeshDict(caseDir, config);
return caseDir;
}
async generateMesh(caseDir: string, stlFile: string): Promise<OpenFOAMMeshResult> {
// 1. Block mesh (background mesh)
await exec('blockMesh', { cwd: caseDir, timeout: 60_000 });
// 2. Copy STL to triSurface
const triDir = path.join(caseDir, 'constant', 'triSurface');
await mkdir(triDir, { recursive: true });
await copyFile(stlFile, path.join(triDir, 'enclosure.stl'));
// 3. snappyHexMesh
await exec('snappyHexMesh -overwrite', { cwd: caseDir, timeout: 300_000 });
// 4. Check mesh quality
const checkResult = await exec('checkMesh', { cwd: caseDir });
return this.parseMeshCheck(checkResult.stdout);
}
async runSolver(caseDir: string, solver: OpenFOAMSolver): Promise<CFDResult> {
const result = await exec(solver, {
cwd: caseDir,
timeout: 600_000, // 10 min timeout
});
return {
converged: this.checkConvergence(result.stdout),
iterations: this.extractIterations(result.stdout),
residuals: this.extractResiduals(result.stdout),
...await this.extractResults(caseDir),
};
}
}
4. Data Model (Pydantic Schemas)
4.1 Input Schema
# domain_agents/mechanical/schema.py
from __future__ import annotations
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field
# ─── Sub-schemas ───
class Dimensions(BaseModel):
x: float = Field(gt=0, description="Width in mm")
y: float = Field(gt=0, description="Depth in mm")
z: float = Field(gt=0, description="Height in mm")
unit: str = Field(default="mm")
class MountingHole(BaseModel):
x: float = Field(description="X position in mm from origin")
y: float = Field(description="Y position in mm from origin")
diameter: float = Field(gt=0, description="Hole diameter in mm")
type: str = Field(default="through", pattern="^(through|threaded|standoff)$")
thread_spec: Optional[str] = Field(default=None, description='e.g. "M3x0.5"')
class ConnectorCutout(BaseModel):
name: str = Field(description='e.g. "USB-C", "barrel jack"')
width: float = Field(gt=0)
height: float = Field(gt=0)
face: str = Field(pattern="^(front|back|left|right|top|bottom)$")
x: float
y: float
class ComponentHeight(BaseModel):
top: float = Field(gt=0, description="Max component height above PCB, mm")
bottom: float = Field(ge=0, description="Max component height below PCB, mm")
class PCBConstraints(BaseModel):
dimensions: Dimensions = Field(description="PCB outline dimensions")
mounting_holes: list[MountingHole] = Field(min_length=1)
max_component_height: ComponentHeight
connector_cutouts: list[ConnectorCutout] = Field(default_factory=list)
clearance: float = Field(default=2, gt=0, description="Min clearance around PCB, mm")
class HeatSource(BaseModel):
name: str
power: float = Field(gt=0, description="Watts")
position: tuple[float, float]
footprint: tuple[float, float] = Field(description="(width, depth)")
class CoolingMethod(str, Enum):
natural_convection = "natural_convection"
forced_convection = "forced_convection"
heatsink = "heatsink"
liquid = "liquid"
class ThermalConstraints(BaseModel):
total_dissipation: float = Field(gt=0, description="Total power dissipation in Watts")
heat_sources: list[HeatSource] = Field(min_length=1)
max_junction_temp: float = Field(default=85, description="Max junction temperature degC")
ambient_temp: float = Field(default=25, description="Ambient temperature degC")
cooling_method: CoolingMethod = CoolingMethod.natural_convection
class FrequencyRange(BaseModel):
min: float
max: float
class VibrationSpec(BaseModel):
g_rms: Optional[float] = Field(default=None, description="Random vibration level")
frequency_range: Optional[FrequencyRange] = None
class ShockSpec(BaseModel):
peak_g: Optional[float] = None
duration: Optional[float] = Field(default=None, description="ms")
class IPRating(str, Enum):
IP20 = "IP20"
IP44 = "IP44"
IP54 = "IP54"
IP65 = "IP65"
IP67 = "IP67"
IP68 = "IP68"
class TemperatureRange(BaseModel):
min: float = Field(default=-20, description="degC")
max: float = Field(default=60, description="degC")
class EnvironmentalConstraints(BaseModel):
ip_rating: IPRating = IPRating.IP20
temperature_range: TemperatureRange = Field(default_factory=TemperatureRange)
vibration: Optional[VibrationSpec] = None
shock: Optional[ShockSpec] = None
altitude: Optional[float] = Field(default=None, description="Max altitude in meters")
class ManufacturingMethod(str, Enum):
fdm = "3d_print_fdm"
sla = "3d_print_sla"
sls = "3d_print_sls"
injection_molding = "injection_molding"
cnc_machining = "cnc_machining"
sheet_metal = "sheet_metal"
casting = "casting"
class JoinMethod(str, Enum):
snap_fit = "snap_fit"
screws = "screws"
ultrasonic_weld = "ultrasonic_weld"
adhesive = "adhesive"
gasket = "gasket"
class SurfaceFinish(str, Enum):
as_printed = "as_printed"
sanded = "sanded"
painted = "painted"
textured = "textured"
polished = "polished"
class MechanicalRequirements(BaseModel):
material: Optional[str] = Field(default=None, description='Preferred material or "auto"')
manufacturing_method: ManufacturingMethod = ManufacturingMethod.fdm
wall_thickness: Optional[float] = Field(default=None, gt=0, description="Min wall thickness mm")
join_method: JoinMethod = JoinMethod.screws
surface_finish: Optional[SurfaceFinish] = None
min_safety_factor: float = Field(default=2.0, gt=0)
class TaskType(str, Enum):
enclosure_generation = "enclosure_generation"
fea_stress = "fea_stress"
fea_modal = "fea_modal"
fea_thermal = "fea_thermal"
cfd_thermal = "cfd_thermal"
tolerance_analysis = "tolerance_analysis"
material_selection = "material_selection"
mechanical_bom = "mechanical_bom"
dfa_analysis = "dfa_analysis"
class ProjectPhase(str, Enum):
POC = "POC"
EVT = "EVT"
DVT = "DVT"
PVT = "PVT"
# ─── Top-level Input ───
class MechanicalInput(BaseModel):
task_type: TaskType
pcb: Optional[PCBConstraints] = None
thermal: Optional[ThermalConstraints] = None
environmental: Optional[EnvironmentalConstraints] = None
mechanical: Optional[MechanicalRequirements] = None
existing_model: Optional[str] = Field(default=None, description="Path to .FCStd or .step file")
project_phase: ProjectPhase = ProjectPhase.EVT
4.2 Output Schema
# ─── Output Sub-schemas ───
class CADArtifact(BaseModel):
fcstd_path: str = Field(description="Path to FreeCAD native file")
step_path: str = Field(description="Path to STEP file")
stl_path: str = Field(description="Path to STL mesh file")
volume: float = Field(gt=0, description="Part volume in mm3")
surface_area: float = Field(gt=0, description="Surface area in mm2")
bounding_box: Dimensions
mass_grams: float = Field(gt=0, description="Estimated mass in grams")
class MeshInfo(BaseModel):
node_count: int = Field(gt=0)
element_count: int = Field(gt=0)
element_type: str
class CriticalLocation(BaseModel):
position: tuple[float, float, float]
stress: float
description: str
class StaticStressResults(BaseModel):
analysis_type: str = Field(default="static_stress")
max_von_mises: float = Field(description="MPa")
max_displacement: float = Field(description="mm")
safety_factor: float
critical_locations: list[CriticalLocation]
class ModalResults(BaseModel):
analysis_type: str = Field(default="modal")
natural_frequencies: list[float] = Field(description="Hz")
mode_descriptions: list[str]
class Hotspot(BaseModel):
position: tuple[float, float, float]
temperature: float
nearest_component: Optional[str] = None
class ThermalResults(BaseModel):
analysis_type: str = Field(default="thermal")
max_temperature: float = Field(description="degC")
min_temperature: float = Field(description="degC")
thermal_resistance: float = Field(description="degC/W")
hotspots: list[Hotspot]
class FEAResult(BaseModel):
analysis_type: str = Field(pattern="^(static_stress|modal|thermal)$")
solver: str = Field(default="calculix")
mesh_info: MeshInfo
results: StaticStressResults | ModalResults | ThermalResults
passed: bool
results_file_path: str = Field(description="Path to .frd results file")
class ToleranceDimension(BaseModel):
name: str
nominal: float = Field(description="mm")
tolerance: float = Field(description="+/- mm")
distribution: str = Field(default="normal", pattern="^(uniform|normal)$")
class WorstCaseResult(BaseModel):
gap_min: float = Field(description="mm")
gap_max: float = Field(description="mm")
passed: bool
class RSSResult(BaseModel):
gap_mean: float = Field(description="mm")
gap_std_dev: float = Field(description="mm")
cpk: float = Field(description="Process capability index")
yield_percent: float = Field(description="Expected yield %")
passed: bool
class ToleranceStack(BaseModel):
name: str
dimensions: list[ToleranceDimension]
worst_case: WorstCaseResult
rss: RSSResult
class MaterialProperties(BaseModel):
density: float = Field(description="g/cm3")
youngs_modulus: float = Field(description="GPa")
yield_strength: float = Field(description="MPa")
tensile_strength: float = Field(description="MPa")
elongation: float = Field(description="% at break")
thermal_conductivity: float = Field(description="W/(m*K)")
specific_heat: float = Field(description="J/(kg*K)")
cte: float = Field(description="um/(m*K), coeff. of thermal expansion")
max_service_temp: float = Field(description="degC")
flammability: Optional[str] = Field(default=None, description="UL94 rating")
class MaterialAlternative(BaseModel):
name: str
tradeoff: str
class SupplierInfo(BaseModel):
trade_name: Optional[str] = None
manufacturer: Optional[str] = None
unit_cost: Optional[float] = Field(default=None, description="$/kg")
class MaterialSpec(BaseModel):
name: str
category: str = Field(pattern="^(polymer|metal|ceramic|composite)$")
grade: Optional[str] = None
properties: MaterialProperties
rationale: str = Field(description="Why this material was selected")
alternatives: list[MaterialAlternative] = Field(default_factory=list)
supplier_info: Optional[SupplierInfo] = None
class MechanicalBOMEntry(BaseModel):
item_number: int = Field(gt=0)
part_name: str
material: str
quantity: int = Field(gt=0)
manufacturing_method: str
estimated_cost: float = Field(description="USD per unit")
weight: float = Field(description="grams")
drawing: Optional[str] = Field(default=None, description="Path to drawing file")
class DFAIssue(BaseModel):
severity: str = Field(pattern="^(critical|warning|info)$")
part: str
issue: str
recommendation: str
class DFAScore(BaseModel):
total_parts: int = Field(gt=0)
theoretical_min_parts: int = Field(gt=0)
dfa_index: float = Field(ge=0, le=100, description="DFA efficiency score")
assembly_time: float = Field(description="Estimated assembly time in seconds")
issues: list[DFAIssue]
class DigitalThreadLinks(BaseModel):
requirements_traced: list[str] = Field(description="Requirement IDs addressed")
design_elements: list[str] = Field(description="Design element IDs created")
validation_evidence: list[str] = Field(description="FEA/test result IDs")
# ─── Top-level Output ───
class MechanicalOutput(BaseModel):
task_type: str
cad_artifacts: Optional[CADArtifact] = None
fea_results: Optional[FEAResult] = None
tolerance_stacks: Optional[list[ToleranceStack]] = None
material_spec: Optional[MaterialSpec] = None
mechanical_bom: Optional[list[MechanicalBOMEntry]] = None
dfa_score: Optional[DFAScore] = None
digital_thread: DigitalThreadLinks
warnings: list[str] = Field(default_factory=list)
recommendations: list[str] = Field(default_factory=list)
5. Feature Prioritization (MoSCoW)
Must Have (P0) — Required for ME Agent MVP
| Feature | Description | Dependency |
|---|---|---|
| Enclosure generation | LLM generates FreeCAD script from PCB constraints → STEP/STL output | FreeCADAdapter |
| FreeCAD adapter | Headless script execution, STEP/STL export, mesh generation | FreeCAD 0.21+ |
| CalculiX adapter | Static stress FEA via .inp files, result parsing |
CalculiX 2.21+ |
| Material selection | Select from built-in library (30+ materials) with documented rationale | Material DB |
| Tolerance stack-up | Worst-case + RSS analysis, Cpk calculation | None (analytical) |
| Mechanical BOM | Extract BOM from CAD model with cost estimates | FreeCADAdapter |
| Multi-provider LLM | Anthropic + OpenAI provider abstraction (LLM Provider) | anthropic, openai |
| Pydantic validation | Input/output schema validation for all task types | pydantic |
Should Have (P1) — Expected for Phase 2 release
| Feature | Description | Dependency |
|---|---|---|
| OpenFOAM adapter | CFD thermal simulation for airflow/cooling analysis | OpenFOAM v11+ |
| CFD thermal analysis | Predict hotspots, airflow patterns in enclosures | OpenFOAMAdapter |
| DFA analysis | Design for Assembly scoring and improvement recommendations | None (analytical) |
| STEP/STL export | Multi-format export with configurable mesh quality | FreeCADAdapter |
| FEA thermal | Steady-state thermal analysis via CalculiX | CalculiXAdapter |
Could Have (P2) — If time permits
| Feature | Description | Dependency |
|---|---|---|
| Modal/vibration analysis | Natural frequency extraction, harmonic response | CalculiXAdapter |
| Impact/drop simulation | Transient FEA for drop test prediction | CalculiXAdapter |
| 2D tolerance stack visualization | Generate tolerance stack-up diagram as SVG | None |
| 3D tolerance analysis | GD&T-aware tolerance analysis | FreeCADAdapter |
| Parametric sweep | Run multiple design variants and compare FEA results | All adapters |
Won’t Have (Out of Scope)
| Feature | Rationale |
|---|---|
| Complex multi-body assemblies | Requires assembly solver; defer to Phase 3 |
| Injection mold tooling design | Specialized DFM; separate agent scope |
| Sheet metal unfolding/DFM | Requires dedicated FreeCAD Sheet Metal workbench integration |
| Fluid-structure interaction (FSI) | Coupled FEA+CFD; too complex for initial release |
| Topology optimization | Experimental in FreeCAD; defer to Phase 3 |
6. Integration Points
6.1 Upstream Dependencies
flowchart LR
REQ[REQ Agent<br/>Constraints] --> ME
EE[EE Agent<br/>PCB Outline, Thermal Map] --> ME
ID[ID Agent<br/>Form Factor, Aesthetics] --> ME
SYS[SYS Agent<br/>Interface Definitions] --> ME
ME[ME Agent]
style ME fill:#9b59b6,color:#fff
style REQ fill:#3498db,color:#fff
style EE fill:#3498db,color:#fff
style ID fill:#3498db,color:#fff
style SYS fill:#3498db,color:#fff
| Source Agent | Data Consumed | Format |
|---|---|---|
| REQ | Mechanical constraints (dimensions, IP rating, temp range, vibration) | constraints.json → MechanicalConstraints |
| EE | PCB outline dimensions, mounting holes, component heights, thermal dissipation map | pcb-constraints.json → PCBConstraintsSchema |
| ID | Form factor envelope, aesthetic constraints, material palette | form-factor.json → EnvironmentalConstraintsSchema |
| SYS | Mechanical interface definitions (mounting, connectors, cable routing) | interfaces.json → ConnectorCutoutSchema |
6.2 Downstream Consumers
flowchart LR
ME[ME Agent] --> MFG[MFG Agent<br/>STEP + BOM for DFM]
ME --> SIM[SIM Agent<br/>Mesh for Multi-physics]
ME --> TST[TST Agent<br/>Test Specifications]
ME --> SC[SC Agent<br/>Mechanical BOM]
ME --> COST[Cost Agent<br/>Material + Mfg Estimates]
style ME fill:#9b59b6,color:#fff
style MFG fill:#27ae60,color:#fff
style SIM fill:#27ae60,color:#fff
style TST fill:#27ae60,color:#fff
style SC fill:#27ae60,color:#fff
style COST fill:#27ae60,color:#fff
| Target Agent | Data Provided | Format |
|---|---|---|
| MFG | STEP file + mechanical BOM for DFM review | .step, mechanical-bom.json |
| SIM | FEA mesh for multi-physics coupled simulation | .inp mesh, .frd results |
| TST | Mechanical test specifications (drop, vibration, thermal cycling) | mech-test-spec.json |
| SC | Mechanical BOM (fasteners, gaskets, enclosure parts) | mechanical-bom.csv |
| Cost | Material costs, manufacturing method estimates | cost-estimate.json |
6.3 Digital Thread Integration
The ME Agent links all artifacts to the Neo4j digital thread:
// Requirement → Design Element → Material → FEA Validation
MATCH (req:Requirement {id: 'REQ-MECH-001'})
CREATE (design:DesignElement {
id: 'DE-ENC-001',
type: 'enclosure',
cadFile: 'enclosure_v1.step',
agent: 'mechanical-agent'
})
CREATE (mat:Material {
name: 'ABS',
grade: 'Ultramid A3WG6',
yieldStrength: 85
})
CREATE (fea:ValidationEvidence {
id: 'FEA-001',
type: 'static_stress',
safetyFactor: 2.8,
passed: true,
solver: 'calculix'
})
CREATE (req)-[:IMPLEMENTED_BY]->(design)
CREATE (design)-[:USES_MATERIAL]->(mat)
CREATE (design)-[:VALIDATED_BY]->(fea)
7. Implementation Phases (14 Weeks)
Phase A: Foundation (Weeks 1-3)
Goal: Multi-provider LLM layer + base agent infrastructure + FreeCAD adapter
| Week | Deliverables |
|---|---|
| Wk 1 | LLMProvider interface, AnthropicProvider, OpenAIProvider, LLMProviderFactory, unit tests |
| Wk 2 | FreeCADAdapter: detection, headless script execution, STEP/STL export, mesh generation |
| Wk 3 | MechanicalAgent base class: task routing, input/output Pydantic schemas, agent lifecycle |
Exit Criteria:
LLMProviderFactory.create('anthropic')returns working providerFreeCADAdapter.detect()finds FreeCAD installationFreeCADAdapter.executeScript()runs a hello-world script headlessly- All Pydantic schemas pass type checking
Phase B: Core ME Agent (Weeks 4-6)
Goal: Enclosure generation + material selection + mechanical BOM
| Week | Deliverables |
|---|---|
| Wk 4 | Enclosure generation: LLM prompt engineering, FreeCAD script templates, parametric enclosure generation from PCB constraints |
| Wk 5 | Material selection engine: material library (30+ entries), selection algorithm, rationale generation |
| Wk 6 | Mechanical BOM generation: extract parts from FreeCAD model, cost estimation, BOM export (CSV/JSON) |
Exit Criteria:
- Given PCB dimensions + mounting holes → generates valid STEP enclosure in <15 min
- Material selection returns documented rationale with alternatives
- Mechanical BOM exports with correct item count and estimated costs
Phase C: FEA Integration (Weeks 7-9)
Goal: CalculiX adapter + tolerance analysis + FEA workflows
| Week | Deliverables |
|---|---|
| Wk 7 | CalculiXAdapter: detection, .inp file generation, solver execution, .frd parsing |
| Wk 8 | Static stress FEA workflow: mesh from FreeCAD → CalculiX → safety factor report |
| Wk 9 | Tolerance stack-up analysis: worst-case + RSS calculation, Cpk, yield prediction |
Exit Criteria:
- CalculiX adapter runs static stress analysis on generated enclosure
- Safety factor calculation matches manual verification within 5%
- Tolerance analysis produces correct worst-case and RSS results
Phase D: CFD & DFA (Weeks 10-12)
Goal: OpenFOAM adapter + DFA analysis + integration testing
| Week | Deliverables |
|---|---|
| Wk 10 | OpenFOAMAdapter: case setup, snappyHexMesh, buoyantSimpleFoam execution |
| Wk 11 | CFD thermal workflow: enclosure STL → mesh → thermal simulation → hotspot report |
| Wk 12 | DFA analysis engine + full integration testing (enclosure → FEA → CFD → BOM pipeline) |
Exit Criteria:
- OpenFOAM adapter runs thermal CFD on enclosure geometry
- DFA analysis produces actionable recommendations
- End-to-end pipeline: PCB constraints → enclosure → FEA → BOM completes successfully
Phase E: Hardening & Documentation (Weeks 13-14)
Goal: Error handling, CLI integration, documentation
| Week | Deliverables |
|---|---|
| Wk 13 | Error handling: retry logic, timeout management, graceful degradation when tools unavailable. CLI commands: forge run mechanical-enclosure, forge run mechanical-fea |
| Wk 14 | Documentation: user guide, API reference, example workflows. Performance optimization, edge case testing |
Exit Criteria:
- All error paths tested with mocked tool failures
- CLI commands registered and documented
-
80% code coverage on agent and adapter code
Timeline Visualization
gantt
title ME Agent Implementation (14 Weeks)
dateFormat YYYY-MM-DD
axisFormat %b %d
section Phase A: Foundation
LLM Provider Layer :a1, 2026-01-05, 7d
FreeCAD Adapter :a2, after a1, 7d
ME Agent Base + Schemas :a3, after a2, 7d
section Phase B: Core
Enclosure Generation :b1, after a3, 7d
Material Selection :b2, after b1, 7d
Mechanical BOM :b3, after b2, 7d
section Phase C: FEA
CalculiX Adapter :c1, after b3, 7d
Stress FEA Workflow :c2, after c1, 7d
Tolerance Analysis :c3, after c2, 7d
section Phase D: CFD & DFA
OpenFOAM Adapter :d1, after c3, 7d
CFD Thermal Workflow :d2, after d1, 7d
DFA + Integration Tests :d3, after d2, 7d
section Phase E: Hardening
Error Handling + CLI :e1, after d3, 7d
Docs + Optimization :e2, after e1, 7d
8. Success Metrics & KPIs
8.1 Performance Targets
| Metric | Target | Measurement |
|---|---|---|
| Enclosure generation time | < 15 minutes | Time from input submission to STEP output |
| FEA analysis time | < 10 minutes | Mesh generation + CalculiX solve + result parsing |
| CFD analysis time | < 20 minutes | Mesh generation + OpenFOAM solve + post-processing |
| Tolerance analysis time | < 30 seconds | Analytical calculation |
| Material selection time | < 10 seconds | Database query + LLM rationale |
| First-pass success rate | > 70% | Enclosures that pass FEA without revision |
| FEA accuracy | < 10% deviation | Compared to commercial FEA on reference models |
8.2 Quality Targets
| Metric | Target | Measurement |
|---|---|---|
| STEP file validity | 100% | Imports successfully in any CAD tool |
| FEA convergence rate | > 95% | CalculiX solves without divergence |
| Schema validation pass rate | 100% | All outputs pass Pydantic validation |
| Digital thread completeness | > 90% | All artifacts linked in Neo4j |
| Test coverage | > 80% | Unit + integration tests |
8.3 Business Impact Targets
| Metric | Target | Measurement |
|---|---|---|
| Enclosure design cycle reduction | > 90% | Weeks → hours |
| Simulation cost reduction | 100% | $0 license cost vs. $30K+ |
| Prototype iteration speed | 2x improvement | Fewer physical prototyping rounds |
9. Risk Matrix
| # | Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|---|
| R1 | LLM-generated FreeCAD scripts fail — syntax errors, invalid geometry, boolean operation failures | High | High | Script template library, AST validation before execution, self-healing retry loop with error feedback to LLM, curated test cases |
| R2 | FreeCAD headless instability — crashes, memory leaks, version incompatibilities | Medium | High | Docker container isolation, version pinning (0.21.2), process watchdog with timeout, fallback to script templates |
| R3 | CalculiX convergence failures — ill-conditioned mesh, inappropriate element types | Medium | Medium | Mesh quality pre-checks, auto mesh refinement, convergence monitoring, timeout + fallback to coarser mesh |
| R4 | OpenFOAM setup complexity — dictionary file errors, mesh quality issues | Medium | Medium | Template case directories, validated dictionary generators, mesh quality checks via checkMesh |
| R5 | LLM hallucinated material properties — incorrect values for strength, thermal conductivity | Low | High | Verified material database (not LLM-generated), cross-reference with MatWeb/MATWEB data, unit sanity checks |
| R6 | STEP export compatibility — FreeCAD STEP files not importing in downstream tools | Low | Medium | STEP AP214 compliance testing, reference model validation against SOLIDWORKS/Fusion 360 import |
| R7 | Performance degradation — FEA/CFD taking too long for large models | Medium | Medium | Element count limits, adaptive mesh coarsening, timeout with partial results, background execution |
| R8 | Security: arbitrary code execution — LLM generates malicious FreeCAD Python scripts | Low | Critical | Script sandbox (no os, subprocess, socket imports), AST whitelist, filesystem isolation via Docker |
Risk Mitigation Architecture
flowchart TD
A[LLM generates script] --> B{AST Validation}
B -->|Blocked imports| C[Reject + retry]
B -->|Clean| D{Sandbox Execution}
D -->|Timeout| E[Kill process + retry with simpler geometry]
D -->|Crash| F[Log error + retry with template fallback]
D -->|Success| G{Output Validation}
G -->|Invalid geometry| H[Feed error back to LLM]
G -->|Valid| I[Accept output]
style B fill:#f39c12,color:#000
style D fill:#e74c3c,color:#fff
style G fill:#27ae60,color:#fff
10. Material Library (Reference)
The ME Agent includes a verified material database. Properties are sourced from manufacturer datasheets, not LLM-generated.
10.1 Polymers
| Material | Density (g/cm³) | Yield (MPa) | Tensile (MPa) | E (GPa) | Tmax (°C) | k (W/m·K) | CTE (µm/m·K) | Use Case |
|---|---|---|---|---|---|---|---|---|
| ABS | 1.04 | 42 | 44 | 2.3 | 80 | 0.17 | 90 | General enclosures, prototyping |
| ABS+PC | 1.10 | 50 | 55 | 2.5 | 100 | 0.20 | 75 | Impact-resistant enclosures |
| PLA | 1.24 | 60 | 65 | 3.5 | 55 | 0.13 | 68 | Low-cost prototyping only |
| PETG | 1.27 | 50 | 53 | 2.2 | 70 | 0.15 | 60 | Chemical-resistant enclosures |
| Nylon PA6 | 1.14 | 70 | 85 | 2.8 | 120 | 0.25 | 80 | High-temp, snap fits |
| Nylon PA6-GF30 | 1.36 | 120 | 160 | 9.5 | 140 | 0.35 | 30 | Structural, high-temp |
| PC | 1.20 | 62 | 66 | 2.4 | 130 | 0.20 | 65 | Transparent enclosures, high impact |
| PP | 0.91 | 35 | 38 | 1.5 | 100 | 0.12 | 100 | Chemical resistance, living hinges |
| POM (Acetal) | 1.41 | 65 | 70 | 3.0 | 90 | 0.31 | 110 | Gears, precision mechanisms |
| PEEK | 1.32 | 100 | 105 | 4.0 | 250 | 0.25 | 47 | Extreme environments |
10.2 Metals
| Material | Density (g/cm³) | Yield (MPa) | Tensile (MPa) | E (GPa) | Tmax (°C) | k (W/m·K) | CTE (µm/m·K) | Use Case |
|---|---|---|---|---|---|---|---|---|
| Al 6061-T6 | 2.70 | 276 | 310 | 69 | 150 | 167 | 23.6 | CNC enclosures, heatsinks |
| Al 5052-H32 | 2.68 | 193 | 228 | 70 | 150 | 138 | 23.8 | Sheet metal enclosures |
| Al 7075-T6 | 2.81 | 503 | 572 | 72 | 150 | 130 | 23.4 | High-strength structural |
| SS 304 | 8.00 | 215 | 505 | 193 | 800 | 16.2 | 17.3 | Corrosion-resistant enclosures |
| SS 316L | 8.00 | 170 | 485 | 193 | 800 | 16.3 | 15.9 | Medical, marine |
| Carbon Steel 1018 | 7.87 | 370 | 440 | 205 | 400 | 51.9 | 11.7 | General structural |
| Copper C110 | 8.94 | 69 | 221 | 117 | 200 | 391 | 17.0 | Heatsinks, bus bars |
| Brass C360 | 8.50 | 310 | 385 | 97 | 200 | 115 | 20.5 | Connectors, fittings |
| Ti-6Al-4V | 4.43 | 880 | 950 | 114 | 350 | 6.7 | 8.6 | Aerospace, medical implants |
10.3 Composites & Specialty
| Material | Density (g/cm³) | Yield (MPa) | Tensile (MPa) | E (GPa) | Tmax (°C) | k (W/m·K) | Use Case |
|---|---|---|---|---|---|---|---|
| FR-4 (PCB substrate) | 1.85 | – | 310 | 22 | 130 | 0.29 | Reference for thermal analysis |
| CFRP (quasi-iso) | 1.55 | – | 600 | 70 | 150 | 5.0 | Lightweight structural |
| Silicone (60A) | 1.15 | – | 8 | 0.005 | 200 | 0.20 | Gaskets, seals |
| Thermal pad | 2.0-3.0 | – | – | – | 200 | 1.0-6.0 | Thermal interface materials |
11. Reference: FreeCAD Enclosure Script
Example LLM-generated FreeCAD Python script for enclosure generation:
"""
MetaForge ME Agent — Enclosure Generation Script
Generated for: IoT Sensor Enclosure
PCB: 60mm x 40mm, 4x M3 mounting holes
IP Rating: IP54
Material: ABS
"""
import FreeCAD
import Part
import Mesh
# ─── Parameters (from MechanicalInput) ───
PCB_WIDTH = 60.0 # mm
PCB_DEPTH = 40.0 # mm
PCB_HEIGHT = 1.6 # mm
CLEARANCE = 2.0 # mm around PCB
WALL = 2.0 # mm wall thickness
COMP_HEIGHT_TOP = 12.0 # mm max component height above PCB
COMP_HEIGHT_BOT = 2.0 # mm max component height below PCB
# Derived dimensions
INNER_W = PCB_WIDTH + 2 * CLEARANCE
INNER_D = PCB_DEPTH + 2 * CLEARANCE
INNER_H_BOT = COMP_HEIGHT_BOT + PCB_HEIGHT + 2.0 # bottom half
INNER_H_TOP = COMP_HEIGHT_TOP + 2.0 # top half
OUTER_W = INNER_W + 2 * WALL
OUTER_D = INNER_D + 2 * WALL
# Mounting hole positions (relative to PCB origin)
MOUNT_HOLES = [
(3.5, 3.5),
(3.5, 36.5),
(56.5, 3.5),
(56.5, 36.5),
]
MOUNT_HOLE_DIA = 3.2 # M3 clearance
# ─── Create Document ───
doc = FreeCAD.newDocument("Enclosure")
# ─── Bottom Shell ───
outer_bot = Part.makeBox(OUTER_W, OUTER_D, INNER_H_BOT + WALL)
inner_bot = Part.makeBox(INNER_W, INNER_D, INNER_H_BOT)
inner_bot.translate(FreeCAD.Vector(WALL, WALL, WALL))
bottom_shell = outer_bot.cut(inner_bot)
# Add PCB standoffs
for (hx, hy) in MOUNT_HOLES:
standoff_x = hx + CLEARANCE + WALL
standoff_y = hy + CLEARANCE + WALL
standoff = Part.makeCylinder(3.0, COMP_HEIGHT_BOT + WALL,
FreeCAD.Vector(standoff_x, standoff_y, WALL))
hole = Part.makeCylinder(MOUNT_HOLE_DIA / 2, COMP_HEIGHT_BOT + WALL + 1,
FreeCAD.Vector(standoff_x, standoff_y, WALL - 0.5))
standoff = standoff.cut(hole)
bottom_shell = bottom_shell.fuse(standoff)
# ─── Top Shell ───
outer_top = Part.makeBox(OUTER_W, OUTER_D, INNER_H_TOP + WALL)
inner_top = Part.makeBox(INNER_W, INNER_D, INNER_H_TOP)
inner_top.translate(FreeCAD.Vector(WALL, WALL, 0))
top_shell = outer_top.cut(inner_top)
# ─── Connector Cutout (USB-C on back face) ───
usbc_cutout = Part.makeBox(10.0, WALL + 2, 4.0)
usbc_cutout.translate(FreeCAD.Vector(
OUTER_W / 2 - 5.0, # centered
OUTER_D - WALL - 1, # back face
WALL + 2.0 # height from bottom
))
bottom_shell = bottom_shell.cut(usbc_cutout)
# ─── Ventilation Slots (IP54: dust-protected) ───
for i in range(4):
slot = Part.makeBox(15.0, 1.0, WALL + 2)
slot.translate(FreeCAD.Vector(
OUTER_W / 2 - 7.5,
WALL - 1,
INNER_H_TOP - 3.0 - i * 3.0
))
top_shell = top_shell.cut(slot)
# ─── Add Parts to Document ───
bottom_part = doc.addObject("Part::Feature", "BottomShell")
bottom_part.Shape = bottom_shell
top_part = doc.addObject("Part::Feature", "TopShell")
top_part.Shape = top_shell
doc.recompute()
# ─── Export ───
doc.saveAs("/output/enclosure.FCStd")
Part.export([bottom_part, top_part], "/output/enclosure.step")
Mesh.export([bottom_part, top_part], "/output/enclosure.stl")
print(f"Volume (bottom): {bottom_shell.Volume:.1f} mm³")
print(f"Volume (top): {top_shell.Volume:.1f} mm³")
print(f"Bounding box: {OUTER_W} x {OUTER_D} x {INNER_H_BOT + INNER_H_TOP + 2*WALL} mm")
print("Export complete.")
12. Glossary
| Term | Definition |
|---|---|
| CAD | Computer-Aided Design — 3D modeling software |
| CAE | Computer-Aided Engineering — simulation and analysis |
| CFD | Computational Fluid Dynamics — airflow/thermal fluid simulation |
| Cpk | Process Capability Index — statistical measure of manufacturing precision |
| CTE | Coefficient of Thermal Expansion |
| DFA | Design for Assembly — methodology for reducing assembly complexity |
| DFM | Design for Manufacturability |
| FEA | Finite Element Analysis — structural/thermal simulation |
| FEM | Finite Element Method — mathematical approach underlying FEA |
| GD&T | Geometric Dimensioning and Tolerancing |
| IP rating | Ingress Protection — dust/water resistance standard (IEC 60529) |
| RSS | Root Sum of Squares — statistical tolerance analysis method |
| STEP | Standard for the Exchange of Product model data (ISO 10303) |
| STL | Standard Tessellation Language — mesh format for 3D printing |
| Von Mises | Equivalent stress criterion for ductile material failure prediction |
Related Documentation
| Document | Description |
|---|---|
| Agent System | Base agent interface, lifecycle, testing patterns |
| Tool Adapters | Adapter interface, existing adapters (KiCad, NGSpice, supplier APIs) |
| Architecture | System architecture, data flows, agent-tool access matrix |
| Framework Mapping | 25-discipline framework, ME agent positioning |
| AI Memory & Knowledge | Knowledge Layer: pgvector, RAG retrieval, material/failure mode knowledge |
| System Observability | Agent metrics, LLM token/cost tracking, distributed tracing |
Document Version: v0.1-draft Author: MetaForge Architecture Team Last Updated: 2026-02-15
| ← Agent System | Tool Adapters → |