""" Generic Field Data Extractor ============================= Reusable extractor for parsing NX exported field data (.fld files). Works for any result type: displacement, stress, strain, temperature, etc. Supports TWO formats: 1. NX native .fld format (exported from ResultProbe) 2. CSV format with headers Usage: extractor = FieldDataExtractor(field_file="export_field_dz.fld") results = extractor.extract() max_value = results['max_abs_value'] """ import csv from pathlib import Path from typing import Dict, Any, Optional, List import numpy as np class FieldDataExtractor: """ Generic extractor for NX exported field data files (.fld or .csv format). Supports: - Displacement (X, Y, Z components) - Stress (von Mises, principals, components) - Strain - Temperature - Any other scalar field data """ def __init__( self, field_file: str, result_column: str = "x(mm)", # Column name to extract (for CSV format) aggregation: str = "max_abs", # max_abs, max, min, mean, std ): """ Args: field_file: Path to .fld or .csv file result_column: Column name containing the result values (for CSV format, ignored for .fld) aggregation: How to aggregate values (max_abs, max, min, mean, std) """ self.field_file = Path(field_file) self.result_column = result_column self.aggregation = aggregation def _is_nx_field_format(self) -> bool: """Check if file is NX field format or CSV""" with open(self.field_file, 'r') as f: first_line = f.readline() return first_line.startswith('FIELD:') def _parse_nx_field_file(self) -> List[float]: """ Parse NX native field export format (.fld). Format: FIELD: [ResultProbe] : [TABLE] ...metadata... START DATA step, node_id, value 0, 396, -0.086716040968895 ... END DATA """ values = [] in_data_section = False with open(self.field_file, 'r') as f: for line in f: line = line.strip() if line.startswith('START DATA'): in_data_section = True continue if line.startswith('END DATA'): break if in_data_section and line: # Data format: step, node_id, value parts = [p.strip() for p in line.split(',')] if len(parts) >= 3: try: value = float(parts[2]) # Third column is the value values.append(value) except ValueError: continue return values def _parse_csv_file(self) -> List[float]: """ Parse CSV file with column headers. Expected format: node_id,x(mm),y(mm),z(mm) 1,0.0,0.0,0.5 2,0.1,0.0,0.6 ... """ values = [] with open(self.field_file, 'r') as f: reader = csv.DictReader(f) for row in reader: try: value = float(row[self.result_column]) values.append(value) except (KeyError, ValueError) as e: continue # Skip invalid rows return values def extract(self) -> Dict[str, Any]: """ Extract and aggregate field data. Returns: dict: { 'value': aggregated value, 'all_values': list of all values, 'node_count': number of nodes, 'stats': {min, max, mean, std, max_abs} } """ if not self.field_file.exists(): raise FileNotFoundError(f"Field file not found: {self.field_file}") # Detect format and parse if self._is_nx_field_format(): values = self._parse_nx_field_file() else: values = self._parse_csv_file() if not values: raise ValueError(f"No valid data found in field file: {self.field_file}") values_array = np.array(values) # Calculate statistics stats = { 'min': float(np.min(values_array)), 'max': float(np.max(values_array)), 'mean': float(np.mean(values_array)), 'std': float(np.std(values_array)), 'max_abs': float(np.max(np.abs(values_array))), } # Get aggregated value based on method aggregation_map = { 'max': stats['max'], 'min': stats['min'], 'mean': stats['mean'], 'std': stats['std'], 'max_abs': stats['max_abs'], } aggregated_value = aggregation_map.get(self.aggregation, stats['max_abs']) return { 'value': aggregated_value, 'all_values': values, 'node_count': len(values), 'stats': stats, 'aggregation_method': self.aggregation, } def extract_displacement_field( field_file: str, component: str = "z", # x, y, or z aggregation: str = "max_abs" ) -> Dict[str, Any]: """ Convenience function for extracting displacement data. Args: field_file: Path to displacement field file component: Displacement component (x, y, or z) aggregation: Aggregation method Returns: Extracted displacement data """ column_map = { 'x': 'x(mm)', 'y': 'y(mm)', 'z': 'z(mm)', } column = column_map.get(component.lower(), 'x(mm)') extractor = FieldDataExtractor(field_file, result_column=column, aggregation=aggregation) return extractor.extract() if __name__ == "__main__": # Example usage import sys if len(sys.argv) > 1: field_file = sys.argv[1] results = extract_displacement_field(field_file, component="z") print(f"Max absolute Z-displacement: {results['value']:.6f} mm") print(f"Statistics: {results['stats']}")