Files
Atomizer/docs/plans/MODEL_INTROSPECTION_MASTER_PLAN.md
2026-02-15 08:00:21 +00:00

32 KiB
Raw Blame History

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: 1217 working days for production-quality v1. Primary tools: NXOpen Python API (Layer 1), pyNastran BDF (Layers 23), 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 23 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

{
  "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

{
  "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

{
  "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

{
  "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 23 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

{
  "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.

[
  {
    "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

{
  "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 23 — 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):

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)

# 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 (34 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 (34 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 (34 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 (12 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 (23 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 34 34
Phase 2 — FEA model (BDF) 34 68 — (parallel with Phase 1)
Phase 3 — Solver config (BDF) 34 912 Phase 2 (shared script)
Phase 4 — Baseline (OP2) 12 1014 — (parallel with Phase 3)
Phase 5 — Orchestrator 23 1217 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 23: 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.