Neural Field Data Parser: From NX Nastran Files to Training Data
Complete Implementation Guide

What You Have vs What You Need
✅ What NX Nastran Gives You:
Files Available:

.sim - Simulation file with load/BC definitions
.fem - Finite element model
.prt - Part geometry
.bdf/.dat - Nastran input deck (mesh, materials, loads, BCs)
.op2 - Binary results (stress, displacement, strain)
.f06 - ASCII results (human readable)
.log - Solver log

This is SUFFICIENT! The BDF contains everything about setup, OP2 contains all results.

Step-by-Step Instructions for Manual Data Generation
Step 1: Set Up Your Analysis in NX
1. Create your geometry in NX
2. Generate mesh (record statistics)
3. Apply materials
4. Define boundary conditions:
   - Fixed supports
   - Pinned constraints
   - Contact (if needed)
5. Apply loads:
   - Forces
   - Pressures
   - Gravity
6. Set up solution parameters
7. Run analysis
8. Ensure these files are generated:
   - model.bdf (or .dat)
   - model.op2
   - model.f06
Step 2: Organize Your Files
training_case_001/
├── input/
│   ├── model.bdf          # Main input deck
│   ├── model.sim          # NX simulation file
│   └── geometry.prt       # Original geometry
├── output/
│   ├── model.op2          # Binary results
│   ├── model.f06          # ASCII results
│   └── model.log          # Solver log
└── metadata.json          # Your manual annotations

Python Parser Implementation
Main Parser Script
python"""
neural_field_parser.py
Parses NX Nastran files into Neural Field training data
"""

import json
import numpy as np
import h5py
from pathlib import Path
from datetime import datetime
import hashlib

# pyNastran imports
from pyNastran.bdf.bdf import BDF
from pyNastran.op2.op2 import OP2

