674 lines
21 KiB
Markdown
674 lines
21 KiB
Markdown
|
|
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!
|