fix(tools): make Zernike OPD tools robust to extra subcases
- Replace brittle order-based subcase mapping with name-based search - Tools now directly search for required angles (20, 40, 60, 90) by label - Ignores extra subcases (e.g., 30, 50 degrees) without errors - Falls back to numeric IDs (1,2,3,4) if angle labels not found - Clear error messages show exactly which subcases are missing This allows running WFE analysis on simulations with >4 subcases without manual file/code modifications. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user