class NastranToNeuralFieldParser:
    """
    Parses Nastran BDF/OP2 files into Neural Field data structure
    """
    
    def __init__(self, case_directory):
        self.case_dir = Path(case_directory)
        self.bdf_file = self.case_dir / "input" / "model.bdf"
        self.op2_file = self.case_dir / "output" / "model.op2"
        
        # Initialize readers
        self.bdf = BDF(debug=False)
        self.op2 = OP2(debug=False)
        
        # Data structure
        self.neural_field_data = {
            "metadata": {},
            "geometry": {},
            "mesh": {},
            "materials": {},
            "boundary_conditions": {},
            "loads": {},
            "results": {}
        }
        
    def parse_all(self):
        """
        Main parsing function
        """
        print("Starting parse of Nastran files...")
        
        # Parse input deck
        print("Reading BDF file...")
        self.bdf.read_bdf(str(self.bdf_file))
        
        # Parse results
        print("Reading OP2 file...")
        self.op2.read_op2(str(self.op2_file))
        
        # Extract all data
        self.extract_metadata()
        self.extract_mesh()
        self.extract_materials()
        self.extract_boundary_conditions()
        self.extract_loads()
        self.extract_results()
        
        # Save to file
        self.save_data()
        
        print("Parse complete!")
        return self.neural_field_data
    
    def extract_metadata(self):
        """
        Extract metadata and analysis info
        """
        self.neural_field_data["metadata"] = {
            "version": "1.0.0",
            "created_at": datetime.now().isoformat(),
            "source": "NX_Nastran",
            "case_directory": str(self.case_dir),
            "analysis_type": self.op2.sol,  # SOL 101, 103, etc.
            "title": self.bdf.case_control_deck.title.title if hasattr(self.bdf.case_control_deck, 'title') else "",
            "units": {
                "length": "mm",  # You may need to specify this
                "force": "N",
                "stress": "Pa",
                "temperature": "K"
            }
        }
    
    def extract_mesh(self):
        """
        Extract mesh data from BDF
        """
        print("Extracting mesh...")
        
        # Nodes
        nodes = []
        node_ids = []
        for nid, node in sorted(self.bdf.nodes.items()):
            node_ids.append(nid)
            nodes.append(node.get_position())
        
        nodes_array = np.array(nodes)
        
        # Elements
        element_data = {
            "solid": [],
            "shell": [],
            "beam": [],
            "rigid": []
        }
        
        # Solid elements (TETRA, HEXA, PENTA)
        for eid, elem in self.bdf.elements.items():
            elem_type = elem.type
            
            if elem_type in ['CTETRA', 'CHEXA', 'CPENTA', 'CTETRA10', 'CHEXA20']:
                element_data["solid"].append({
                    "id": eid,
                    "type": elem_type,
                    "nodes": elem.node_ids,
                    "material_id": elem.mid,
                    "property_id": elem.pid if hasattr(elem, 'pid') else None
                })
                
            elif elem_type in ['CQUAD4', 'CTRIA3', 'CQUAD8', 'CTRIA6']:
                element_data["shell"].append({
                    "id": eid,
                    "type": elem_type,
                    "nodes": elem.node_ids,
                    "material_id": elem.mid,
                    "property_id": elem.pid,
                    "thickness": elem.T() if hasattr(elem, 'T') else None
                })
                
            elif elem_type in ['CBAR', 'CBEAM', 'CROD']:
                element_data["beam"].append({
                    "id": eid,
                    "type": elem_type,
                    "nodes": elem.node_ids,
                    "material_id": elem.mid,
                    "property_id": elem.pid
                })
                
            elif elem_type in ['RBE2', 'RBE3', 'RBAR']:
                element_data["rigid"].append({
                    "id": eid,
                    "type": elem_type,
                    "nodes": elem.node_ids
                })
        
        # Store mesh data
        self.neural_field_data["mesh"] = {
            "statistics": {
                "n_nodes": len(nodes),
                "n_elements": len(self.bdf.elements),
                "element_types": {
                    "solid": len(element_data["solid"]),
                    "shell": len(element_data["shell"]),
                    "beam": len(element_data["beam"]),
                    "rigid": len(element_data["rigid"])
                }
            },
            "nodes": {
                "ids": node_ids,
                "coordinates": nodes_array.tolist(),
                "shape": list(nodes_array.shape)
            },
            "elements": element_data
        }
    
    def extract_materials(self):
        """
        Extract material properties
        """
        print("Extracting materials...")
        
        materials = []
        for mid, mat in self.bdf.materials.items():
            mat_data = {
                "id": mid,
                "type": mat.type
            }
            
            if mat.type == 'MAT1':  # Isotropic material
                mat_data.update({
                    "E": mat.e,      # Young's modulus
                    "nu": mat.nu,    # Poisson's ratio
                    "rho": mat.rho,  # Density
                    "G": mat.g,      # Shear modulus
                    "alpha": mat.a if hasattr(mat, 'a') else None,  # Thermal expansion
                    "tref": mat.tref if hasattr(mat, 'tref') else None,
                    "ST": mat.St() if hasattr(mat, 'St') else None,  # Tensile stress limit
                    "SC": mat.Sc() if hasattr(mat, 'Sc') else None,  # Compressive stress limit
                    "SS": mat.Ss() if hasattr(mat, 'Ss') else None   # Shear stress limit
                })
                
            materials.append(mat_data)
        
        self.neural_field_data["materials"] = materials
    
    def extract_boundary_conditions(self):
        """
        Extract boundary conditions from BDF
        """
        print("Extracting boundary conditions...")
        
        bcs = {
            "spc": [],      # Single point constraints
            "mpc": [],      # Multi-point constraints
            "suport": []    # Free body supports
        }
        
        # SPC (fixed DOFs)
        for spc_id, spc_list in self.bdf.spcs.items():
            for spc in spc_list:
                bcs["spc"].append({
                    "id": spc_id,
                    "node": spc.node_ids[0] if hasattr(spc, 'node_ids') else spc.node,
                    "dofs": spc.components,  # Which DOFs are constrained (123456)
                    "enforced_motion": spc.enforced
                })
        
        # MPC equations
        for mpc_id, mpc_list in self.bdf.mpcs.items():
            for mpc in mpc_list:
                bcs["mpc"].append({
                    "id": mpc_id,
                    "nodes": mpc.node_ids,
                    "coefficients": mpc.coefficients,
                    "components": mpc.components
                })
        
        self.neural_field_data["boundary_conditions"] = bcs
    
    def extract_loads(self):
        """
        Extract loads from BDF
        """
        print("Extracting loads...")
        
        loads = {
            "point_forces": [],
            "pressure": [],
            "gravity": [],
            "thermal": []
        }
        
        # Point forces (FORCE, MOMENT)
        for load_id, load_list in self.bdf.loads.items():
            for load in load_list:
                if load.type == 'FORCE':
                    loads["point_forces"].append({
                        "id": load_id,
                        "node": load.node,
                        "magnitude": load.mag,
                        "direction": [load.xyz[0], load.xyz[1], load.xyz[2]],
                        "coord_system": load.cid
                    })
                    
                elif load.type == 'MOMENT':
                    loads["point_forces"].append({
                        "id": load_id,
                        "node": load.node,
                        "moment": load.mag,
                        "direction": [load.xyz[0], load.xyz[1], load.xyz[2]],
                        "coord_system": load.cid
                    })
                    
                elif load.type in ['PLOAD', 'PLOAD2', 'PLOAD4']:
                    loads["pressure"].append({
                        "id": load_id,
                        "elements": load.element_ids,
                        "pressure": load.pressure,
                        "type": load.type
                    })
                    
                elif load.type == 'GRAV':
                    loads["gravity"].append({
                        "id": load_id,
                        "acceleration": load.scale,
                        "direction": [load.N[0], load.N[1], load.N[2]],
                        "coord_system": load.cid
                    })
        
        # Temperature loads
        for temp_id, temp_list in self.bdf.temps.items():
            for temp in temp_list:
                loads["thermal"].append({
                    "id": temp_id,
                    "node": temp.node,
                    "temperature": temp.temperature
                })
        
        self.neural_field_data["loads"] = loads
    
    def extract_results(self):
        """
        Extract results from OP2
        """
        print("Extracting results...")
        
        results = {}
        
        # Get subcase ID (usually 1 for linear static)
        subcase_id = 1
        
        # Displacement
        if hasattr(self.op2, 'displacements'):
            disp = self.op2.displacements[subcase_id]
            disp_data = disp.data[0, :, :]  # [itime=0, all_nodes, 6_dofs]
            
            results["displacement"] = {
                "node_ids": disp.node_gridtype[:, 0].tolist(),
                "data": disp_data.tolist(),
                "shape": list(disp_data.shape),
                "max_magnitude": float(np.max(np.linalg.norm(disp_data[:, :3], axis=1)))
            }
        
        # Stress - handle different element types
        stress_results = {}
        
        # Solid stress
        if hasattr(self.op2, 'ctetra_stress'):
            stress = self.op2.ctetra_stress[subcase_id]
            stress_data = stress.data[0, :, :]
            stress_results["solid_stress"] = {
                "element_ids": stress.element_node[:, 0].tolist(),
                "data": stress_data.tolist(),
                "von_mises": stress_data[:, -1].tolist() if stress_data.shape[1] > 6 else None
            }
        
        # Shell stress  
        if hasattr(self.op2, 'cquad4_stress'):
            stress = self.op2.cquad4_stress[subcase_id]
            stress_data = stress.data[0, :, :]
            stress_results["shell_stress"] = {
                "element_ids": stress.element_node[:, 0].tolist(),
                "data": stress_data.tolist()
            }
        
        results["stress"] = stress_results
        
        # Strain
        strain_results = {}
        if hasattr(self.op2, 'ctetra_strain'):
            strain = self.op2.ctetra_strain[subcase_id]
            strain_data = strain.data[0, :, :]
            strain_results["solid_strain"] = {
                "element_ids": strain.element_node[:, 0].tolist(),
                "data": strain_data.tolist()
            }
        
        results["strain"] = strain_results
        
        # SPC Forces (reactions)
        if hasattr(self.op2, 'spc_forces'):
            spc = self.op2.spc_forces[subcase_id]
            spc_data = spc.data[0, :, :]
            results["reactions"] = {
                "node_ids": spc.node_gridtype[:, 0].tolist(),
                "forces": spc_data.tolist()
            }
        
        self.neural_field_data["results"] = results
    
    def save_data(self):
        """
        Save parsed data to JSON and HDF5
        """
        print("Saving data...")
        
        # Save JSON metadata
        json_file = self.case_dir / "neural_field_data.json"
        with open(json_file, 'w') as f:
            # Convert numpy arrays to lists for JSON serialization
            json.dump(self.neural_field_data, f, indent=2, default=str)
        
        # Save HDF5 for large arrays
        h5_file = self.case_dir / "neural_field_data.h5"
        with h5py.File(h5_file, 'w') as f:
            # Save mesh data
            mesh_grp = f.create_group('mesh')
            mesh_grp.create_dataset('node_coordinates', 
                                   data=np.array(self.neural_field_data["mesh"]["nodes"]["coordinates"]))
            
            # Save results
            if "results" in self.neural_field_data:
                results_grp = f.create_group('results')
                if "displacement" in self.neural_field_data["results"]:
                    results_grp.create_dataset('displacement', 
                                             data=np.array(self.neural_field_data["results"]["displacement"]["data"]))
        
        print(f"Data saved to {json_file} and {h5_file}")

