875 lines
32 KiB
Markdown
875 lines
32 KiB
Markdown
# Model Introspection Master Plan — v1.0
|
||
|
||
**Authors:** Technical Lead 🔧 (plan owner), NX Expert 🖥️ (initial research)
|
||
**Date:** 2026-02-15
|
||
**Status:** Approved for R&D implementation
|
||
**Location:** `docs/plans/MODEL_INTROSPECTION_MASTER_PLAN.md`
|
||
|
||
---
|
||
|
||
## 1. Executive Summary
|
||
|
||
Atomizer currently executes optimization studies that users manually configure. It has **basic introspection** — expression extraction, mass properties, partial solver config — but lacks the deep model knowledge needed to *understand* what it's optimizing.
|
||
|
||
This plan defines a **four-layer introspection framework** that captures the complete data picture of any NX CAD/FEA model before optimization. The output is a single structured JSON file (`model_introspection.json`) containing everything an engineer or agent needs to design a sound optimization study:
|
||
|
||
- **What can change** — design variables, expressions, parametric geometry
|
||
- **What the FEA model looks like** — mesh quality, element types, materials, properties
|
||
- **What physics governs the problem** — boundary conditions, loads, solver config, subcases
|
||
- **Where we're starting from** — baseline displacement, stress, frequency, mass
|
||
|
||
A fifth layer (dependency mapping / expression graphs) is **deferred to v2** due to NXOpen API limitations that make reliable extraction impractical today.
|
||
|
||
**Timeline:** 12–17 working days for production-quality v1.
|
||
**Primary tools:** NXOpen Python API (Layer 1), pyNastran BDF (Layers 2–3), pyNastran OP2 (Layer 4).
|
||
|
||
---
|
||
|
||
## 2. Current State
|
||
|
||
### 2.1 What Exists
|
||
|
||
| Script | Location | Extracts | Output |
|
||
|--------|----------|----------|--------|
|
||
| `introspect_part.py` | `nx_journals/` | Expressions (user/internal), mass, materials, bodies, features, datums, units | `_temp_introspection.json` |
|
||
| `introspect_sim.py` | `nx_journals/` | Solutions (partial), BCs (partial), subcases (exploratory) | `_introspection_sim.json` |
|
||
| `discover_model.py` | `nx_journals/` | Quick scan of expressions + solutions | JSON to stdout |
|
||
| `extract_displacement.py` | `optimization_engine/extractors/` | Max displacement from OP2 | Per-trial result |
|
||
| `extract_von_mises_stress.py` | `optimization_engine/extractors/` | Max von Mises from OP2 | Per-trial result |
|
||
| `extract_part_mass_material.py` | `nx_journals/` | Mass + material from part | JSON |
|
||
|
||
### 2.2 What's Missing
|
||
|
||
- ❌ **Mesh quality metrics** — no aspect ratio, jacobian, warpage, skew
|
||
- ❌ **BC/load details** — no magnitudes, DOF specifications, target node/element sets
|
||
- ❌ **Solver configuration** — no output requests, convergence settings, solution sequence details
|
||
- ❌ **Property cards** — no PSHELL thickness, PSOLID assignments, property-element mapping
|
||
- ❌ **Baseline results in one place** — extractors exist but aren't aggregated pre-optimization
|
||
- ❌ **Unified output** — no single JSON capturing the full model state
|
||
- ❌ **Validation** — no cross-checking between data sources
|
||
|
||
---
|
||
|
||
## 3. Framework Architecture
|
||
|
||
### 3.1 Four-Layer Model (v1)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ Layer 1: GEOMETRIC PARAMETERS [NXOpen API] │
|
||
│ Expressions, features, mass, materials, units │
|
||
│ → What can be optimized? │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ Layer 2: FEA MODEL STRUCTURE [pyNastran BDF] │
|
||
│ Mesh quality, element types, materials, properties │
|
||
│ → What's the baseline mesh health? │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ Layer 3: SOLVER CONFIGURATION [pyNastran BDF] │
|
||
│ Solutions, subcases, BCs, loads, output requests │
|
||
│ → What physics governs the problem? │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ Layer 4: BASELINE RESULTS [pyNastran OP2] │
|
||
│ Pre-optimization stress, displacement, frequency, mass │
|
||
│ → Where are we starting from? │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
|
||
DEFERRED TO v2:
|
||
┌───────────────────────────────────────────────────────────────┐
|
||
│ Layer 5: DEPENDENCIES & RELATIONSHIPS │
|
||
│ Expression graph, feature tree, parametric sensitivities │
|
||
└───────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 3.2 Design Principles
|
||
|
||
1. **One source per data type.** Don't mix NXOpen and pyNastran for the same data. NXOpen owns geometry/expressions; pyNastran owns FEA/solver data.
|
||
2. **BDF export is a prerequisite.** Layer 2–3 extraction requires a current BDF file. The orchestrator must trigger a BDF export (or verify freshness) before parsing.
|
||
3. **Report, don't recommend.** v1 reports what exists in the model. It does NOT auto-suggest bounds, objectives, or study types. That's the engineer's job.
|
||
4. **Fail gracefully.** If one layer fails, the others still produce output. Partial introspection is better than no introspection.
|
||
5. **Validate across sources.** Where data overlaps (element count, mass, materials), cross-check and flag discrepancies.
|
||
|
||
### 3.3 Data Flow
|
||
|
||
```
|
||
┌──────────┐
|
||
│ .prt │
|
||
│ file │
|
||
└────┬─────┘
|
||
│
|
||
NXOpen API
|
||
│
|
||
▼
|
||
┌──────────────┐
|
||
│ Layer 1 │
|
||
│ Geometric │
|
||
│ Parameters │
|
||
└──────┬───────┘
|
||
│
|
||
┌────────────────────┼────────────────────┐
|
||
│ │ │
|
||
▼ ▼ ▼
|
||
┌────────┐ ┌──────────┐ ┌──────────┐
|
||
│ .bdf │ │ .sim │ │ .op2 │
|
||
│ file │ │ file │ │ file │
|
||
└───┬────┘ └──────────┘ └────┬─────┘
|
||
│ (metadata only │
|
||
│ via NXOpen) │
|
||
pyNastran BDF pyNastran OP2
|
||
│ │
|
||
▼ ▼
|
||
┌──────────────┐ ┌──────────────┐
|
||
│ Layer 2+3 │ │ Layer 4 │
|
||
│ FEA Model │ │ Baseline │
|
||
│ + Solver │ │ Results │
|
||
└──────┬───────┘ └──────┬───────┘
|
||
│ │
|
||
└──────────┬───────────────────────┘
|
||
│
|
||
▼
|
||
┌────────────────┐
|
||
│ Orchestrator │
|
||
│ Merge + JSON │
|
||
│ + Validate │
|
||
└────────┬───────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────┐
|
||
│ model_introspection.json │
|
||
│ introspection_summary.md │
|
||
└──────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Master JSON Schema
|
||
|
||
### 4.1 Top-Level Structure
|
||
|
||
```json
|
||
{
|
||
"introspection_version": "1.0.0",
|
||
"timestamp": "ISO-8601",
|
||
"model_id": "string — derived from part filename",
|
||
"files": {
|
||
"part": "path/to/model.prt",
|
||
"sim": "path/to/model_sim1.sim",
|
||
"fem": "path/to/model_fem1.fem",
|
||
"bdf": "path/to/exported.bdf",
|
||
"op2": "path/to/results.op2"
|
||
},
|
||
"geometric_parameters": { "..." },
|
||
"fea_model": { "..." },
|
||
"solver_configuration": { "..." },
|
||
"baseline_results": { "..." },
|
||
"candidate_design_variables": [ "..." ],
|
||
"validation": { "..." },
|
||
"metadata": {
|
||
"extraction_time_seconds": 0.0,
|
||
"layers_completed": ["geometric", "fea_model", "solver", "baseline"],
|
||
"layers_failed": [],
|
||
"warnings": []
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.2 Layer 1 — `geometric_parameters`
|
||
|
||
```json
|
||
{
|
||
"expressions": {
|
||
"user_defined": [
|
||
{
|
||
"name": "thickness",
|
||
"value": 3.0,
|
||
"units": "mm",
|
||
"formula": "3.0",
|
||
"is_constant": false,
|
||
"part": "bracket.prt"
|
||
}
|
||
],
|
||
"internal": [
|
||
{
|
||
"name": "p47",
|
||
"value": 6.0,
|
||
"units": "mm",
|
||
"formula": "thickness * 2"
|
||
}
|
||
],
|
||
"total_user": 3,
|
||
"total_internal": 52
|
||
},
|
||
"mass_properties": {
|
||
"mass_kg": 0.234,
|
||
"volume_mm3": 85000.0,
|
||
"surface_area_mm2": 15000.0,
|
||
"center_of_gravity_mm": [12.3, 45.6, 78.9],
|
||
"num_solid_bodies": 1
|
||
},
|
||
"materials": [
|
||
{
|
||
"name": "Aluminum 6061-T6",
|
||
"assigned_to_bodies": ["Body(1)"],
|
||
"properties": {
|
||
"density_kg_m3": 2700.0,
|
||
"youngs_modulus_MPa": 68900.0,
|
||
"poisson_ratio": 0.33,
|
||
"yield_strength_MPa": 276.0,
|
||
"ultimate_strength_MPa": 310.0
|
||
},
|
||
"source": "NXOpen part material"
|
||
}
|
||
],
|
||
"features": {
|
||
"total_count": 12,
|
||
"by_type": {
|
||
"Extrude": 3,
|
||
"Shell": 1,
|
||
"Sketch": 2,
|
||
"Datum Plane": 2,
|
||
"Fillet": 4
|
||
},
|
||
"suppressed_count": 0
|
||
},
|
||
"units": {
|
||
"length": "Millimeter",
|
||
"mass": "Kilogram",
|
||
"force": "Newton",
|
||
"temperature": "Celsius",
|
||
"system": "Metric (mm, kg, N, °C)"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.3 Layer 2 — `fea_model`
|
||
|
||
```json
|
||
{
|
||
"mesh": {
|
||
"total_nodes": 12450,
|
||
"total_elements": 8234,
|
||
"element_types": {
|
||
"CTETRA": { "count": 7800, "order": "linear" },
|
||
"CQUAD4": { "count": 434, "order": "linear" }
|
||
},
|
||
"quality_metrics": {
|
||
"aspect_ratio": {
|
||
"min": 1.02,
|
||
"max": 8.34,
|
||
"mean": 2.45,
|
||
"std": 1.23,
|
||
"p95": 5.12,
|
||
"threshold": 10.0,
|
||
"elements_exceeding": 0
|
||
},
|
||
"jacobian": {
|
||
"min": 0.62,
|
||
"max": 1.0,
|
||
"mean": 0.91,
|
||
"threshold": 0.5,
|
||
"elements_below": 0
|
||
},
|
||
"warpage_deg": {
|
||
"max": 5.2,
|
||
"threshold": 10.0,
|
||
"elements_exceeding": 0
|
||
},
|
||
"skew_deg": {
|
||
"max": 45.2,
|
||
"threshold": 60.0,
|
||
"elements_exceeding": 0
|
||
}
|
||
},
|
||
"quality_verdict": "PASS"
|
||
},
|
||
"materials": [
|
||
{
|
||
"mat_id": 1,
|
||
"card_type": "MAT1",
|
||
"name": "Aluminum 6061-T6",
|
||
"E_MPa": 68900.0,
|
||
"G_MPa": 25900.0,
|
||
"nu": 0.33,
|
||
"rho": 2.7e-6,
|
||
"alpha": 2.36e-5
|
||
}
|
||
],
|
||
"properties": [
|
||
{
|
||
"prop_id": 1,
|
||
"card_type": "PSHELL",
|
||
"thickness_mm": 3.0,
|
||
"mat_id": 1,
|
||
"element_count": 434
|
||
},
|
||
{
|
||
"prop_id": 2,
|
||
"card_type": "PSOLID",
|
||
"mat_id": 1,
|
||
"element_count": 7800
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Mesh quality computation:** All quality metrics are computed from element node coordinates using pyNastran's geometry data. For each element, aspect ratio = longest edge / shortest edge. Jacobian is computed at element integration points. This is more reliable than NXOpen's `QualityAuditBuilder` which has limited documentation.
|
||
|
||
### 4.4 Layer 3 — `solver_configuration`
|
||
|
||
```json
|
||
{
|
||
"solutions": [
|
||
{
|
||
"name": "Solution 1",
|
||
"sol_sequence": 101,
|
||
"sol_type": "Static Linear",
|
||
"solver": "NX Nastran"
|
||
}
|
||
],
|
||
"subcases": [
|
||
{
|
||
"id": 1,
|
||
"label": "Subcase - Static 1",
|
||
"load_set_id": 1,
|
||
"spc_set_id": 1,
|
||
"output_requests": {
|
||
"displacement": { "format": "OP2", "scope": "ALL" },
|
||
"stress": { "format": "OP2", "scope": "ALL" },
|
||
"strain": null,
|
||
"force": null
|
||
}
|
||
}
|
||
],
|
||
"constraints": [
|
||
{
|
||
"spc_id": 1,
|
||
"type": "SPC1",
|
||
"dofs_constrained": [1, 2, 3, 4, 5, 6],
|
||
"dof_labels": ["Tx", "Ty", "Tz", "Rx", "Ry", "Rz"],
|
||
"node_count": 145,
|
||
"node_ids_sample": [1, 2, 3, 4, 5],
|
||
"description": "Fixed support — all 6 DOF"
|
||
}
|
||
],
|
||
"loads": [
|
||
{
|
||
"load_id": 1,
|
||
"type": "FORCE",
|
||
"node_id": 456,
|
||
"magnitude_N": 1000.0,
|
||
"direction": [0.0, -1.0, 0.0],
|
||
"components_N": { "Fx": 0.0, "Fy": -1000.0, "Fz": 0.0 }
|
||
},
|
||
{
|
||
"load_id": 2,
|
||
"type": "PLOAD4",
|
||
"element_count": 25,
|
||
"pressure_MPa": 5.0,
|
||
"direction": "element normal"
|
||
}
|
||
],
|
||
"bulk_data_stats": {
|
||
"total_cards": 15234,
|
||
"card_types": {
|
||
"GRID": 12450,
|
||
"CTETRA": 7800,
|
||
"CQUAD4": 434,
|
||
"MAT1": 1,
|
||
"PSHELL": 1,
|
||
"PSOLID": 1,
|
||
"SPC1": 1,
|
||
"FORCE": 1,
|
||
"PLOAD4": 1
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**BDF export requirement:** The orchestrator must ensure a current BDF file exists before Layer 2–3 extraction. Options:
|
||
1. Export BDF via NXOpen journal (`sim.ExportNastranDeck()`) as the first orchestrator step
|
||
2. Accept a user-provided BDF path
|
||
3. Find the most recent BDF in the sim output directory and verify its timestamp
|
||
|
||
Option 1 is preferred — it guarantees freshness.
|
||
|
||
### 4.5 Layer 4 — `baseline_results`
|
||
|
||
```json
|
||
{
|
||
"source_op2": "bracket_sim1-solution_1.op2",
|
||
"solution": "Solution 1",
|
||
"subcase_id": 1,
|
||
"converged": true,
|
||
"displacement": {
|
||
"max_magnitude_mm": 2.34,
|
||
"max_node_id": 4567,
|
||
"max_component": "Tz",
|
||
"max_component_value_mm": -2.31,
|
||
"mean_magnitude_mm": 0.45
|
||
},
|
||
"stress": {
|
||
"von_mises": {
|
||
"max_MPa": 145.6,
|
||
"max_element_id": 2345,
|
||
"mean_MPa": 45.2,
|
||
"p95_MPa": 112.0
|
||
},
|
||
"margin_of_safety": {
|
||
"yield": 0.89,
|
||
"ultimate": 1.13,
|
||
"yield_strength_MPa": 276.0,
|
||
"ultimate_strength_MPa": 310.0,
|
||
"note": "MoS = (allowable / actual) - 1"
|
||
}
|
||
},
|
||
"modal": null,
|
||
"mass_from_solver_kg": 0.234
|
||
}
|
||
```
|
||
|
||
**Note:** `modal` is populated only if a SOL 103 result exists. Fields would include `modes: [{number, frequency_hz, effective_mass_fraction}]`.
|
||
|
||
### 4.6 `candidate_design_variables`
|
||
|
||
This section **reports** expressions that are likely design variable candidates. It does NOT suggest bounds or objectives — that's the engineer's job.
|
||
|
||
```json
|
||
[
|
||
{
|
||
"name": "thickness",
|
||
"current_value": 3.0,
|
||
"units": "mm",
|
||
"reason": "User-defined expression, non-constant, drives geometry"
|
||
},
|
||
{
|
||
"name": "width",
|
||
"current_value": 50.0,
|
||
"units": "mm",
|
||
"reason": "User-defined expression, non-constant, drives geometry"
|
||
}
|
||
]
|
||
```
|
||
|
||
**Selection criteria:** An expression is a candidate DV if:
|
||
1. It is user-defined (not internal `p###`)
|
||
2. It is not constant (formula is not just a literal used in a single non-geometric context)
|
||
3. It has dimensional units (mm, deg, etc.) or is clearly a count
|
||
4. Its name is not a known system expression (e.g., `PI`, `TRUE`, unit names)
|
||
|
||
### 4.7 `validation`
|
||
|
||
```json
|
||
{
|
||
"cross_checks": [
|
||
{
|
||
"metric": "element_count",
|
||
"nxopen_value": 8234,
|
||
"pynastran_value": 8234,
|
||
"match": true
|
||
},
|
||
{
|
||
"metric": "mass_kg",
|
||
"nxopen_value": 0.234,
|
||
"pynastran_value": 0.2338,
|
||
"match": false,
|
||
"delta_percent": 0.09,
|
||
"tolerance_percent": 1.0,
|
||
"within_tolerance": true,
|
||
"note": "Small delta due to mesh discretization vs. CAD geometry"
|
||
},
|
||
{
|
||
"metric": "material_E_MPa",
|
||
"nxopen_value": 68900.0,
|
||
"pynastran_value": 68900.0,
|
||
"match": true
|
||
}
|
||
],
|
||
"overall_status": "PASS",
|
||
"discrepancies": []
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Extraction Methods
|
||
|
||
### 5.1 Layer 1 — NXOpen Python API
|
||
|
||
**Base script:** `nx_journals/introspect_part.py` (enhance, don't rewrite)
|
||
|
||
| Data | API | Notes |
|
||
|------|-----|-------|
|
||
| Expressions | `part.Expressions` iterator | Filter user vs internal via name pattern (`p###` = internal) |
|
||
| Expression values | `expr.Value`, `expr.RightHandSide`, `expr.Units.Name` | |
|
||
| Mass properties | `part.MeasureManager.NewMassProperties()` | Requires solid body list |
|
||
| Materials | `body.GetPhysicalMaterial()` | Per-body; extract property values via `GetPropertyValue()` |
|
||
| Features | `part.Features` iterator | Type via `type(feature).__name__`; count suppressed |
|
||
| Units | `part.UnitCollection`, `part.PartUnits` | System-level unit identification |
|
||
|
||
**Enhancement needed:**
|
||
- Add candidate DV identification logic
|
||
- Structure output to match schema §4.2
|
||
- Add error handling for each extraction block (fail gracefully)
|
||
|
||
### 5.2 Layers 2–3 — pyNastran BDF
|
||
|
||
**New script:** `nx_journals/introspect_bdf.py` (or `optimization_engine/extractors/introspect_bdf.py`)
|
||
|
||
| Data | pyNastran API | Notes |
|
||
|------|---------------|-------|
|
||
| Elements | `bdf.elements` dict | Key = EID, value has `.type` attribute |
|
||
| Nodes | `bdf.nodes` dict | Key = NID |
|
||
| Materials | `bdf.materials` dict | MAT1: `.E`, `.G`, `.nu`, `.rho` |
|
||
| Properties | `bdf.properties` dict | PSHELL: `.t`, `.mid1`; PSOLID: `.mid` |
|
||
| SPCs | `bdf.spcs` dict | SPC1: `.components` (DOF string), `.node_ids` |
|
||
| Forces | `bdf.loads` dict | FORCE: `.mag`, `.xyz` (direction vector) |
|
||
| Pressures | `bdf.loads` dict | PLOAD4: `.pressures`, element IDs |
|
||
| Subcases | `bdf.subcases` dict | `.params` for LOAD, SPC, output requests |
|
||
| Bulk stats | `bdf.card_count` | Card type → count |
|
||
|
||
**Mesh quality computation** (pyNastran element geometry):
|
||
|
||
```python
|
||
import numpy as np
|
||
from pyNastran.bdf.bdf import BDF
|
||
|
||
def compute_mesh_quality(bdf_model):
|
||
"""Compute mesh quality metrics from element geometry."""
|
||
aspect_ratios = []
|
||
|
||
for eid, elem in bdf_model.elements.items():
|
||
if elem.type in ('CQUAD4', 'CTRIA3'):
|
||
# Get node positions
|
||
nodes = [bdf_model.nodes[nid].get_position() for nid in elem.node_ids]
|
||
|
||
# Compute edge lengths
|
||
edges = []
|
||
n = len(nodes)
|
||
for i in range(n):
|
||
edge = np.linalg.norm(nodes[(i+1) % n] - nodes[i])
|
||
edges.append(edge)
|
||
|
||
ar = max(edges) / max(min(edges), 1e-12)
|
||
aspect_ratios.append(ar)
|
||
|
||
elif elem.type == 'CTETRA':
|
||
nodes = [bdf_model.nodes[nid].get_position() for nid in elem.node_ids[:4]]
|
||
edges = []
|
||
for i in range(4):
|
||
for j in range(i+1, 4):
|
||
edges.append(np.linalg.norm(nodes[j] - nodes[i]))
|
||
ar = max(edges) / max(min(edges), 1e-12)
|
||
aspect_ratios.append(ar)
|
||
|
||
if not aspect_ratios:
|
||
return None
|
||
|
||
ar = np.array(aspect_ratios)
|
||
return {
|
||
"min": float(ar.min()),
|
||
"max": float(ar.max()),
|
||
"mean": float(ar.mean()),
|
||
"std": float(ar.std()),
|
||
"p95": float(np.percentile(ar, 95))
|
||
}
|
||
```
|
||
|
||
### 5.3 Layer 4 — pyNastran OP2
|
||
|
||
**Leverage existing extractors:**
|
||
- `optimization_engine/extractors/extract_displacement.py`
|
||
- `optimization_engine/extractors/extract_von_mises_stress.py`
|
||
|
||
**New aggregation script:** `nx_journals/introspect_baseline.py`
|
||
|
||
| Data | pyNastran API | Notes |
|
||
|------|---------------|-------|
|
||
| Displacement | `op2.displacements[subcase_id].data` | Magnitude = sqrt(Tx² + Ty² + Tz²) |
|
||
| Stress | `op2.ctetra_stress[sc]` or `op2.cquad4_stress[sc]` | Von Mises column varies by element type |
|
||
| Eigenvalues | `op2.eigenvalues` | SOL 103 only |
|
||
| Grid point weight | `op2.grid_point_weight` | Solver-computed mass |
|
||
|
||
### 5.4 BDF Export (Prerequisite Step)
|
||
|
||
```python
|
||
# NXOpen journal to export BDF from .sim
|
||
def export_bdf(sim_path, output_bdf_path):
|
||
"""Export Nastran input deck from simulation."""
|
||
theSession = NXOpen.Session.GetSession()
|
||
# Open sim, find solution, export deck
|
||
# Details depend on NX version — see introspect_sim.py patterns
|
||
pass
|
||
```
|
||
|
||
**Alternative:** If a solve has already been run, the BDF exists in the solver output directory. The orchestrator should check for it before triggering a fresh export.
|
||
|
||
---
|
||
|
||
## 6. Implementation Phases
|
||
|
||
### Phase 1: Enhanced Part Introspection (3–4 days)
|
||
|
||
**Goal:** Complete Layer 1 extraction with structured JSON output.
|
||
|
||
**Tasks:**
|
||
1. Refactor `introspect_part.py` output to match schema §4.2
|
||
2. Add candidate DV identification logic (§4.6 criteria)
|
||
3. Add feature type counting and suppression detection
|
||
4. Add material property extraction via `GetPropertyValue()`
|
||
5. Structured error handling — each block in try/except, log failures
|
||
6. Unit tests with known bracket/beam models
|
||
|
||
**Output:** `layer1_geometric.json`
|
||
**Owner:** NX Expert
|
||
|
||
### Phase 2: BDF-Based FEA Model Introspection (3–4 days)
|
||
|
||
**Goal:** Complete Layer 2 extraction — mesh, materials, properties, quality.
|
||
|
||
**Tasks:**
|
||
1. Create `introspect_bdf.py` with pyNastran BDF parsing
|
||
2. Implement mesh quality computation (aspect ratio, jacobian, warpage, skew)
|
||
3. Extract material cards (MAT1, MAT2 logged but not parsed in v1)
|
||
4. Extract property cards (PSHELL, PSOLID) with element assignments
|
||
5. Compute quality verdict (PASS/WARN/FAIL based on thresholds)
|
||
6. Test on Hydrotech beam BDF and M1 mirror BDF
|
||
|
||
**Output:** `layer2_fea_model.json`
|
||
**Owner:** NX Expert
|
||
|
||
### Phase 3: BDF-Based Solver Configuration (3–4 days)
|
||
|
||
**Goal:** Complete Layer 3 extraction — subcases, BCs, loads, output requests.
|
||
|
||
**Tasks:**
|
||
1. Extend `introspect_bdf.py` with subcase parsing
|
||
2. Extract SPC constraints with DOF details and node counts
|
||
3. Extract loads (FORCE, PLOAD4, MOMENT, GRAV) with magnitudes and directions
|
||
4. Extract output requests from case control
|
||
5. Add bulk data card statistics
|
||
6. Integrate BDF export step (NXOpen journal or path detection)
|
||
|
||
**Output:** `layer3_solver.json`
|
||
**Owner:** NX Expert
|
||
|
||
### Phase 4: Baseline Results Aggregation (1–2 days)
|
||
|
||
**Goal:** Complete Layer 4 — aggregate existing extractors into baseline JSON.
|
||
|
||
**Tasks:**
|
||
1. Create `introspect_baseline.py` using existing OP2 extractors
|
||
2. Compute displacement max/mean with node identification
|
||
3. Compute stress max with element identification and MoS
|
||
4. Optionally extract modal frequencies if SOL 103 results exist
|
||
5. Extract solver-computed mass from grid point weight generator
|
||
|
||
**Output:** `layer4_baseline.json`
|
||
**Owner:** NX Expert or Technical Lead
|
||
|
||
### Phase 5: Orchestrator + Validation (2–3 days)
|
||
|
||
**Goal:** Single-command full introspection with cross-validation and summary.
|
||
|
||
**Tasks:**
|
||
1. Create `run_introspection.py` orchestrator
|
||
2. Sequence: BDF export → Layer 1 → Layer 2 → Layer 3 → Layer 4
|
||
3. Merge all layer JSONs into master `model_introspection.json`
|
||
4. Implement cross-validation checks (§4.7)
|
||
5. Generate `introspection_summary.md` — human-readable report
|
||
6. Add CLI interface: `python run_introspection.py model.prt model_sim1.sim [--op2 path]`
|
||
|
||
**Output:** `model_introspection.json` + `introspection_summary.md`
|
||
**Owner:** NX Expert + Technical Lead (review)
|
||
|
||
### Timeline Summary
|
||
|
||
| Phase | Days | Cumulative | Depends On |
|
||
|-------|------|-----------|------------|
|
||
| Phase 1 — Part introspection | 3–4 | 3–4 | — |
|
||
| Phase 2 — FEA model (BDF) | 3–4 | 6–8 | — (parallel with Phase 1) |
|
||
| Phase 3 — Solver config (BDF) | 3–4 | 9–12 | Phase 2 (shared script) |
|
||
| Phase 4 — Baseline (OP2) | 1–2 | 10–14 | — (parallel with Phase 3) |
|
||
| Phase 5 — Orchestrator | 2–3 | 12–17 | All prior phases |
|
||
|
||
**Phases 1 and 2 can run in parallel** (different APIs, different scripts). Phase 4 can run in parallel with Phase 3. Critical path: Phase 2 → Phase 3 → Phase 5.
|
||
|
||
---
|
||
|
||
## 7. Validation Strategy
|
||
|
||
### 7.1 Cross-Source Checks
|
||
|
||
Where data is available from multiple sources, cross-validate:
|
||
|
||
| Metric | Source A | Source B | Tolerance |
|
||
|--------|----------|----------|-----------|
|
||
| Element count | pyNastran `len(bdf.elements)` | NXOpen `FeelementLabelMap.Size` | Exact match |
|
||
| Node count | pyNastran `len(bdf.nodes)` | NXOpen `FenodeLabelMap.Size` | Exact match |
|
||
| Mass | NXOpen `MeasureManager` | OP2 grid point weight | 1% (mesh vs. CAD geometry) |
|
||
| Material E | NXOpen `GetPropertyValue('YoungModulus')` | pyNastran `bdf.materials[id].E` | Exact match |
|
||
| Material ρ | NXOpen `GetPropertyValue('Density')` | pyNastran `bdf.materials[id].rho` | Unit conversion tolerance |
|
||
|
||
### 7.2 Self-Consistency Checks
|
||
|
||
- Total element count = sum of per-type counts
|
||
- Every property ID referenced by elements exists in properties list
|
||
- Every material ID referenced by properties exists in materials list
|
||
- SPC/LOAD set IDs in subcases exist in constraints/loads lists
|
||
- OP2 subcase IDs match BDF subcase IDs
|
||
|
||
### 7.3 Sanity Checks
|
||
|
||
- Mass > 0
|
||
- Max displacement > 0 (model is loaded and responding)
|
||
- Max stress > 0
|
||
- No element type with 0 elements in the count
|
||
- At least 1 constraint and 1 load in every subcase
|
||
|
||
### 7.4 Validation Verdict
|
||
|
||
```
|
||
PASS — All checks pass
|
||
WARN — Non-critical discrepancies (mass within tolerance but not exact)
|
||
FAIL — Critical mismatch (element count differs, missing materials)
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Integration with Atomizer HQ
|
||
|
||
### 8.1 How Agents Consume Introspection Data
|
||
|
||
| Agent | Uses | For |
|
||
|-------|------|-----|
|
||
| **Study Builder** | `candidate_design_variables`, `expressions`, `units` | Study configuration, DV setup |
|
||
| **Technical Lead** | `mesh.quality_metrics`, `baseline_results`, `validation` | Technical review, go/no-go |
|
||
| **Optimizer** | `fea_model.mesh.total_elements`, `baseline_results` | Runtime estimation, convergence criteria |
|
||
| **Manager** | `metadata.layers_completed`, `validation.overall_status` | Status tracking, resource planning |
|
||
|
||
### 8.2 Usage Workflow
|
||
|
||
```
|
||
1. Antoine opens new study
|
||
2. Run introspection: python run_introspection.py model.prt model_sim1.sim
|
||
3. Review introspection_summary.md
|
||
4. Study Builder reads model_introspection.json → proposes study config
|
||
5. Technical Lead reviews → approves or flags issues
|
||
6. Optimization proceeds with full model context
|
||
```
|
||
|
||
### 8.3 Knowledge Base Integration
|
||
|
||
Every introspection JSON is stored in the study directory:
|
||
```
|
||
studies/<project>/<study>/
|
||
1_setup/
|
||
model/
|
||
model_introspection.json ← NEW
|
||
introspection_summary.md ← NEW
|
||
model.prt
|
||
model_sim1.sim
|
||
```
|
||
|
||
This becomes the canonical reference for all agents working on the study.
|
||
|
||
---
|
||
|
||
## 9. Scope Exclusions (v1)
|
||
|
||
The following are **explicitly NOT handled** in v1. Attempting to introspect models with these features will produce partial results with appropriate warnings.
|
||
|
||
| Feature | Why Excluded | v2 Priority |
|
||
|---------|-------------|-------------|
|
||
| **Assemblies** | Multi-part expression scoping, component transforms, assembly-level materials — significant complexity | High |
|
||
| **Composite materials** (MAT2, MAT8) | Ply stack introspection, layup sequences, orientation angles — specialized domain | Medium |
|
||
| **Nonlinear analysis** (SOL 106) | Contact definitions, convergence settings, load stepping, large deformation flags | Medium |
|
||
| **Topology optimization** regions | Design vs. non-design space, manufacturing constraints, density filters | Low |
|
||
| **Expression dependency graphs** | NXOpen `RightHandSide` parsing is fragile: breaks on built-in functions (`if()`, `min()`), unit qualifiers, substring expression names. Feature-to-expression links require rebuilding builder objects — massive effort | Medium |
|
||
| **Parametric sensitivity estimation** | Labeling `thickness → mass` as "linear" is textbook, not model-specific. Real sensitivity requires perturbation studies (separate effort) | Low |
|
||
| **Multi-FEM models** | Multiple FEM files linked to one part — need to handle collector mapping across files | Medium |
|
||
| **Thermal-structural coupling** | Multi-physics BC detection, thermal load application | Low |
|
||
| **Contact pairs** | Contact detection, friction coefficients, contact algorithms | Medium |
|
||
| **Dynamic loads** | PSD, time-history, random vibration, frequency-dependent loads | Low |
|
||
|
||
---
|
||
|
||
## 10. v2 Roadmap
|
||
|
||
### 10.1 Expression Dependency Graph (Layer 5)
|
||
|
||
**Challenge:** NXOpen's `RightHandSide` returns the formula as a string, but parsing it reliably requires handling:
|
||
- NX built-in math functions
|
||
- Unit-qualified references
|
||
- Expression names that are substrings of other names
|
||
- Conditional expressions (`if(expr > val, a, b)`)
|
||
|
||
**Approach for v2:** Build a proper tokenizer/parser for NX expression syntax. Consider exporting expressions to a file and parsing offline rather than relying on API string manipulation.
|
||
|
||
### 10.2 Assembly Support
|
||
|
||
**Challenge:** Expressions live at component scope. Design variables may be in sub-components. Assembly-level mass is different from component mass.
|
||
|
||
**Approach:** Recursive introspection — introspect each component, then merge with assembly context (transforms, overrides).
|
||
|
||
### 10.3 Composite Introspection
|
||
|
||
**Challenge:** MAT2/MAT8 cards, PCOMP/PCOMPG properties, ply orientations, stacking sequences.
|
||
|
||
**Approach:** Extend pyNastran parsing for composite cards. Map plies to design variables (thickness, angle).
|
||
|
||
### 10.4 AI-Powered Analysis (Future)
|
||
|
||
- Sensitivity prediction from geometry/BC features without running FEA
|
||
- Design variable clustering (correlated parameters)
|
||
- Failure mode prediction from stress distributions
|
||
- Automated study type recommendation (with engineer approval)
|
||
|
||
---
|
||
|
||
## 11. Appendix: Key Reference Paths
|
||
|
||
### Existing Scripts
|
||
| Script | Path | Status |
|
||
|--------|------|--------|
|
||
| `introspect_part.py` | `nx_journals/introspect_part.py` | Enhance for v1 |
|
||
| `introspect_sim.py` | `nx_journals/introspect_sim.py` | Reference only (BDF replaces most functionality) |
|
||
| `discover_model.py` | `nx_journals/discover_model.py` | Reference only |
|
||
| `extract_displacement.py` | `optimization_engine/extractors/extract_displacement.py` | Use in Layer 4 |
|
||
| `extract_von_mises_stress.py` | `optimization_engine/extractors/extract_von_mises_stress.py` | Use in Layer 4 |
|
||
|
||
### New Scripts (to create)
|
||
| Script | Purpose |
|
||
|--------|---------|
|
||
| `introspect_bdf.py` | Layers 2–3: BDF parsing for mesh, materials, properties, solver config |
|
||
| `introspect_baseline.py` | Layer 4: OP2 result aggregation |
|
||
| `run_introspection.py` | Master orchestrator — runs all layers, merges, validates |
|
||
| `export_bdf.py` | NXOpen journal to export Nastran input deck |
|
||
|
||
### NXOpen API Reference
|
||
- Expressions: `NXOpen.Part.Expressions`
|
||
- Mass: `NXOpen.Part.MeasureManager.NewMassProperties()`
|
||
- Materials: `body.GetPhysicalMaterial()`, `mat.GetPropertyValue()`
|
||
- Features: `NXOpen.Part.Features`
|
||
- FEM: `NXOpen.CAE.FemPart.BaseFEModel`
|
||
- SIM: `NXOpen.CAE.SimPart.Simulation`
|
||
|
||
### pyNastran API Reference
|
||
- BDF: `pyNastran.bdf.bdf.BDF` — `.elements`, `.nodes`, `.materials`, `.properties`, `.spcs`, `.loads`, `.subcases`
|
||
- OP2: `pyNastran.op2.op2.OP2` — `.displacements`, `.ctetra_stress`, `.cquad4_stress`, `.eigenvalues`
|
||
|
||
---
|
||
|
||
## 12. Sign-Off
|
||
|
||
| Role | Status | Notes |
|
||
|------|--------|-------|
|
||
| Technical Lead 🔧 | ✅ Approved | Plan owner, technical review complete |
|
||
| NX Expert 🖥️ | 🔲 Pending | Initial research author, implementation owner |
|
||
| Manager 🎯 | 🔲 Pending | Resource allocation, timeline approval |
|
||
| CEO (Antoine) | 🔲 Pending | Strategic direction approval |
|
||
|
||
---
|
||
|
||
*The physics is the boss. Introspection just makes sure we understand what the physics is doing before we start changing things.*
|
||
|
||
— Technical Lead 🔧 | Atomizer Engineering Co.
|