diff --git a/tools/zernike_html_generator.py b/tools/zernike_html_generator.py
index fe606c47..b9e3e1b9 100644
--- a/tools/zernike_html_generator.py
+++ b/tools/zernike_html_generator.py
@@ -655,22 +655,31 @@ def main_opd(op2_path: Path):
print(f"\nAvailable subcases: {list(extractor.displacements.keys())}")
- # Map subcases (try common patterns)
+ # Map subcases - find required angles by name (robust to extra subcases)
displacements = extractor.displacements
+ required_angles = ['90', '20', '40', '60']
subcase_map = {}
- if '1' in displacements and '2' in displacements:
- subcase_map = {'90': '1', '20': '2', '40': '3', '60': '4'}
- elif '90' in displacements and '20' in displacements:
- subcase_map = {'90': '90', '20': '20', '40': '40', '60': '60'}
- else:
- available = sorted(displacements.keys(), key=lambda x: int(x) if x.isdigit() else 0)
- if len(available) >= 4:
- subcase_map = {'90': available[0], '20': available[1], '40': available[2], '60': available[3]}
- print(f"[WARN] Using mapped subcases: {subcase_map}")
- else:
- print(f"[ERROR] Need 4 subcases, found: {available}")
- return
+ # First, try direct angle matching (e.g., "20", "40", "60", "90")
+ for angle in required_angles:
+ if angle in displacements:
+ subcase_map[angle] = angle
+
+ # If we didn't find all angles, try numeric IDs (e.g., "1"=90, "2"=20, "3"=40, "4"=60)
+ if len(subcase_map) < 4:
+ if all(str(i) in displacements for i in range(1, 5)):
+ subcase_map = {'90': '1', '20': '2', '40': '3', '60': '4'}
+ print(f"[INFO] Using numeric subcases: 1=90°, 2=20°, 3=40°, 4=60°")
+
+ # Check if we found all required angles
+ if len(subcase_map) < 4:
+ missing = [a for a in required_angles if a not in subcase_map]
+ print(f"[ERROR] Missing required subcases: {missing}")
+ print(f"[ERROR] Available subcases: {list(displacements.keys())}")
+ print(f"[ERROR] Required subcases must be named: {required_angles} (or numeric 1,2,3,4)")
+ return
+
+ print(f"[INFO] Using subcase mapping: {subcase_map}")
output_dir = op2_path.parent
base = op2_path.stem
@@ -898,26 +907,30 @@ def main(op2_path: Path):
displacements = read_displacements(op2_path)
print(f"Found subcases: {list(displacements.keys())}")
- # Map subcases (try common patterns)
- # Pattern 1: Direct labels (1, 2, 3, 4)
- # Pattern 2: Angle labels (90, 20, 40, 60)
+ # Map subcases - find required angles by name (robust to extra subcases)
+ required_angles = ['90', '20', '40', '60']
subcase_map = {}
- if '1' in displacements and '2' in displacements:
- # Standard NX pattern: 1=90deg, 2=20deg, 3=40deg, 4=60deg
- subcase_map = {'90': '1', '20': '2', '40': '3', '60': '4'}
- elif '90' in displacements and '20' in displacements:
- # Direct angle labels
- subcase_map = {'90': '90', '20': '20', '40': '40', '60': '60'}
- else:
- # Try to use whatever is available
- available = sorted(displacements.keys(), key=lambda x: int(x) if x.isdigit() else 0)
- if len(available) >= 4:
- subcase_map = {'90': available[0], '20': available[1], '40': available[2], '60': available[3]}
- print(f"[WARN] Using mapped subcases: {subcase_map}")
- else:
- print(f"[ERROR] Need 4 subcases, found: {available}")
- return
+ # First, try direct angle matching (e.g., "20", "40", "60", "90")
+ for angle in required_angles:
+ if angle in displacements:
+ subcase_map[angle] = angle
+
+ # If we didn't find all angles, try numeric IDs (e.g., "1"=90, "2"=20, "3"=40, "4"=60)
+ if len(subcase_map) < 4:
+ if all(str(i) in displacements for i in range(1, 5)):
+ subcase_map = {'90': '1', '20': '2', '40': '3', '60': '4'}
+ print(f"[INFO] Using numeric subcases: 1=90°, 2=20°, 3=40°, 4=60°")
+
+ # Check if we found all required angles
+ if len(subcase_map) < 4:
+ missing = [a for a in required_angles if a not in subcase_map]
+ print(f"[ERROR] Missing required subcases: {missing}")
+ print(f"[ERROR] Available subcases: {list(displacements.keys())}")
+ print(f"[ERROR] Required subcases must be named: {required_angles} (or numeric 1,2,3,4)")
+ return
+
+ print(f"[INFO] Using subcase mapping: {subcase_map}")
# Check all required subcases exist
for angle, label in subcase_map.items():
diff --git a/tools/zernike_html_generator_annular.py b/tools/zernike_html_generator_annular.py
index c7c8f5aa..afd11ee0 100644
--- a/tools/zernike_html_generator_annular.py
+++ b/tools/zernike_html_generator_annular.py
@@ -747,25 +747,30 @@ def main_annular(op2_path: Path, inner_radius_mm: float):
displacements = read_displacements(op2_path)
print(f"Found subcases: {list(displacements.keys())}")
- # Map subcases
+ # Map subcases - find required angles by name (robust to extra subcases)
+ required_angles = ['90', '20', '40', '60']
subcase_map = {}
- if '1' in displacements and '2' in displacements:
- subcase_map = {'90': '1', '20': '2', '40': '3', '60': '4'}
- elif '90' in displacements and '20' in displacements:
- subcase_map = {'90': '90', '20': '20', '40': '40', '60': '60'}
- else:
- available = sorted(displacements.keys(), key=lambda x: int(x) if x.isdigit() else 0)
- if len(available) >= 4:
- subcase_map = {'90': available[0], '20': available[1], '40': available[2], '60': available[3]}
- print(f"[WARN] Using mapped subcases: {subcase_map}")
- else:
- print(f"[ERROR] Need 4 subcases, found: {available}")
- return
- for angle, label in subcase_map.items():
- if label not in displacements:
- print(f"[ERROR] Subcase '{label}' (angle {angle}) not found")
- return
+ # First, try direct angle matching (e.g., "20", "40", "60", "90")
+ for angle in required_angles:
+ if angle in displacements:
+ subcase_map[angle] = angle
+
+ # If we didn't find all angles, try numeric IDs (e.g., "1"=90, "2"=20, "3"=40, "4"=60)
+ if len(subcase_map) < 4:
+ if all(str(i) in displacements for i in range(1, 5)):
+ subcase_map = {'90': '1', '20': '2', '40': '3', '60': '4'}
+ print(f"[INFO] Using numeric subcases: 1=90°, 2=20°, 3=40°, 4=60°")
+
+ # Check if we found all required angles
+ if len(subcase_map) < 4:
+ missing = [a for a in required_angles if a not in subcase_map]
+ print(f"[ERROR] Missing required subcases: {missing}")
+ print(f"[ERROR] Available subcases: {list(displacements.keys())}")
+ print(f"[ERROR] Required subcases must be named: {required_angles} (or numeric 1,2,3,4)")
+ return
+
+ print(f"[INFO] Using subcase mapping: {subcase_map}")
output_dir = op2_path.parent
base = op2_path.stem