# ============================================================================
# USAGE SCRIPT
# ============================================================================

def main():
    """
    Main function to run the parser
    """
    import sys
    
    if len(sys.argv) < 2:
        print("Usage: python neural_field_parser.py <case_directory>")
        sys.exit(1)
    
    case_dir = sys.argv[1]
    
    # Create parser
    parser = NastranToNeuralFieldParser(case_dir)
    
    # Parse all data
    try:
        data = parser.parse_all()
        print("\nParsing successful!")
        print(f"Nodes: {data['mesh']['statistics']['n_nodes']}")
        print(f"Elements: {data['mesh']['statistics']['n_elements']}")
        print(f"Materials: {len(data['materials'])}")
        
    except Exception as e:
        print(f"\nError during parsing: {e}")
        import traceback
        traceback.print_exc()

if __name__ == "__main__":
    main()

Validation Script
python"""
validate_parsed_data.py
Validates the parsed neural field data
"""

import json
import h5py
import numpy as np
from pathlib import Path

class NeuralFieldDataValidator:
    """
    Validates parsed data for completeness and consistency
    """
    
    def __init__(self, case_directory):
        self.case_dir = Path(case_directory)
        self.json_file = self.case_dir / "neural_field_data.json"
        self.h5_file = self.case_dir / "neural_field_data.h5"
        
    def validate(self):
        """
        Run all validation checks
        """
        print("Starting validation...")
        
        # Load data
        with open(self.json_file, 'r') as f:
            data = json.load(f)
        
        # Check required fields
        required_fields = [
            "metadata", "mesh", "materials", 
            "boundary_conditions", "loads", "results"
        ]
        
        for field in required_fields:
            if field not in data:
                print(f"❌ Missing required field: {field}")
                return False
            else:
                print(f"✅ Found {field}")
        
        # Validate mesh
        n_nodes = data["mesh"]["statistics"]["n_nodes"]
        n_elements = data["mesh"]["statistics"]["n_elements"]
        
        print(f"\nMesh Statistics:")
        print(f"  Nodes: {n_nodes}")
        print(f"  Elements: {n_elements}")
        
        # Check results consistency
        if "displacement" in data["results"]:
            disp_nodes = len(data["results"]["displacement"]["node_ids"])
            if disp_nodes != n_nodes:
                print(f"⚠️  Displacement nodes ({disp_nodes}) != mesh nodes ({n_nodes})")
        
        # Check HDF5 file
        with h5py.File(self.h5_file, 'r') as f:
            print(f"\nHDF5 Contents:")
            for key in f.keys():
                print(f"  {key}: {list(f[key].keys())}")
        
        print("\n✅ Validation complete!")
        return True

if __name__ == "__main__":
    import sys
    validator = NeuralFieldDataValidator(sys.argv[1])
    validator.validate()

Step-by-Step Usage Instructions
1. Prepare Your Analysis
bash# In NX:
1. Create geometry
2. Generate mesh
3. Apply materials (MAT1 cards)
4. Apply constraints (SPC)
5. Apply loads (FORCE, PLOAD4)
6. Run SOL 101 (Linear Static)
7. Request output: DISPLACEMENT=ALL, STRESS=ALL, STRAIN=ALL
2. Organize Files
bashmkdir training_case_001
mkdir training_case_001/input
mkdir training_case_001/output

# Copy files
cp your_model.bdf training_case_001/input/model.bdf
cp your_model.op2 training_case_001/output/model.op2
cp your_model.f06 training_case_001/output/model.f06
3. Run Parser
bash# Install requirements
pip install pyNastran numpy h5py

# Run parser
python neural_field_parser.py training_case_001

# Validate
python validate_parsed_data.py training_case_001
4. Check Output
You'll get:

neural_field_data.json - Complete metadata and structure
neural_field_data.h5 - Large arrays (mesh, results)


Automation Script for Multiple Cases
python"""
batch_parser.py
Parse multiple cases automatically
"""

import os
from pathlib import Path
from neural_field_parser import NastranToNeuralFieldParser

def batch_parse(root_directory):
    """
    Parse all cases in directory
    """
    root = Path(root_directory)
    cases = [d for d in root.iterdir() if d.is_dir()]
    
    results = []
    for case in cases:
        print(f"\nProcessing {case.name}...")
        try:
            parser = NastranToNeuralFieldParser(case)
            data = parser.parse_all()
            results.append({
                "case": case.name,
                "status": "success",
                "nodes": data["mesh"]["statistics"]["n_nodes"],
                "elements": data["mesh"]["statistics"]["n_elements"]
            })
        except Exception as e:
            results.append({
                "case": case.name,
                "status": "failed",
                "error": str(e)
            })
    
    # Summary
    print("\n" + "="*50)
    print("BATCH PROCESSING COMPLETE")
    print("="*50)
    for r in results:
        status = "✅" if r["status"] == "success" else "❌"
        print(f"{status} {r['case']}: {r['status']}")
    
    return results

if __name__ == "__main__":
    batch_parse("./training_data")

What to Add Manually
Create a metadata.json in each case directory with design intent:
json{
  "design_parameters": {
    "thickness": 2.5,
    "fillet_radius": 5.0,
    "rib_height": 15.0
  },
  "optimization_context": {
    "objectives": ["minimize_weight", "minimize_stress"],
    "constraints": ["max_displacement < 2mm"],
    "iteration": 42
  },
  "notes": "Baseline design with standard loading"
}

Troubleshooting
Common Issues:

"Can't find BDF nodes"

Make sure you're using .bdf or .dat, not .sim
Check that mesh was exported to solver deck


"OP2 has no results"

Ensure analysis completed successfully
Check that you requested output (DISP=ALL, STRESS=ALL)


"Memory error with large models"

Use HDF5 chunking for very large models
Process in batches



This parser gives you everything you need to start training neural networks on your FEA data. The format is future-proof and will work with your automated generation pipeline!