diff --git a/docs/06_PROTOCOLS_DETAILED/ASSEMBLY_FEM_WORKFLOW.md b/docs/06_PROTOCOLS_DETAILED/ASSEMBLY_FEM_WORKFLOW.md new file mode 100644 index 00000000..86b4a79e --- /dev/null +++ b/docs/06_PROTOCOLS_DETAILED/ASSEMBLY_FEM_WORKFLOW.md @@ -0,0 +1,188 @@ +# Assembly FEM Optimization Workflow + +This document describes the multi-part assembly FEM workflow used when optimizing complex assemblies with `.afm` (Assembly FEM) files. + +## Overview + +Assembly FEMs have a more complex dependency chain than single-part simulations: + +``` +.prt (geometry) → _fem1.fem (component mesh) → .afm (assembly mesh) → .sim (solution) +``` + +Each level must be updated in sequence when design parameters change. + +## When This Workflow Applies + +This workflow is automatically triggered when: +- The working directory contains `.afm` files +- Multiple `.fem` files exist (component meshes) +- Multiple `.prt` files exist (component geometry) + +Examples: +- M1 Mirror assembly (M1_Blank + M1_Vertical_Support_Skeleton) +- Multi-component mechanical assemblies +- Any NX assembly where components have separate FEM files + +## The 4-Step Workflow + +### Step 1: Update Expressions in Geometry Part (.prt) + +``` +Open M1_Blank.prt +├── Find and update design expressions +│ ├── whiffle_min = 42.5 +│ ├── whiffle_outer_to_vertical = 75.0 +│ └── inner_circular_rib_dia = 550.0 +├── Rebuild geometry (DoUpdate) +└── Save part +``` + +The `.prt` file contains the parametric CAD model with expressions that drive dimensions. These expressions are updated with new design parameter values, then the geometry is rebuilt. + +### Step 2: Update Component FEM Files (.fem) + +``` +For each component FEM: +├── Open M1_Blank_fem1.fem +│ ├── UpdateFemodel() - regenerates mesh from updated geometry +│ └── Save FEM +├── Open M1_Vertical_Support_Skeleton_fem1.fem +│ ├── UpdateFemodel() +│ └── Save FEM +└── ... (repeat for all component FEMs) +``` + +Each component FEM is linked to its source geometry. `UpdateFemodel()` regenerates the mesh based on the updated geometry. + +### Step 3: Update Assembly FEM (.afm) + +``` +Open ASSY_M1_assyfem1.afm +├── UpdateFemodel() - updates assembly mesh +├── Merge coincident nodes (at component interfaces) +├── Resolve labeling conflicts (duplicate node/element IDs) +└── Save AFM +``` + +The assembly FEM combines component meshes. This step: +- Reconnects meshes at shared interfaces +- Resolves numbering conflicts between component meshes +- Ensures mesh continuity for accurate analysis + +### Step 4: Solve Simulation (.sim) + +``` +Open ASSY_M1_assyfem1_sim1.sim +├── Execute solve +│ ├── Foreground mode for all solutions +│ └── or Background mode for specific solution +└── Save simulation +``` + +The simulation file references the assembly FEM and contains solution setup (loads, constraints, subcases). + +## File Dependencies + +``` +M1 Mirror Example: + +M1_Blank.prt ─────────────────────> M1_Blank_fem1.fem ─────────┐ + │ │ │ + │ (expressions) │ (component mesh) │ + ↓ ↓ │ +M1_Vertical_Support_Skeleton.prt ──> M1_..._Skeleton_fem1.fem ─┤ + │ + ↓ + ASSY_M1_assyfem1.afm ──> ASSY_M1_assyfem1_sim1.sim + (assembly mesh) (solution) +``` + +## API Functions Used + +| Step | NX API Call | Purpose | +|------|-------------|---------| +| 1 | `OpenBase()` | Open .prt file | +| 1 | `ImportFromFile()` | Import expressions from .exp file (preferred) | +| 1 | `DoUpdate()` | Rebuild geometry | +| 2-3 | `UpdateFemodel()` | Regenerate mesh from geometry | +| 3 | `DuplicateNodesCheckBuilder` | Merge coincident nodes at interfaces | +| 3 | `MergeOccurrenceNodes = True` | Critical: enables cross-component merge | +| 4 | `SolveAllSolutions()` | Execute FEA (Foreground mode recommended) + +### Expression Update Method + +The recommended approach uses expression file import: + +```python +# Write expressions to .exp file +with open(exp_path, 'w') as f: + for name, value in expressions.items(): + unit = get_unit_for_expression(name) + f.write(f"[{unit}]{name}={value}\n") + +# Import into part +modified, errors = workPart.Expressions.ImportFromFile( + exp_path, + NXOpen.ExpressionCollection.ImportMode.Replace +) +``` + +This is more reliable than `EditExpressionWithUnits()` for batch updates. + +## Error Handling + +Common issues and solutions: + +### "Update undo happened" +- Geometry update failed due to constraint violations +- Check expression values are within valid ranges +- May need to adjust parameter bounds + +### "This operation can only be done on the work part" +- Work part not properly set before operation +- Use `SetWork()` to make target part the work part + +### Node merge warnings +- Manual intervention may be needed for complex interfaces +- Check mesh connectivity in NX after solve + +### "Billion nm" RMS values +- Indicates node merging failed - coincident nodes not properly merged +- Check `MergeOccurrenceNodes = True` is set +- Verify tolerance (0.01 mm recommended) +- Run node merge after every FEM update, not just once + +## Configuration + +The workflow auto-detects assembly FEMs, but you can configure behavior: + +```json +{ + "nx_settings": { + "expression_part": "M1_Blank", // Override auto-detection + "component_fems": [ // Explicit list of FEMs to update + "M1_Blank_fem1.fem", + "M1_Vertical_Support_Skeleton_fem1.fem" + ], + "afm_file": "ASSY_M1_assyfem1.afm" + } +} +``` + +## Implementation Reference + +See `optimization_engine/solve_simulation.py` for the full implementation: + +- `detect_assembly_fem()` - Detects if assembly workflow needed +- `update_expressions_in_part()` - Step 1 implementation +- `update_fem_part()` - Step 2 implementation +- `update_assembly_fem()` - Step 3 implementation +- `solve_simulation_file()` - Step 4 implementation + +## Tips + +1. **Start with baseline solve**: Before optimization, manually verify the full workflow completes in NX +2. **Check mesh quality**: Poor mesh quality after updates can cause solve failures +3. **Monitor memory**: Assembly FEMs with many components use significant memory +4. **Use Foreground mode**: For multi-subcase solutions, Foreground mode ensures all subcases complete diff --git a/docs/ZERNIKE_INTEGRATION.md b/docs/ZERNIKE_INTEGRATION.md new file mode 100644 index 00000000..f5768b6d --- /dev/null +++ b/docs/ZERNIKE_INTEGRATION.md @@ -0,0 +1,313 @@ +# Zernike Wavefront Analysis Integration + +This document describes how to use Atomizer's Zernike analysis capabilities for telescope mirror optimization. + +## Overview + +Atomizer includes a full Zernike polynomial decomposition system for analyzing wavefront errors (WFE) in telescope mirror FEA simulations. The system: + +- Extracts nodal displacements from NX Nastran OP2 files +- Fits Zernike polynomials using Noll indexing (optical standard) +- Computes RMS metrics (global and filtered) +- Analyzes individual aberrations (astigmatism, coma, trefoil, etc.) +- Supports multi-subcase analysis (different gravity orientations) + +## Quick Start + +### Simple Extraction + +```python +from optimization_engine.extractors import extract_zernike_from_op2 + +# Extract Zernike metrics for a single subcase +result = extract_zernike_from_op2( + op2_file="model-solution_1.op2", + subcase="20" # 20 degree elevation +) + +print(f"Global RMS: {result['global_rms_nm']:.2f} nm") +print(f"Filtered RMS: {result['filtered_rms_nm']:.2f} nm") +print(f"Astigmatism: {result['astigmatism_rms_nm']:.2f} nm") +``` + +### In Optimization Objective + +```python +from optimization_engine.extractors.zernike_helpers import create_zernike_objective + +# Create objective function +zernike_obj = create_zernike_objective( + op2_finder=lambda: sim_dir / "model-solution_1.op2", + subcase="20", + metric="filtered_rms_nm" +) + +# Use in Optuna trial +def objective(trial): + # ... suggest parameters ... + # ... run simulation ... + + rms = zernike_obj() + return rms +``` + +## RMS Calculation Method + +**IMPORTANT**: Atomizer uses the correct surface-based RMS calculation matching optical standards: + +```python +# Global RMS = sqrt(mean(W^2)) - RMS of actual WFE surface values +global_rms = sqrt(mean(W_nm ** 2)) + +# Filtered RMS = sqrt(mean(W_residual^2)) +# where W_residual = W_nm - Z[:, :4] @ coeffs[:4] (low-order fit subtracted) +filtered_rms = sqrt(mean(W_residual ** 2)) +``` + +This is **different** from summing Zernike coefficients! The RMS is computed from the actual WFE surface values, not from `sqrt(sum(coeffs^2))`. + +## Available Metrics + +### RMS Metrics +| Metric | Description | +|--------|-------------| +| `global_rms_nm` | RMS of entire WFE surface: `sqrt(mean(W^2))` | +| `filtered_rms_nm` | RMS after removing modes 1-4 (piston, tip, tilt, defocus) | +| `rms_filter_j1to3_nm` | RMS after removing only modes 1-3 (keeps defocus) - "optician workload" | + +### Aberration Magnitudes +| Metric | Zernike Modes | Description | +|--------|--------------|-------------| +| `defocus_nm` | J4 | Focus error | +| `astigmatism_rms_nm` | J5 + J6 | Combined astigmatism | +| `coma_rms_nm` | J7 + J8 | Combined coma | +| `trefoil_rms_nm` | J9 + J10 | Combined trefoil | +| `spherical_nm` | J11 | Primary spherical | + +## Multi-Subcase Analysis + +For telescope mirrors, gravity orientation affects surface shape. Standard subcases: + +| Subcase | Description | +|---------|-------------| +| 20 | Low elevation (operational) | +| 40 | Mid-low elevation | +| 60 | Mid-high elevation | +| 90 | Horizontal (polishing orientation) | + +### Extract All Subcases + +```python +from optimization_engine.extractors import ZernikeExtractor + +extractor = ZernikeExtractor("model.op2") +results = extractor.extract_all_subcases(reference_subcase="20") + +for label, metrics in results.items(): + print(f"Subcase {label}: {metrics['filtered_rms_nm']:.1f} nm") +``` + +### Relative Analysis + +Compare deformation between orientations: + +```python +from optimization_engine.extractors.zernike_helpers import create_relative_zernike_objective + +# Minimize deformation at 20 deg relative to polishing position (90 deg) +relative_obj = create_relative_zernike_objective( + op2_finder=lambda: sim_dir / "model.op2", + target_subcase="20", + reference_subcase="90" +) + +relative_rms = relative_obj() +``` + +## Optimization Configuration + +### Example: Single Objective (Filtered RMS) + +```json +{ + "objectives": [ + { + "name": "filtered_rms", + "direction": "minimize", + "extractor": "zernike", + "extractor_config": { + "subcase": "20", + "metric": "filtered_rms_nm" + } + } + ] +} +``` + +### Example: Multi-Objective (RMS + Mass) + +```json +{ + "objectives": [ + { + "name": "filtered_rms_20deg", + "direction": "minimize", + "extractor": "zernike", + "extractor_config": { + "subcase": "20", + "metric": "filtered_rms_nm" + } + }, + { + "name": "mass", + "direction": "minimize", + "extractor": "mass_from_expression" + } + ], + "optimization_settings": { + "sampler": "NSGA-II", + "protocol": 11 + } +} +``` + +### Example: Constrained (Stress + Aberration Limits) + +```json +{ + "constraints": [ + { + "name": "astigmatism_limit", + "type": "upper_bound", + "threshold": 50.0, + "extractor": "zernike", + "extractor_config": { + "subcase": "90", + "metric": "astigmatism_rms_nm" + } + } + ] +} +``` + +## Advanced: ZernikeObjectiveBuilder + +For complex multi-subcase objectives: + +```python +from optimization_engine.extractors.zernike_helpers import ZernikeObjectiveBuilder + +builder = ZernikeObjectiveBuilder( + op2_finder=lambda: sim_dir / "model.op2" +) + +# Weight operational positions more heavily +builder.add_subcase_objective("20", "filtered_rms_nm", weight=1.0) +builder.add_subcase_objective("40", "filtered_rms_nm", weight=0.5) +builder.add_subcase_objective("60", "filtered_rms_nm", weight=0.5) + +# Create combined objective (weighted sum) +objective = builder.build_weighted_sum() + +# Or: worst-case across subcases +worst_case_obj = builder.build_max() +``` + +## Zernike Settings + +### Configuration Options + +| Setting | Default | Description | +|---------|---------|-------------| +| `n_modes` | 50 | Number of Zernike modes to fit | +| `filter_orders` | 4 | Low-order modes to filter (1-4 = piston through defocus) | +| `displacement_unit` | "mm" | Unit of displacement in OP2 ("mm", "m", "um", "nm") | + +### Unit Conversions + +Wavefront error (WFE) is computed as: + +``` +WFE_nm = 2 * displacement * unit_conversion +``` + +Where `unit_conversion` converts to nanometers: +- mm: 1e6 +- m: 1e9 +- um: 1e3 + +The factor of 2 accounts for the optical convention (surface error doubles as wavefront error for reflection). + +## NX Nastran Setup + +### Required Subcases + +Your NX Nastran model should have subcases for each gravity orientation: + +``` +SUBCASE 20 + SUBTITLE=20 deg elevation + LOAD = ... + +SUBCASE 40 + SUBTITLE=40 deg elevation + LOAD = ... +``` + +The extractor identifies subcases by: +1. Numeric value in SUBTITLE (preferred) +2. SUBCASE ID number + +### Output Requests + +Ensure displacement output is requested: + +``` +SET 999 = ALL +DISPLACEMENT(SORT1,REAL) = 999 +``` + +## Migration from Legacy Scripts + +If you were using `zernike_Post_Script_NX.py`: + +| Old Approach | Atomizer Equivalent | +|--------------|---------------------| +| Manual OP2 parsing | `ZernikeExtractor` | +| `compute_zernike_coeffs_chunked()` | `compute_zernike_coefficients()` | +| `write_exp_file()` | Configure as objective/constraint | +| HTML reports | Dashboard visualization (TBD) | +| RMS log CSV | Optuna database + export | + +### Key Differences + +1. **Integration**: Zernike is now an extractor like displacement/stress +2. **Optimization**: Direct use as objectives/constraints in Optuna +3. **Multi-objective**: Native NSGA-II support for RMS + mass Pareto optimization +4. **Neural Acceleration**: Can train surrogate on Zernike metrics (Protocol 12) + +## Example Study Structure + +``` +studies/ + mirror_optimization/ + 1_setup/ + optimization_config.json + model/ + ASSY_M1.prt + ASSY_M1_assyfem1.afm + ASSY_M1_assyfem1_sim1.sim + 2_results/ + study.db + zernike_analysis/ + trial_001_zernike.json + trial_002_zernike.json + ... + run_optimization.py +``` + +## See Also + +- [examples/optimization_config_zernike_mirror.json](../examples/optimization_config_zernike_mirror.json) - Full example configuration +- [optimization_engine/extractors/extract_zernike.py](../optimization_engine/extractors/extract_zernike.py) - Core implementation +- [optimization_engine/extractors/zernike_helpers.py](../optimization_engine/extractors/zernike_helpers.py) - Helper functions diff --git a/examples/Zernike_old_reference/nx_post_each_iter.py b/examples/Zernike_old_reference/nx_post_each_iter.py new file mode 100644 index 00000000..5aa37f0b --- /dev/null +++ b/examples/Zernike_old_reference/nx_post_each_iter.py @@ -0,0 +1,332 @@ +# nx_post_each_iter.py +import os, subprocess +import NXOpen +from datetime import datetime +import csv, re + +# --- SETTINGS --- +TEST_ENV_PY = r"C:\Users\antoi\anaconda3\envs\test_env\python.exe" +SCRIPT_NAME = "zernike_Post_Script_NX.py" # your script in the .sim folder +OP2_NAME = "assy_m1_assyfem1_sim1-solution_1.op2" +EXP_NAME = "Iteration_results_expression.exp" +TIMEOUT = None # e.g., 900 for 15 min +# Option A: set via env NX_GEOM_PART_NAME, else hardcode your CAD part name here. +GEOM_PART_NAME = os.environ.get("NX_GEOM_PART_NAME", "ASSY_M1_assyfem1") + +# --------------- + +def import_iteration_results_exp(exp_path: str, lw) -> bool: + """Import EXP into current Work part (Replace) and update.""" + theSession = NXOpen.Session.GetSession() + workPart = theSession.Parts.BaseWork + + if not os.path.isfile(exp_path): + lw.WriteLine(f"[EXP][ERROR] File not found: {exp_path}") + return False + + mark_import = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Import Expressions") + try: + modified, err_msgs = workPart.Expressions.ImportFromFile( + exp_path, NXOpen.ExpressionCollection.ImportMode.Replace + ) + # surface any parsing messages + try: + if err_msgs: + for m in err_msgs: + lw.WriteLine(f"[EXP][WARN] {m}") + except Exception: + pass + + mark_update = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "NX update") + nErrs = theSession.UpdateManager.DoUpdate(mark_update) + theSession.DeleteUndoMark(mark_update, "NX update") + + theSession.SetUndoMarkName(mark_import, "Expressions") + theSession.DeleteUndoMark(mark_import, None) + + lw.WriteLine(f"[EXP] Imported OK (modified={modified}, nErrs={nErrs})") + return True + except Exception as ex: + lw.WriteLine(f"[EXP][FATAL] {ex}") + try: + theSession.DeleteUndoMark(mark_import, None) + except Exception: + pass + return False + +def export_all_named_expressions_to_exp(workPart, out_path, lw): + """ + Export expressions to an .exp file using the 3-arg signature: + ExportToFile(, , ) + Works across NX versions where enums live under either: + NXOpen.ExpressionCollection.ExportMode / SortType + or + NXOpen.ExpressionCollectionExportMode / ExpressionCollectionSortType + """ + try: + if not out_path.lower().endswith(".exp"): + out_path += ".exp" + + mode_cls = getattr(NXOpen.ExpressionCollection, "ExportMode", + getattr(NXOpen, "ExpressionCollectionExportMode", None)) + sort_cls = getattr(NXOpen.ExpressionCollection, "SortType", + getattr(NXOpen, "ExpressionCollectionSortType", None)) + if mode_cls is None or sort_cls is None: + raise RuntimeError("Unsupported NX/Open version: ExportMode/SortType enums not found") + + workPart.Expressions.ExportToFile(mode_cls.WorkPart, out_path, sort_cls.AlphaNum) + lw.WriteLine(f"[EXP-EXPORT] Wrote: {out_path}") + return True + except Exception as ex: + lw.WriteLine(f"[EXP-EXPORT][ERROR] {ex}") + return False + + + + + +def parse_exp_file_to_dict(exp_path): + """ + Parse NX .exp lines like: + // comments + [MilliMeter]SomeName=0.001234 + SomeOther=42 + into { 'SomeName': numeric_or_str, ... }. + """ + out = {} + num = re.compile(r'^[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$') + + with open(exp_path, 'r', encoding='utf-8', errors='ignore') as f: + for line in f: + s = line.strip() + if not s or s.startswith('//'): + continue + if '=' not in s: + continue + left, right = s.split('=', 1) + # strip optional [Unit] prefixes on left side + left = re.sub(r'\[[^\]]*\]\s*', '', left).strip() + key = left + val = right.strip() + + # try numeric + v = val + if num.match(val): + try: + v = float(val) + except Exception: + pass + out[key] = v + return out + +def append_named_exprs_row(results_dir, run_id, run_dt, expr_dict, lw, source, part_name): + """ + Appends one row to Results/NX_named_expressions_log.csv + Columns auto-extend for new expression names. + Adds metadata: RunID, RunDateTimeLocal, Source ('SIM'|'PART'), PartName. + """ + log_csv = os.path.join(results_dir, "NX_named_expressions_log.csv") + meta = { + "RunID": run_id, + "RunDateTimeLocal": run_dt.strftime("%Y-%m-%d %H:%M:%S"), + "Source": source, + "PartName": part_name, + } + row = {**meta, **expr_dict} + + # Create or extend header as needed + if not os.path.exists(log_csv): + fieldnames = list(meta.keys()) + sorted(expr_dict.keys()) + with open(log_csv, "w", newline="", encoding="utf-8") as f: + w = csv.DictWriter(f, fieldnames=fieldnames) + w.writeheader() + w.writerow({k: row.get(k, "") for k in fieldnames}) + lw.WriteLine(f"[EXP-EXPORT] Created CSV log: {log_csv}") + return + + with open(log_csv, "r", newline="", encoding="utf-8") as f: + r = csv.reader(f) + existing = list(r) + + if not existing: + fieldnames = list(meta.keys()) + sorted(expr_dict.keys()) + with open(log_csv, "w", newline="", encoding="utf-8") as f: + w = csv.DictWriter(f, fieldnames=fieldnames) + w.writeheader() + w.writerow({k: row.get(k, "") for k in fieldnames}) + lw.WriteLine(f"[EXP-EXPORT] Rebuilt CSV log: {log_csv}") + return + + header = existing[0] + known = set(header) + new_cols = [c for c in meta.keys() if c not in known] + \ + sorted([k for k in expr_dict.keys() if k not in known]) + if new_cols: + header = header + new_cols + + with open(log_csv, "w", newline="", encoding="utf-8") as f: + w = csv.DictWriter(f, fieldnames=header) + w.writeheader() + # Rewrite old rows (padding any new columns) + for data in existing[1:]: + old_row = {h: (data[i] if i < len(data) else "") for i, h in enumerate(existing[0])} + for c in new_cols: + old_row.setdefault(c, "") + w.writerow({k: old_row.get(k, "") for k in header}) + # Append new row + w.writerow({k: row.get(k, "") for k in header}) + + lw.WriteLine(f"[EXP-EXPORT] Appended CSV log: {log_csv}") + +def export_geometry_named_expressions(sim_part, results_dir, run_id, lw): + """ + Switch display to the geometry part (like in your journal), export expressions, then restore. + GEOM_PART_NAME must be resolvable via Session.Parts.FindObject. + """ + theSession = NXOpen.Session.GetSession() + original_display = theSession.Parts.BaseDisplay + original_work = theSession.Parts.BaseWork + + try: + if not GEOM_PART_NAME: + lw.WriteLine("[EXP-EXPORT][WARN] GEOM_PART_NAME not set; skipping geometry export.") + return False, None, None + + try: + part1 = theSession.Parts.FindObject(GEOM_PART_NAME) + except Exception: + lw.WriteLine(f"[EXP-EXPORT][WARN] Geometry part not found by name: {GEOM_PART_NAME}") + return False, None, None + + mark = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Change Displayed Part") + status, pls = theSession.Parts.SetActiveDisplay( + part1, + NXOpen.DisplayPartOption.AllowAdditional, + NXOpen.PartDisplayPartWorkPartOption.UseLast + ) + # Switch to Modeling, like your journal + try: + theSession.ApplicationSwitchImmediate("UG_APP_MODELING") + except Exception: + pass + + workPart = theSession.Parts.Work + out_exp = os.path.join(results_dir, f"NamedExpressions_PART_{run_id}.exp") + ok = export_all_named_expressions_to_exp(workPart, out_exp, lw) + + if pls is not None: + pls.Dispose() + theSession.DeleteUndoMark(mark, None) + + # Part name for logging + part_name = os.path.splitext(os.path.basename(workPart.FullPath))[0] if workPart and workPart.FullPath else GEOM_PART_NAME + return ok, out_exp if ok else None, part_name + + except Exception as ex: + lw.WriteLine(f"[EXP-EXPORT][ERROR] Geometry export failed: {ex}") + return False, None, None + + finally: + # Try to restore prior display/work part and CAE app + try: + if original_display is not None: + theSession.Parts.SetActiveDisplay( + original_display, + NXOpen.DisplayPartOption.AllowAdditional, + NXOpen.PartDisplayPartWorkPartOption.UseLast + ) + except Exception: + pass + try: + theSession.ApplicationSwitchImmediate("UG_APP_SFEM") # back to CAE if applicable + except Exception: + pass + + + +def run_post(sim_dir, lw, run_id, results_dir): + post_script = os.path.join(sim_dir, SCRIPT_NAME) + op2 = os.path.join(sim_dir, OP2_NAME) + + if not os.path.exists(TEST_ENV_PY): + lw.WriteLine(f"[ERROR] test_env python not found: {TEST_ENV_PY}") + return 3 + if not os.path.exists(post_script): + lw.WriteLine(f"[ERROR] Post script not found: {post_script}") + return 4 + if not os.path.exists(op2): + lw.WriteLine(f"[ERROR] OP2 not found: {op2}") + return 2 + + cmd = [TEST_ENV_PY, post_script, "--op2", op2] + lw.WriteLine("[POST] " + " ".join(cmd)) + lw.WriteLine(f"[POST] cwd={sim_dir}") + + env = os.environ.copy() + env["ZERNIKE_RUN_ID"] = run_id + env["ZERNIKE_RESULTS_DIR"] = results_dir + + proc = subprocess.run( + cmd, cwd=sim_dir, capture_output=True, text=True, + shell=False, timeout=TIMEOUT, env=env + ) + if proc.stdout: + lw.WriteLine(proc.stdout) + if proc.stderr: + lw.WriteLine("[STDERR]\n" + proc.stderr) + lw.WriteLine(f"[INFO] Post finished (rc={proc.returncode})") + return proc.returncode + + + + + +def main(): + s = NXOpen.Session.GetSession() + lw = s.ListingWindow; lw.Open() + sim_part = s.Parts.BaseWork + sim_dir = os.path.dirname(sim_part.FullPath) + + # --- New: Results folder + a run id/timestamp we can also hand to Zernike --- + results_dir = os.path.join(sim_dir, "Results") + os.makedirs(results_dir, exist_ok=True) + run_dt = datetime.now() + run_id = run_dt.strftime("%Y%m%d_%H%M%S") + + # --- Run the Zernike post (hand it the same run id & results dir via env) --- + rc = run_post(sim_dir, lw, run_id, results_dir) + + if rc != 0: + lw.WriteLine(f"[POST] Zernike post failed (rc={rc}). Skipping EXP import and NX expr logging.") + return # or 'pass' if you prefer to continue anyway + + # Import EXP if it exists — prefer Results/, then fall back to the sim folder + exp_candidates = [ + os.path.join(results_dir, EXP_NAME), + os.path.join(sim_dir, EXP_NAME), + ] + for exp_path in exp_candidates: + if os.path.isfile(exp_path): + import_iteration_results_exp(exp_path, lw) + break + else: + lw.WriteLine(f"[EXP] Skipped: not found → {exp_candidates[0]}") + + + # --- Export SIM (work CAE part) expressions and append log --- + sim_part_name = os.path.splitext(os.path.basename(sim_part.FullPath))[0] if sim_part and sim_part.FullPath else "SIM" + named_exp_sim = os.path.join(results_dir, f"NamedExpressions_SIM_{run_id}.exp") + if export_all_named_expressions_to_exp(sim_part, named_exp_sim, lw): + exprs_sim = parse_exp_file_to_dict(named_exp_sim) + append_named_exprs_row(results_dir, run_id, run_dt, exprs_sim, lw, source="SIM", part_name=sim_part_name) + + # --- Export GEOMETRY (modeling) part expressions like your journal, and append log --- + ok_part, part_exp_path, part_name = export_geometry_named_expressions(sim_part, results_dir, run_id, lw) + if ok_part and part_exp_path: + exprs_part = parse_exp_file_to_dict(part_exp_path) + append_named_exprs_row(results_dir, run_id, run_dt, exprs_part, lw, source="PART", part_name=part_name) + + + +if __name__ == "__main__": + main() diff --git a/examples/Zernike_old_reference/zernike_Post_Script_NX.py b/examples/Zernike_old_reference/zernike_Post_Script_NX.py new file mode 100644 index 00000000..ae8bbee0 --- /dev/null +++ b/examples/Zernike_old_reference/zernike_Post_Script_NX.py @@ -0,0 +1,1012 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python3 +""" +Zernike per Subcase (relative to 20 deg) -> HTML + CSVs (HEADLESS) + dual-reference RMS (vs 20 deg and vs 90 deg) + +- Auto-targets OP2: + * default: m1_optimization_baseline_Isogrid_fem1_i_fem1_sim1_test2-solution_1.op2 in CWD + * or: pass --op2 FULLPATH +- Auto-loads matching .dat/.bdf (same-basename preferred; else first found) +- Extracts subcases by label/ID (expects 90,20,40,60) +- Uses 20 deg as the reference for plots/pages and the *_Rel20 fields (vs 20 deg) +- Also computes RMS relative to 90 deg and exports them: + * EXP: RMS__Global_Rel20, RMS__Filtered_Rel20, RMS__Global_Rel90, RMS__Filtered_Rel90 + * LOG: Rel_GlobalRMS_nm__vs20, Rel_FilteredRMS_nm__vs20, Rel_GlobalRMS_nm__vs90, Rel_FilteredRMS_nm__vs90 +- Outputs: + * Combined nodal CSV (abs 20 deg + relative others vs 20 deg) + * One HTML for 20 deg (absolute) and one HTML per other subcase (relative to 20 deg), + with each relative page's RMS table also showing the absolute RMS for that angle + * One HTML for 90 deg (absolute) with manufacturing metrics (numeric only) + * Zernike coeff CSVs (|coeff| per page; with labels and optional bar chart) + * RMS summary CSV (ABS for all; REL20 for non-reference) + * EXP file with 90/20/40/60 ABS; REL20 (vs 20 deg); and REL90 (vs 90 deg) + * Appends a row to Results/RMS_log.csv (ABS & REL vs 20 deg & REL vs 90 deg) +- HTML auto-open controlled by OPEN_HTML flag + +Requires: numpy pandas plotly pyNastran matplotlib +""" + +import os, webbrowser, re, argparse, sys +import numpy as np +import pandas as pd +from math import factorial +from numpy.linalg import LinAlgError +from datetime import datetime + +# Best-effort UTF-8 console (ignored if not supported) +try: + sys.stdout.reconfigure(encoding="utf-8") + sys.stderr.reconfigure(encoding="utf-8") +except Exception: + pass + +# Try messagebox for friendlier fatal popup if available; otherwise print-only +try: + from tkinter import messagebox # optional +except Exception: + messagebox = None + +# Plotly / mesh +import plotly.graph_objects as go +from plotly.subplots import make_subplots +from matplotlib.tri import Triangulation + +# Nastran IO +from pyNastran.op2.op2 import OP2 +from pyNastran.bdf.bdf import BDF + +# ============ MONKEY PATCH FOR UNKNOWN NASTRAN VERSIONS ============ +import pyNastran.op2.op2_interface.version as pynastran_version + +_original_parse = pynastran_version.parse_nastran_version + +def patched_parse_nastran_version(data, version, encoding, log): + try: + return _original_parse(data, version, encoding, log) + except RuntimeError as e: + if 'unknown version' in str(e): + version_str = str(e).split('=')[1].strip("')") + log.warning(f'Unknown Nastran version {version_str}, attempting to parse anyway') + return 'msc', version_str # Assume MSC Nastran format + raise + +pynastran_version.parse_nastran_version = patched_parse_nastran_version +print("[INFO] Applied pyNastran version compatibility patch", flush=True) +# =================================================================== + + +# ---------------- Config ---------------- +N_MODES = 50 +AMP = 2.0 # visual scale for residual plot +PANCAKE = 10.0 # flattens Z range for viewing +PLOT_DOWNSAMPLE = 10000 +FILTER_LOW_ORDERS = 4 # piston, tip, tilt, defocus +SHOW_ZERNIKE_BAR = True # adds a horizontal bar chart of |coeff| with labels + +REQUIRED_SUBCASES = [90, 20, 40, 60] + +# Labels +POLISH_LABEL = "90" # polishing orientation (horizontal) +POLISH_TITLE = "90 deg" # for plot/table titles +REF_LABEL = "20" # reference subcase label as string +REF_TITLE = "20 deg" # for plot/table titles + +# Displacement unit in OP2 -> nm scale for WFE = 2*Disp_Z +DISP_SRC_UNIT = "mm" # "mm" or "m" +NM_PER_UNIT = 1e6 if DISP_SRC_UNIT == "mm" else 1e9 # 1 mm = 1e6 nm, 1 m = 1e9 nm + +# Name of fixed OP2 (used when --op2 is not supplied) +OP2_FIXED_NAME = "assy_m1_assyfem1_sim1-solution_1.op2" + +# Auto-open HTML reports? +OPEN_HTML = False +# ---------------------------------------- + + +# --------- Zernike utilities ---------- +def noll_indices(j: int): + if j < 1: + raise ValueError("Noll index j must be >= 1") + count = 0 + n = 0 + while True: + if n == 0: + ms = [0] + elif n % 2 == 0: + ms = [0] + [m for k in range(1, n//2 + 1) for m in (-2*k, 2*k)] + else: + ms = [m for k in range(0, (n+1)//2) for m in (-(2*k+1), (2*k+1))] + for m in ms: + count += 1 + if count == j: + return n, m + n += 1 + + +def zernike_noll(j, r, th): + n, m = noll_indices(j) + R = np.zeros_like(r) + for s in range((n-abs(m))//2 + 1): + c = ((-1)**s * factorial(n-s) / + (factorial(s) * + factorial((n+abs(m))//2 - s) * + factorial((n-abs(m))//2 - s))) + R += c * r**(n-2*s) + if m == 0: + return R + return R * (np.cos(m*th) if m > 0 else np.sin(-m*th)) + +def compute_zernike_coeffs_chunked(X, Y, vals, n_modes, chunk_size=100000): + Xc, Yc = X - np.mean(X), Y - np.mean(Y) + R = float(np.max(np.hypot(Xc, Yc))) + r = np.hypot(Xc/R, Yc/R).astype(np.float32) + th = np.arctan2(Yc, Xc).astype(np.float32) + mask = (r <= 1.0) & ~np.isnan(vals) + if not np.any(mask): + raise RuntimeError("No valid points inside unit disk.") + idx = np.nonzero(mask)[0] + m = int(n_modes) + G = np.zeros((m, m), dtype=np.float64) # Z^T Z + h = np.zeros((m,), dtype=np.float64) # Z^T v) + v = vals.astype(np.float64) + + for start in range(0, len(idx), chunk_size): + sl = idx[start:start+chunk_size] + r_b, th_b, v_b = r[sl], th[sl], v[sl] + Zb = np.column_stack([zernike_noll(j, r_b, th_b).astype(np.float32) + for j in range(1, m+1)]) + G += (Zb.T @ Zb).astype(np.float64) + h += (Zb.T @ v_b).astype(np.float64) + try: + coeffs = np.linalg.solve(G, h) + except LinAlgError: + coeffs = np.linalg.lstsq(G, h, rcond=None)[0] + return coeffs, R +# -------------------------------------- + + +# ------------- IO helpers ------------- +def find_dat_or_bdf(op2_path: str): + folder = os.path.dirname(op2_path) + base = os.path.splitext(os.path.basename(op2_path))[0] + for ext in (".dat", ".bdf"): + cand = os.path.join(folder, base + ext) + if os.path.exists(cand): + return cand + for name in os.listdir(folder): + if name.lower().endswith((".dat", ".bdf")): + return os.path.join(folder, name) + raise FileNotFoundError("No .dat or .bdf found in folder; geometry required.") + +def pick_files_cli(): + ap = argparse.ArgumentParser() + ap.add_argument("--op2", default=None, help="Full path to OP2 (optional)") + ap.add_argument("--simdir", default=None, help="Directory to search (optional)") + args = ap.parse_args() + + if args.op2: + op2_path = args.op2 + else: + base_dir = args.simdir or os.getcwd() + op2_path = os.path.join(base_dir, OP2_FIXED_NAME) + + if not os.path.exists(op2_path): + raise FileNotFoundError(f"OP2 not found: {op2_path}") + + dat_path = find_dat_or_bdf(op2_path) + return op2_path, dat_path + +def read_geometry(dat_path): + bdf = BDF() + bdf.read_bdf(dat_path) + node_geo = {int(nid): node.get_position() for nid, node in bdf.nodes.items()} + return node_geo + +def _extract_first_int(text: str): + if not isinstance(text, str): + return None + m = re.search(r'-?\d+', text) + return int(m.group(0)) if m else None + +def read_displacements_by_subcase(op2_path): + """ + Return dict with keys {'90','20','40','60'}. + Priority: subtitle numeric match, else isubcase numeric match. + If exact set not found: + - If 4+ numeric subcases exist, remap the 4 smallest to 90,20,40,60 (logged) + - Else: raise with a detailed dump. + Also writes an index CSV next to the OP2 for inspection. + """ + want = REQUIRED_SUBCASES[:] # [90,20,40,60] + out_exact = {} + debug_rows = [] + + op2 = OP2() + op2.read_op2(op2_path) + if not op2.displacements: + raise RuntimeError("No displacement subcases found in OP2.") + + for key, darr in op2.displacements.items(): + data = darr.data + dmat = data[0] if data.ndim == 3 else (data if data.ndim == 2 else None) + if dmat is None: + continue + + ngt = darr.node_gridtype.astype(int) + node_ids = ngt if ngt.ndim == 1 else ngt[:, 0] + + subtitle_raw = getattr(darr, 'subtitle', None) + isub_raw = getattr(darr, 'isubcase', None) + isub_int = int(isub_raw) if isinstance(isub_raw, int) else None + sub_from_text = _extract_first_int(subtitle_raw) if isinstance(subtitle_raw, str) else None + + debug_rows.append({ + "table_key": str(key), + "isubcase": isub_int, + "subtitle": str(subtitle_raw) if subtitle_raw is not None else "", + "subtitle_int": sub_from_text if sub_from_text is not None else "", + "nnodes": int(len(node_ids)), + "_node_ids": node_ids.astype(int), + "_dmat": dmat.copy(), + }) + + if isinstance(sub_from_text, int) and sub_from_text in want: + out_exact[str(sub_from_text)] = {"node_ids": node_ids.astype(int), "disp": dmat.copy()} + continue + if isinstance(isub_int, int) and isub_int in want: + out_exact[str(isub_int)] = {"node_ids": node_ids.astype(int), "disp": dmat.copy()} + + try: + folder = os.path.dirname(op2_path) + base = os.path.splitext(os.path.basename(op2_path))[0] + idx_path = os.path.join(folder, f"{base}_op2_displ_index.csv") + pd.DataFrame([{k: v for k, v in r.items() if not k.startswith('_')} for r in debug_rows])\ + .to_csv(idx_path, index=False) + print(f"[DEBUG] Wrote displacement index: {idx_path}", flush=True) + except Exception as e: + print(f"[WARN] Could not write displacement index CSV: {e}", flush=True) + + if all(str(x) in out_exact for x in want): + print("[INFO] Found exact subcases 90, 20, 40 and 60 via labels.", flush=True) + return out_exact + + numerics = [] + for r in debug_rows: + n = r.get("subtitle_int") + if not isinstance(n, int): + n = r.get("isubcase") if isinstance(r.get("isubcase"), int) else None + if isinstance(n, int): + numerics.append((n, r)) + numerics = sorted(numerics, key=lambda t: t[0]) + + if len(numerics) >= 4: + chosen = numerics[:4] + mapping = dict(zip([c[0] for c in chosen], want)) + out = {} + for n, r in chosen: + lbl = str(mapping[n]) + out[lbl] = {"node_ids": r["_node_ids"], "disp": r["_dmat"]} + print("[WARN] Exact 90/20/40/60 not found; remapped smallest numeric subcases " + f"{[c[0] for c in chosen]} -> {want}", flush=True) + return out + + print("\n[DEBUG] Displacement entries found (dump):", flush=True) + if debug_rows: + header = f'{"table_key":<18} {"isubcase":>7} {"subtitle":<40} {"subtitle_int":>12} {"nnodes":>8}' + print(header); print("-"*len(header)) + for r in debug_rows: + print(f'{r["table_key"]:<18} {str(r["isubcase"]):>7} {r["subtitle"]:<40} ' + f'{str(r["subtitle_int"]):>12} {r["nnodes"]:>8}', flush=True) + missing = [x for x in want if str(x) not in out_exact] + raise RuntimeError(f"Required subcases missing: {missing}. " + "Ensure SUBTITLEs (or isubcase IDs) are exactly 90, 20, 40 and 60.") +# -------------------------------------- + + +# ------------- Core logic ------------- +def build_dataframe_for_subcase(label, node_ids, dmat, node_geo): + rows = [] + for nid, vec in zip(node_ids, dmat): + x0, y0, z0 = node_geo.get(int(nid), (np.nan,)*3) + rows.append({ + "Subcase": label, + "NodeLabel": int(nid), + "X0": x0, "Y0": y0, "Z0": z0, + "Disp_Z": float(vec[2]), + "DispUnit": DISP_SRC_UNIT + }) + df = pd.DataFrame(rows) + if df[["X0", "Y0"]].isna().any().any(): + raise RuntimeError("Geometry missing (X0,Y0) for some nodes. Ensure .dat/.bdf matches the OP2.") + return df + +def compute_relative_dz(df_abs_ref, df_abs_ang): + a = df_abs_ref.set_index("NodeLabel") + b = df_abs_ang.set_index("NodeLabel") + common = a.index.intersection(b.index) + + b2 = b.loc[common].copy() + b2["Rel_Disp_Z"] = b2["Disp_Z"] - a.loc[common, "Disp_Z"] + b2.reset_index(inplace=True) + out = df_abs_ang.merge(b2[["NodeLabel", "Rel_Disp_Z"]], on="NodeLabel", how="inner") + out["Rel_WFE_nm"] = 2.0 * out["Rel_Disp_Z"] * NM_PER_UNIT + return out + +def compute_rms_pair_only(X, Y, W_nm): + coeffs, Rmax = compute_zernike_coeffs_chunked(X, Y, W_nm, N_MODES) + Xc = X - np.mean(X); Yc = Y - np.mean(Y) + r = np.hypot(Xc/Rmax, Yc/Rmax); th = np.arctan2(Yc, Xc) + Z = np.column_stack([zernike_noll(j, r, th) for j in range(1, N_MODES+1)]) + W_res_filt = W_nm - Z[:, :FILTER_LOW_ORDERS].dot(coeffs[:FILTER_LOW_ORDERS]) + global_rms = float(np.sqrt((W_nm ** 2).mean())) + filtered_rms = float(np.sqrt((W_res_filt ** 2).mean())) + return global_rms, filtered_rms + +def analyze_manufacturing_metrics(X, Y, W_nm): + """ + Manufacturing-related metrics at any angle. Returns individual mode magnitudes + and filtered RMS with different filter levels. (Numeric only.) + """ + coeffs, Rmax = compute_zernike_coeffs_chunked(X, Y, W_nm, N_MODES) + Xc = X - np.mean(X); Yc = Y - np.mean(Y) + r = np.hypot(Xc/Rmax, Yc/Rmax); th = np.arctan2(Yc, Xc) + Z = np.column_stack([zernike_noll(j, r, th) for j in range(1, N_MODES+1)]) + + W_res_filt3 = W_nm - Z[:, :3].dot(coeffs[:3]) # includes defocus + rms_filt3 = float(np.sqrt((W_res_filt3 ** 2).mean())) + + W_res_filt4 = W_nm - Z[:, :4].dot(coeffs[:4]) # operational metric + rms_filt4 = float(np.sqrt((W_res_filt4 ** 2).mean())) + + astig_rms = float(np.sqrt(coeffs[4]**2 + coeffs[5]**2)) # J5+J6 + coma_rms = float(np.sqrt(coeffs[6]**2 + coeffs[7]**2)) # J7+J8 + trefoil_rms = float(np.sqrt(coeffs[8]**2 + coeffs[9]**2)) # J9+J10 + + return { + "rms_filter_j1to3": rms_filt3, + "rms_filter_j1to4": rms_filt4, + "defocus_nm": float(abs(coeffs[3])), + "astigmatism_rms": astig_rms, + "coma_rms": coma_rms, + "trefoil_rms": trefoil_rms, + "spherical_nm": float(abs(coeffs[10])) + } + + +def zernike_common_name(n: int, m: int) -> str: + names = { + (0, 0): "Piston", + (1, -1): "Tilt X", + (1, 1): "Tilt Y", + (2, 0): "Defocus", + (2, -2): "Astig 45 deg", + (2, 2): "Astig 0 deg", + (3, -1): "Coma X", + (3, 1): "Coma Y", + (3, -3): "Trefoil X", + (3, 3): "Trefoil Y", + (4, 0): "Primary Spherical", + (4, -2): "Secondary Astig X", + (4, 2): "Secondary Astig Y", + (4, -4): "Quadrafoil X", + (4, 4): "Quadrafoil Y", + (5, -1): "Secondary Coma X", + (5, 1): "Secondary Coma Y", + (5, -3): "Secondary Trefoil X", + (5, 3): "Secondary Trefoil Y", + (5, -5): "Pentafoil X", + (5, 5): "Pentafoil Y", + (6, 0): "Secondary Spherical", + } + return names.get((n, m), f"Z(n={n}, m={m})") + +def zernike_label_for_j(j: int) -> str: + n, m = noll_indices(j) + return f"J{j:02d} - {zernike_common_name(n, m)} (n={n}, m={m})" + +def zernike_report(label, X, Y, W_nm, folder, base, is_relative: bool, + abs_pair=None, ref_title="20 deg", mfg_data=None, mfg_data_vs_20=None): + coeffs, Rmax = compute_zernike_coeffs_chunked(X, Y, W_nm, N_MODES) + Xc = X - np.mean(X); Yc = Y - np.mean(Y) + r = np.hypot(Xc/Rmax, Yc/Rmax); th = np.arctan2(Yc, Xc) + Z = np.column_stack([zernike_noll(j, r, th) for j in range(1, N_MODES+1)]) + W_res_filt = W_nm - Z[:, :FILTER_LOW_ORDERS].dot(coeffs[:FILTER_LOW_ORDERS]) + + global_rms = float(np.sqrt((W_nm ** 2).mean())) + filtered_rms = float(np.sqrt((W_res_filt ** 2).mean())) + + # Downsample for display only + n = len(X) + if n > PLOT_DOWNSAMPLE: + rng = np.random.default_rng(42) + sel = rng.choice(n, size=PLOT_DOWNSAMPLE, replace=False) + Xp, Yp, Wp = X[sel], Y[sel], W_res_filt[sel] + else: + Xp, Yp, Wp = X, Y, W_res_filt + + res_amp = AMP * Wp + max_amp = float(np.max(np.abs(res_amp))) if res_amp.size else 1.0 + + # Triangulate the downsampled set + mesh_traces = [] + try: + tri = Triangulation(Xp, Yp) + if tri.triangles is not None and len(tri.triangles) > 0: + i, j, k = tri.triangles.T + mesh_traces.append(go.Mesh3d( + x=Xp, y=Yp, z=res_amp, + i=i, j=j, k=k, + intensity=res_amp, + opacity=0.85, showscale=False + )) + except Exception: + pass + + mesh_traces.append(go.Scatter3d( + x=Xp, y=Yp, z=res_amp, + mode='markers', + marker=dict(size=2), + showlegend=False + )) + + labels = [zernike_label_for_j(j) for j in range(1, N_MODES+1)] + coeff_abs = np.abs(coeffs) + + title_rel = f" (relative to {ref_title})" if is_relative else " (absolute)" + + # Layout + if mfg_data is not None and mfg_data_vs_20 is not None: + if SHOW_ZERNIKE_BAR: + fig = make_subplots( + rows=5, cols=1, + specs=[[{"type":"scene"}], + [{"type":"table"}], + [{"type":"table"}], + [{"type":"table"}], + [{"type":"xy"}]], + row_heights=[0.40, 0.12, 0.12, 0.18, 0.18], + vertical_spacing=0.025, + subplot_titles=[ + f"Surface Residual & RMS of WFE - Subcase {label}{title_rel}", + "RMS Metrics (Absolute 90 deg)", + "Mode Magnitudes at 90 deg", + "Pre-Correction (90 deg - 20 deg)", + "|Zernike Coefficients| (nm)" + ] + ) + else: + fig = make_subplots( + rows=5, cols=1, + specs=[[{"type":"scene"}], + [{"type":"table"}], + [{"type":"table"}], + [{"type":"table"}], + [{"type":"table"}]], + row_heights=[0.40, 0.12, 0.12, 0.18, 0.18], + vertical_spacing=0.025, + subplot_titles=[ + f"Surface Residual & RMS of WFE - Subcase {label}{title_rel}", + "RMS Metrics (Absolute 90 deg)", + "Mode Magnitudes at 90 deg", + "Pre-Correction (90 deg - 20 deg)", + f"Zernike Coefficients ({N_MODES} modes)" + ] + ) + elif SHOW_ZERNIKE_BAR: + fig = make_subplots( + rows=4, cols=1, + specs=[[{"type":"scene"}], + [{"type":"table"}], + [{"type":"table"}], + [{"type":"xy"}]], + row_heights=[0.52, 0.12, 0.24, 0.12], + vertical_spacing=0.035, + subplot_titles=[ + f"Surface Residual & RMS of WFE - Subcase {label}{title_rel}", + "RMS Metrics", + f"Zernike Coefficients ({N_MODES} modes)", + "|Zernike Coefficients| (nm)" + ] + ) + else: + fig = make_subplots( + rows=3, cols=1, + specs=[[{"type":"scene"}], + [{"type":"table"}], + [{"type":"table"}]], + row_heights=[0.60, 0.14, 0.26], + vertical_spacing=0.03, + subplot_titles=[ + f"Surface Residual & RMS of WFE - Subcase {label}{title_rel}", + "RMS Metrics", + f"Zernike Coefficients ({N_MODES} modes)" + ] + ) + + for tr in mesh_traces: + fig.add_trace(tr, row=1, col=1) + + scene = fig.layout.scene + scene.camera = dict(eye=dict(x=0.5, y=0.5, z=0.5)) + scene.zaxis = dict(range=[-max_amp * PANCAKE, max_amp * PANCAKE]) + + # RMS table + if is_relative and abs_pair is not None: + abs_global, abs_filtered = abs_pair + fig.add_trace(go.Table( + header=dict(values=["Metric", "Relative (nm)", "Absolute (nm)"], align="left"), + cells=dict(values=[ + ["Global RMS","Filtered RMS (modes 1-4 removed)"], + [f"{global_rms:.2f}", f"{filtered_rms:.2f}"], + [f"{abs_global:.2f}", f"{abs_filtered:.2f}"], + ], align="left") + ), row=2, col=1) + elif mfg_data is not None and mfg_data_vs_20 is not None: + # 90 deg Absolute RMS (numeric only) + fig.add_trace(go.Table( + header=dict(values=["Metric","Value (nm)"], align="left"), + cells=dict(values=[ + ["Global RMS", "Filtered RMS (J1-J4)"], + [f"{global_rms:.2f}", f"{filtered_rms:.2f}"] + ], align="left") + ), row=2, col=1) + + # Mode magnitudes at 90 deg (absolute) — numeric only + fig.add_trace(go.Table( + header=dict(values=["Mode","Value (nm)"], align="left"), + cells=dict(values=[ + ["Filtered RMS (J1-J3, with defocus)", + "Astigmatism (J5+J6)", + "Coma (J7+J8)", + "Trefoil (J9+J10)", + "Spherical (J11)"], + [f"{mfg_data['rms_filter_j1to3']:.2f}", + f"{mfg_data['astigmatism_rms']:.2f}", + f"{mfg_data['coma_rms']:.2f}", + f"{mfg_data['trefoil_rms']:.2f}", + f"{mfg_data['spherical_nm']:.2f}"] + ], align="left") + ), row=3, col=1) + + # Pre-correction 90 deg - 20 deg — numeric only + fig.add_trace(go.Table( + header=dict(values=["Mode", "Correction (nm)"], align="left"), + cells=dict(values=[ + ["Total RMS (J1-J3 filter)", + "Defocus (J4)", + "Astigmatism (J5+J6)", + "Coma (J7+J8)"], + [f"{mfg_data_vs_20['rms_filter_j1to3']:.2f}", + f"{mfg_data_vs_20['defocus_nm']:.2f}", + f"{mfg_data_vs_20['astigmatism_rms']:.2f}", + f"{mfg_data_vs_20['coma_rms']:.2f}"] + ], align="left") + ), row=4, col=1) + + elif mfg_data is not None: + # Simple manufacturing table (compat) — numeric only + fig.add_trace(go.Table( + header=dict(values=["Metric","Value (nm)"], align="left"), + cells=dict(values=[ + ["Global RMS", + "Filtered RMS (J1-J4)", + "Filtered RMS (J1-J3, with defocus)", + "Defocus (J4)", + "Astigmatism (J5+J6)", + "Coma (J7+J8)", + "Trefoil (J9+J10)", + "Spherical (J11)"], + [f"{global_rms:.2f}", + f"{filtered_rms:.2f}", + f"{mfg_data['rms_filter_j1to3']:.2f}", + f"{mfg_data['defocus_nm']:.2f}", + f"{mfg_data['astigmatism_rms']:.2f}", + f"{mfg_data['coma_rms']:.2f}", + f"{mfg_data['trefoil_rms']:.2f}", + f"{mfg_data['spherical_nm']:.2f}"] + ], align="left") + ), row=2, col=1) + else: + # Standard absolute table + fig.add_trace(go.Table( + header=dict(values=["Metric","Value (nm)"], align="left"), + cells=dict(values=[ + ["Global RMS","Filtered RMS (modes 1-4 removed)"], + [f"{global_rms:.2f}", f"{filtered_rms:.2f}"] + ], align="left") + ), row=2, col=1) + + # Zernike coefficients + if mfg_data is not None and mfg_data_vs_20 is not None: + pass # manufacturing case: bar chart only + elif SHOW_ZERNIKE_BAR: + fig.add_trace(go.Table( + header=dict(values=["Noll j","Label","|Coeff| (nm)"], align="left"), + cells=dict(values=[ + list(range(1, N_MODES+1)), + labels, + list(np.round(coeff_abs, 3)) + ], align="left") + ), row=3, col=1) + else: + fig.add_trace(go.Table( + header=dict(values=["Noll j","Label","|Coeff| (nm)"], align="left"), + cells=dict(values=[ + list(range(1, N_MODES+1)), + labels, + list(np.round(coeff_abs, 3)) + ], align="left") + ), row=3, col=1) + + if SHOW_ZERNIKE_BAR: + bar_row = 5 if (mfg_data is not None and mfg_data_vs_20 is not None) else 4 + fig.add_trace( + go.Bar( + x=coeff_abs.tolist(), + y=labels, + orientation='h', + hovertemplate="%{y}
|Coeff| = %{x:.3f} nm", + showlegend=False + ), + row=bar_row, col=1 + ) + + fig.update_layout( + width=1400, + height=1400 if (mfg_data is not None and mfg_data_vs_20 is not None) + else (1200 if SHOW_ZERNIKE_BAR else 1000), + margin=dict(t=60,b=20) + ) + + # Save + suffix = f"_sub{label}" + html_path = os.path.join(folder, f"{base}{suffix}.html") + fig.write_html(html_path, include_plotlyjs='cdn') + + pd.DataFrame({ + "Noll_j": list(range(1, N_MODES+1)), + "Label": labels, + "Coeff_nm": [float(c) for c in coeffs], + "CoeffAbs_nm": [float(abs(c)) for c in coeffs] + }).to_csv(os.path.join(folder, f"{base}{suffix}_zcoeffs.csv"), index=False) + + if OPEN_HTML: + try: + webbrowser.open('file://' + os.path.abspath(html_path)) + except Exception: + pass + + return { + "global_rms_nm": global_rms, + "filtered_rms_nm": filtered_rms, + "html_path": html_path + } +# -------------------------------------- + + +def write_exp_file(folder, abs_by_angle, rel20_by_angle, rel90_by_angle, mfg_90=None, mfg_90_vs_20=None): + """ + Writes Iteration_results_expression.exp in [MilliMeter]. + For each angle a in {90,20,40,60}, writes: + - RMS_a_Global, RMS_a_Filtered (absolute) + - RMS_a_Global_Rel20, RMS_a_Filtered_Rel20 (relative to 20 deg) + - RMS_a_Global_Rel90, RMS_a_Filtered_Rel90 (relative to 90 deg) + """ + required = ["90", "20", "40", "60"] + out_path = os.path.join(folder, "Iteration_results_expression.exp") + nm_to_mm = lambda x: float(x) * 1e-6 + + def _missing(dct): + return [a for a in required if a not in dct] + + miss_abs = _missing(abs_by_angle) + miss_r20 = _missing(rel20_by_angle) + miss_r90 = _missing(rel90_by_angle) + if miss_abs or miss_r20 or miss_r90: + raise RuntimeError(f"EXP export aborted. Missing sets -> " + f"abs:{miss_abs}, rel20:{miss_r20}, rel90:{miss_r90}.") + + lines = ["// Version: 3"] + for ang in required: + gA, fA = abs_by_angle[ang] + g20, f20 = rel20_by_angle[ang] + g90, f90 = rel90_by_angle[ang] + lines.append(f"[MilliMeter]RMS_{ang}_Global={nm_to_mm(gA):.7f}") + lines.append(f"[MilliMeter]RMS_{ang}_Filtered={nm_to_mm(fA):.7f}") + lines.append(f"[MilliMeter]RMS_{ang}_Global_Rel20={nm_to_mm(g20):.7f}") + lines.append(f"[MilliMeter]RMS_{ang}_Filtered_Rel20={nm_to_mm(f20):.7f}") + lines.append(f"[MilliMeter]RMS_{ang}_Global_Rel90={nm_to_mm(g90):.7f}") + lines.append(f"[MilliMeter]RMS_{ang}_Filtered_Rel90={nm_to_mm(f90):.7f}") + + # Manufacturing metrics for 90 deg (numeric only) + if mfg_90_vs_20 and mfg_90: + lines.append(f"[MilliMeter]RMS_90_Optician_Workload={nm_to_mm(mfg_90_vs_20['rms_filter_j1to3']):.7f}") + lines.append(f"[MilliMeter]RMS_90_Astig_Abs={nm_to_mm(mfg_90['astigmatism_rms']):.7f}") + lines.append(f"[MilliMeter]RMS_90_Coma_Abs={nm_to_mm(mfg_90['coma_rms']):.7f}") + + with open(out_path, "w", encoding="utf-8-sig", newline="\n") as f: + f.write("\n".join(lines) + "\n") + print(f"[OK] Wrote EXP: {out_path}") + + +def main(): + op2_path, dat_path = pick_files_cli() + folder = os.path.dirname(op2_path) + base = os.path.splitext(os.path.basename(op2_path))[0] + + results_dir = os.environ.get("ZERNIKE_RESULTS_DIR") or os.path.join(folder, "Results") + os.makedirs(results_dir, exist_ok=True) + + env_run_id = os.environ.get("ZERNIKE_RUN_ID") + if env_run_id: + run_id = env_run_id + try: + run_dt = datetime.strptime(env_run_id, "%Y%m%d_%H%M%S") + except Exception: + run_dt = datetime.now() + else: + run_dt = datetime.now() + run_id = run_dt.strftime("%Y%m%d_%H%M%S") + + base_ts = f"{base}_{run_id}" + + node_geo = read_geometry(dat_path) + subdisp = read_displacements_by_subcase(op2_path) # dict: '90','20','40','60' + + # ABS for 20 deg (reference for pages) + if REF_LABEL not in subdisp: + raise RuntimeError(f"Reference subcase '{REF_LABEL}' not found after mapping. " + "Check *_op2_displ_index.csv for labels.") + + df_ref_abs = build_dataframe_for_subcase( + REF_LABEL, + subdisp[REF_LABEL]["node_ids"], + subdisp[REF_LABEL]["disp"], + node_geo + ) + df_ref_abs["WFE_nm"] = 2.0 * df_ref_abs["Disp_Z"] * NM_PER_UNIT + Xref = df_ref_abs["X0"].to_numpy() + Yref = df_ref_abs["Y0"].to_numpy() + Wref = df_ref_abs["WFE_nm"].to_numpy() + abs_ref_global, abs_ref_filtered = compute_rms_pair_only(Xref, Yref, Wref) + + # ABS for 90 deg (manufacturing reference; used for REL vs 90) + if POLISH_LABEL not in subdisp: + raise RuntimeError(f"Subcase '{POLISH_LABEL}' not found after mapping.") + df90_abs = build_dataframe_for_subcase( + POLISH_LABEL, + subdisp[POLISH_LABEL]["node_ids"], + subdisp[POLISH_LABEL]["disp"], + node_geo + ) + df90_abs["WFE_nm"] = 2.0 * df90_abs["Disp_Z"] * NM_PER_UNIT + X90 = df90_abs["X0"].to_numpy() + Y90 = df90_abs["Y0"].to_numpy() + W90 = df90_abs["WFE_nm"].to_numpy() + abs90_global, abs90_filtered = compute_rms_pair_only(X90, Y90, W90) + + combined_rows = [] + def emit_rows(df, label, is_relative): + if is_relative: + for row in df.itertuples(index=False): + combined_rows.append({ + "Subcase": label, + "NodeLabel": row.NodeLabel, + "X0": row.X0, "Y0": row.Y0, "Z0": row.Z0, + "Rel_Disp_Z": getattr(row, "Rel_Disp_Z"), + "WFE_nm": getattr(row, "Rel_WFE_nm"), + "DispUnit": DISP_SRC_UNIT, + "RelativeToRef": True, + "RefAngleDeg": REF_LABEL + }) + else: + for row in df.itertuples(index=False): + combined_rows.append({ + "Subcase": label, + "NodeLabel": row.NodeLabel, + "X0": row.X0, "Y0": row.Y0, "Z0": row.Z0, + "Disp_Z": row.Disp_Z, + "WFE_nm": row.WFE_nm, + "DispUnit": DISP_SRC_UNIT, + "RelativeToRef": False, + "RefAngleDeg": "" + }) + + # Emit absolute 20 deg + emit_rows(df_ref_abs, REF_LABEL, is_relative=False) + + metrics_summary = [{ + "Subcase": f"sub{REF_LABEL}", + "Abs_GlobalRMS_nm": abs_ref_global, + "Abs_FilteredRMS_nm": abs_ref_filtered, + "Rel20_GlobalRMS_nm": np.nan, # reference has no relative (vs 20 deg) + "Rel20_FilteredRMS_nm": np.nan + }] + + abs_values_by_angle = {REF_LABEL: (abs_ref_global, abs_ref_filtered), POLISH_LABEL: (abs90_global, abs90_filtered)} + rel20_values_by_angle = {REF_LABEL: (0.0, 0.0)} # 20 vs 20 = 0 + rel90_values_by_angle = {POLISH_LABEL: (0.0, 0.0)} # 90 vs 90 = 0 + + # Process all angles (ABS; REL vs 20 deg for pages) + all_angles = [str(a) for a in REQUIRED_SUBCASES] + for ang in [a for a in all_angles if a != REF_LABEL]: + if ang not in subdisp: + raise RuntimeError(f"Subcase '{ang}' not found after mapping. " + "Check *_op2_displ_index.csv for labels.") + + if ang == POLISH_LABEL: + abs_global, abs_filtered = abs_values_by_angle[ang] + X_abs, Y_abs = X90, Y90 + else: + df_abs = build_dataframe_for_subcase(ang, subdisp[ang]["node_ids"], subdisp[ang]["disp"], node_geo) + W_abs_nm = 2.0 * df_abs["Disp_Z"].to_numpy() * NM_PER_UNIT + X_abs = df_abs["X0"].to_numpy(); Y_abs = df_abs["Y0"].to_numpy() + abs_global, abs_filtered = compute_rms_pair_only(X_abs, Y_abs, W_abs_nm) + abs_values_by_angle[ang] = (abs_global, abs_filtered) + + # REL vs 20 + if ang == POLISH_LABEL: + df_rel20 = compute_relative_dz(df_ref_abs, df90_abs) + else: + df_rel20 = compute_relative_dz(df_ref_abs, df_abs) + emit_rows(df_rel20, ang, is_relative=True) # Only emit CSV rows for 40/60 + + X_rel20 = df_rel20["X0"].to_numpy(); Y_rel20 = df_rel20["Y0"].to_numpy() + W_rel20 = df_rel20["Rel_WFE_nm"].to_numpy() + + # HTML for 40 and 60 only + if ang != POLISH_LABEL: + m_rel20 = zernike_report( + ang, X_rel20, Y_rel20, W_rel20, results_dir, base_ts, + is_relative=True, abs_pair=(abs_global, abs_filtered), ref_title=REF_TITLE + ) + rel20_values_by_angle[ang] = (m_rel20["global_rms_nm"], m_rel20["filtered_rms_nm"]) + + metrics_summary.append({ + "Subcase": f"sub{ang}", + "Abs_GlobalRMS_nm": abs_global, + "Abs_FilteredRMS_nm": abs_filtered, + "Rel20_GlobalRMS_nm": m_rel20["global_rms_nm"], + "Rel20_FilteredRMS_nm": m_rel20["filtered_rms_nm"] + }) + else: + g20, f20 = compute_rms_pair_only(X_rel20, Y_rel20, W_rel20) + rel20_values_by_angle[ang] = (g20, f20) + + # REL vs 90 for all angles (metrics only) + for ang in all_angles: + df_abs = build_dataframe_for_subcase(ang, subdisp[ang]["node_ids"], subdisp[ang]["disp"], node_geo) + df_rel90 = compute_relative_dz(df90_abs, df_abs) + X_rel90 = df_rel90["X0"].to_numpy(); Y_rel90 = df_rel90["Y0"].to_numpy() + W_rel90 = df_rel90["Rel_WFE_nm"].to_numpy() + g90, f90 = compute_rms_pair_only(X_rel90, Y_rel90, W_rel90) + rel90_values_by_angle[ang] = (g90, f90) + + # Manufacturing metrics for 90 + mfg_metrics_90 = None + mfg_metrics_90_vs_20 = None + + if POLISH_LABEL in subdisp: + mfg_metrics_90 = analyze_manufacturing_metrics(X90, Y90, W90) + + df90_rel20 = compute_relative_dz(df_ref_abs, df90_abs) + X90_rel = df90_rel20["X0"].to_numpy() + Y90_rel = df90_rel20["Y0"].to_numpy() + W90_rel = df90_rel20["Rel_WFE_nm"].to_numpy() + + mfg_metrics_90_vs_20 = analyze_manufacturing_metrics(X90_rel, Y90_rel, W90_rel) + + # Special HTML for 90 (numeric only) + _m90 = zernike_report( + POLISH_LABEL, X90, Y90, W90, results_dir, base_ts, + is_relative=False, + mfg_data=mfg_metrics_90, + mfg_data_vs_20=mfg_metrics_90_vs_20 + ) + + # HTML for 20 deg relative to 90 deg + df_20_rel90 = compute_relative_dz(df90_abs, df_ref_abs) + X_20_rel90 = df_20_rel90["X0"].to_numpy() + Y_20_rel90 = df_20_rel90["Y0"].to_numpy() + W_20_rel90 = df_20_rel90["Rel_WFE_nm"].to_numpy() + + _m_ref = zernike_report( + REF_LABEL, X_20_rel90, Y_20_rel90, W_20_rel90, results_dir, base_ts, + is_relative=True, abs_pair=(abs_ref_global, abs_ref_filtered), ref_title=POLISH_TITLE + ) + + # Combined nodal CSV + comb_df = pd.DataFrame(combined_rows) + comb_path = os.path.join(results_dir, f"{base_ts}_all_subcases_nodal.csv") + comb_df.to_csv(comb_path, index=False) + + # Summary CSV (REL20 naming) + summary_path = os.path.join(results_dir, f"{base_ts}_RMS_summary.csv") + pd.DataFrame(metrics_summary, columns=[ + "Subcase", + "Abs_GlobalRMS_nm","Abs_FilteredRMS_nm", + "Rel20_GlobalRMS_nm","Rel20_FilteredRMS_nm" + ]).to_csv(summary_path, index=False) + + # EXP export + write_exp_file(results_dir, abs_values_by_angle, rel20_values_by_angle, rel90_values_by_angle, + mfg_90=mfg_metrics_90, mfg_90_vs_20=mfg_metrics_90_vs_20) + + # Persistent RMS log + log_path = os.path.join(results_dir, "RMS_log.csv") + row = { + "RunID": run_id, + "RunDateTimeLocal": run_dt.strftime("%Y-%m-%d %H:%M:%S"), + "OP2_Base": base, + "DispUnit": DISP_SRC_UNIT, + "ReferenceAngleDeg": REF_LABEL + } + for ang in [str(a) for a in REQUIRED_SUBCASES]: + gA, fA = abs_values_by_angle.get(ang, (np.nan, np.nan)) + gR20, fR20 = rel20_values_by_angle.get(ang, (np.nan, np.nan)) + gR90, fR90 = rel90_values_by_angle.get(ang, (np.nan, np.nan)) + row[f"Abs_GlobalRMS_nm_{ang}"] = gA + row[f"Abs_FilteredRMS_nm_{ang}"] = fA + row[f"Rel_GlobalRMS_nm_{ang}_vs20"] = gR20 + row[f"Rel_FilteredRMS_nm_{ang}_vs20"] = fR20 + row[f"Rel_GlobalRMS_nm_{ang}_vs90"] = gR90 + row[f"Rel_FilteredRMS_nm_{ang}_vs90"] = fR90 + + if mfg_metrics_90 and mfg_metrics_90_vs_20: + row["Mfg_90_OpticianWorkload_nm"] = mfg_metrics_90_vs_20['rms_filter_j1to3'] + row["Mfg_90_Astig_Abs_nm"] = mfg_metrics_90['astigmatism_rms'] + row["Mfg_90_Coma_Abs_nm"] = mfg_metrics_90['coma_rms'] + + pd.DataFrame([row]).to_csv( + log_path, + mode="a", + header=not os.path.exists(log_path), + index=False + ) + + # Console output (numeric only; ASCII-safe) + if mfg_metrics_90 and mfg_metrics_90_vs_20: + print("\n" + "="*60) + print("MANUFACTURING METRICS (90 deg horizontal polishing)") + print("="*60) + + print(f"\n90 deg Absolute (surface shape on test stand):") + print(f" Filtered RMS (J1-J4): {mfg_metrics_90['rms_filter_j1to4']:.1f} nm") + print(f" Astigmatism (J5+J6): {mfg_metrics_90['astigmatism_rms']:.1f} nm") + print(f" Coma (J7+J8): {mfg_metrics_90['coma_rms']:.1f} nm") + print(f" Trefoil (J9+J10): {mfg_metrics_90['trefoil_rms']:.1f} nm") + + print(f"\n90 deg - 20 deg (pre-correction to polish in):") + print(f" Total RMS (J1-J3): {mfg_metrics_90_vs_20['rms_filter_j1to3']:.1f} nm") + print(f" Defocus (J4): {mfg_metrics_90_vs_20['defocus_nm']:.1f} nm") + print(f" Astigmatism (J5+J6): {mfg_metrics_90_vs_20['astigmatism_rms']:.1f} nm") + print(f" Coma (J7+J8): {mfg_metrics_90_vs_20['coma_rms']:.1f} nm") + print("="*60 + "\n") + + print("[OK] Wrote:") + print(" -", comb_path) + print(" -", summary_path) + print(" -", log_path, "(appended)") + + for r in metrics_summary: + tag = r["Subcase"] + if tag == f"sub{REF_LABEL}": + print(f" - {base_ts}_{tag}.html (ABS Global={r['Abs_GlobalRMS_nm']:.2f} nm, " + f"ABS Filtered={r['Abs_FilteredRMS_nm']:.2f} nm)") + else: + print(f" - {base_ts}_{tag}.html (REL(vs20) Global={r['Rel20_GlobalRMS_nm']:.2f} nm, " + f"REL(vs20) Filtered={r['Rel20_FilteredRMS_nm']:.2f} nm | " + f"ABS Global={r['Abs_GlobalRMS_nm']:.2f} nm, " + f"ABS Filtered={r['Abs_FilteredRMS_nm']:.2f} nm)") + +if __name__ == "__main__": + try: + main() + except Exception as e: + msg = f"Fatal error: {e}" + print(msg) + if messagebox: + try: + messagebox.showerror("Fatal error", str(e)) + except Exception: + pass diff --git a/examples/optimization_config_zernike_mirror.json b/examples/optimization_config_zernike_mirror.json new file mode 100644 index 00000000..a2f4ad75 --- /dev/null +++ b/examples/optimization_config_zernike_mirror.json @@ -0,0 +1,124 @@ +{ + "$schema": "Atomizer Optimization Config - Telescope Mirror Zernike Optimization", + "_description": "Example configuration for optimizing telescope mirror support structures using Zernike wavefront error metrics", + + "study_name": "mirror_wfe_optimization", + + "design_variables": [ + { + "name": "support_ring_radius", + "expression_name": "support_radius", + "min": 150.0, + "max": 250.0, + "units": "mm", + "description": "Radial position of support ring" + }, + { + "name": "support_count", + "expression_name": "n_supports", + "min": 3, + "max": 12, + "type": "integer", + "units": "count", + "description": "Number of support points" + }, + { + "name": "support_stiffness", + "expression_name": "k_support", + "min": 1000.0, + "max": 50000.0, + "units": "N/mm", + "description": "Support spring stiffness" + } + ], + + "objectives": [ + { + "name": "filtered_rms_20deg", + "description": "Filtered RMS WFE at 20 deg elevation (operational)", + "direction": "minimize", + "extractor": "zernike", + "extractor_config": { + "subcase": "20", + "metric": "filtered_rms_nm", + "displacement_unit": "mm", + "n_modes": 50, + "filter_orders": 4 + } + }, + { + "name": "mass", + "description": "Total mirror assembly mass", + "direction": "minimize", + "extractor": "mass_from_expression", + "extractor_config": { + "expression_name": "total_mass" + } + } + ], + + "constraints": [ + { + "name": "max_stress", + "description": "Maximum von Mises stress in mirror", + "type": "upper_bound", + "threshold": 5.0, + "units": "MPa", + "extractor": "von_mises_stress", + "extractor_config": { + "subcase": "90" + } + }, + { + "name": "astigmatism_limit", + "description": "Astigmatism RMS at polishing orientation", + "type": "upper_bound", + "threshold": 50.0, + "units": "nm", + "extractor": "zernike", + "extractor_config": { + "subcase": "90", + "metric": "astigmatism_rms_nm" + } + } + ], + + "optimization_settings": { + "n_trials": 200, + "sampler": "NSGA-II", + "protocol": 11, + "n_startup_trials": 30, + "seed": 42 + }, + + "simulation_settings": { + "solver": "NX Nastran", + "solution_name": null, + "required_subcases": [20, 40, 60, 90], + "op2_pattern": "*-solution_1.op2" + }, + + "zernike_settings": { + "_description": "Global Zernike analysis settings", + "n_modes": 50, + "filter_low_orders": 4, + "displacement_unit": "mm", + "reference_subcase": "20", + "polishing_subcase": "90", + "operational_subcases": ["20", "40", "60"], + "metrics_to_log": [ + "global_rms_nm", + "filtered_rms_nm", + "astigmatism_rms_nm", + "coma_rms_nm", + "trefoil_rms_nm", + "spherical_nm" + ] + }, + + "output_settings": { + "save_zernike_coefficients": true, + "generate_html_reports": true, + "export_exp_file": true + } +} diff --git a/nx_journals/user_generated_journals/journal_afem_update_workflow_exemple.py b/nx_journals/user_generated_journals/journal_afem_update_workflow_exemple.py new file mode 100644 index 00000000..60d8727b --- /dev/null +++ b/nx_journals/user_generated_journals/journal_afem_update_workflow_exemple.py @@ -0,0 +1,5510 @@ +# NX 2506 +# Journal created by Antoine on Fri Nov 28 15:20:19 2025 Eastern Standard Time +# +import math +import NXOpen +import NXOpen.Assemblies +import NXOpen.CAE +def main(args) : + + theSession = NXOpen.Session.GetSession() #type: NXOpen.Session + workSimPart = theSession.Parts.BaseWork + displaySimPart = theSession.Parts.BaseDisplay + markId1 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Load Part") + + part1, partLoadStatus1 = theSession.Parts.Open("C:\\Users\\Antoine\\CADTOMASTE\\Atomizer\\M1-Gigabit\\Latest\\ASSY_M1.prt") + + partLoadStatus1.Dispose() + markId2 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Load Part") + + part2, partLoadStatus2 = theSession.Parts.Open("C:\\Users\\Antoine\\CADTOMASTE\\Atomizer\\M1-Gigabit\\Latest\\M1_Blank_fem1_i.prt") + + partLoadStatus2.Dispose() + markId3 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Change Displayed Part") + + part3 = theSession.Parts.FindObject("M1_Blank") + status1, partLoadStatus3 = theSession.Parts.SetActiveDisplay(part3, NXOpen.DisplayPartOption.AllowAdditional, NXOpen.PartDisplayPartWorkPartOption.UseLast) + + workSimPart = NXOpen.BasePart.Null + workPart = theSession.Parts.Work + displaySimPart = NXOpen.BasePart.Null + displayPart = theSession.Parts.Display + partLoadStatus3.Dispose() + theSession.ApplicationSwitchImmediate("UG_APP_MODELING") + + # ---------------------------------------------- + # Menu: Tools->Utilities->Expressions... + # ---------------------------------------------- + markId4 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start") + + theSession.SetUndoMarkName(markId4, "Expressions Dialog") + + markId5 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Import Expressions") + + expModified1, errorMessages1 = workPart.Expressions.ImportFromFile("C:\\Users\\Antoine\\Atomizer\\nx_journals\\user_generated_journals\\study_variables_expressions_from_journal.exp", NXOpen.ExpressionCollection.ImportMode.Replace) + + markId6 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Edit Expression") + + expression1 = workPart.Expressions.FindObject("whiffle_outer_to_vertical") + unit1 = workPart.UnitCollection.FindObject("MilliMeter") + workPart.Expressions.EditExpressionWithUnits(expression1, unit1, "75.00") + + markId7 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Expressions") + + theSession.DeleteUndoMark(markId7, None) + + markId8 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Expressions") + + markId9 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Make Up to Date") + + markId10 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Update Expression") + + objects1 = [NXOpen.NXObject.Null] * 1 + objects1[0] = expression1 + theSession.UpdateManager.MakeUpToDate(objects1, markId10) + + nErrs1 = theSession.UpdateManager.DoUpdate(markId10) + + objects2 = [NXOpen.NXObject.Null] * 1 + objects2[0] = expression1 + theSession.UpdateManager.MakeUpToDate(objects2, markId9) + + markId11 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "NX update") + + nErrs2 = theSession.UpdateManager.DoUpdate(markId11) + + theSession.DeleteUndoMark(markId11, "NX update") + + theSession.DeleteUndoMark(markId9, None) + + theSession.DeleteUndoMark(markId8, None) + + theSession.SetUndoMarkName(markId4, "Expressions") + + theSession.DeleteUndoMark(markId5, None) + + markId12 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Change Displayed Part") + + simPart1 = theSession.Parts.FindObject("ASSY_M1_assyfem1_sim1") + status2, partLoadStatus4 = theSession.Parts.SetActiveDisplay(simPart1, NXOpen.DisplayPartOption.AllowAdditional, NXOpen.PartDisplayPartWorkPartOption.UseLast) + + workPart = NXOpen.Part.Null + workSimPart = theSession.Parts.BaseWork # ASSY_M1_assyfem1_sim1 + displayPart = NXOpen.Part.Null + displaySimPart = theSession.Parts.BaseDisplay # ASSY_M1_assyfem1_sim1 + partLoadStatus4.Dispose() + theSession.ApplicationSwitchImmediate("UG_APP_SFEM") + + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + simPart2 = workSimPart + theSession.Post.UpdateUserGroupsFromSimPart(simPart2) + + markId13 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Make Work Part") + + component1 = workSimPart.ComponentAssembly.RootComponent.FindObject("COMPONENT ASSY_M1_assyfem1 1") + component2 = component1.FindObject("COMPONENT M1_Blank_fem1 1") + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + partLoadStatus5 = theSession.Parts.SetWorkComponent(component2, NXOpen.PartCollection.RefsetOption.Entire, NXOpen.PartCollection.WorkComponentOption.Visible) + + workFemPart = theSession.Parts.BaseWork + partLoadStatus5.Dispose() + # ---------------------------------------------- + # Menu: Edit->Update + # ---------------------------------------------- + markId14 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Update FE Model") + + fEModel1 = workFemPart.FindObject("FEModel") + fEModel1.UpdateFemodel() + + markId15 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Make Work Part") + + component3 = component1.FindObject("COMPONENT M1_Vertical_Support_Skeleton_fem1 3") + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + partLoadStatus6 = theSession.Parts.SetWorkComponent(component3, NXOpen.PartCollection.RefsetOption.Entire, NXOpen.PartCollection.WorkComponentOption.Visible) + + workFemPart = theSession.Parts.BaseWork # M1_Vertical_Support_Skeleton_fem1 + partLoadStatus6.Dispose() + markId16 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Load Part") + + part4, partLoadStatus7 = theSession.Parts.Open("C:\\Users\\Antoine\\CADTOMASTE\\Atomizer\\M1-Gigabit\\Latest\\M1_Vertical_Support_Skeleton_fem1_i.prt") + + partLoadStatus7.Dispose() + # ---------------------------------------------- + # Menu: Edit->Update + # ---------------------------------------------- + markId17 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Update FE Model") + + fEModel2 = workFemPart.FindObject("FEModel") + fEModel2.UpdateFemodel() + + markId18 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Make Work Part") + + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + partLoadStatus8 = theSession.Parts.SetWorkComponent(component1, NXOpen.PartCollection.RefsetOption.Entire, NXOpen.PartCollection.WorkComponentOption.Visible) + + workAssyFemPart = theSession.Parts.BaseWork + partLoadStatus8.Dispose() + # ---------------------------------------------- + # Menu: Analysis->Finite Element Model Check->Duplicate Nodes... + # ---------------------------------------------- + markId19 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start") + + caePart1 = workAssyFemPart + duplicateNodesCheckBuilder1 = caePart1.ModelCheckMgr.CreateDuplicateNodesCheckBuilder() + + unit2 = duplicateNodesCheckBuilder1.Tolerance.Units + + duplicateNodesCheckBuilder1.Tolerance.Units = unit2 + + duplicateNodesCheckBuilder1.Tolerance.SetFormula("0.01") + + duplicateNodesCheckBuilder1.MergeOccurrenceNodes = True + + theSession.SetUndoMarkName(markId19, "Duplicate Nodes Dialog") + + displaysettings1 = NXOpen.CAE.ModelCheck.DuplicateNodesCheckBuilder.DisplaySettings() + + displaysettings1.ShowDuplicateNodes = True + displaysettings1.ShowMergedNodeLabels = False + displaysettings1.ShowRetainedNodeLabels = False + displaysettings1.KeepNodesColor = displaySimPart.Colors.Find("Blue") + displaysettings1.MergeNodesColor = displaySimPart.Colors.Find("Yellow") + displaysettings1.UnableToMergeNodesColor = displaySimPart.Colors.Find("Red") + duplicateNodesCheckBuilder1.DisplaySettingsData = displaysettings1 + + selectTaggedObjectList1 = duplicateNodesCheckBuilder1.SelectionList + + duplicateNodesCheckBuilder1.CheckScopeOption = NXOpen.CAE.ModelCheck.CheckScope.Displayed + + duplicateNodesCheckBuilder1.IdentifyDuplicateNodes() + + scaleAboutPoint1 = NXOpen.Point3d(-933.20293900317381, 483.96876985524074, 0.0) + viewCenter1 = NXOpen.Point3d(933.20293900318075, -483.96876985525068, 0.0) + displaySimPart.ModelingViews.WorkView.ZoomAboutPoint(0.80000000000000004, scaleAboutPoint1, viewCenter1) + + scaleAboutPoint2 = NXOpen.Point3d(-1166.5036737539685, 604.96096231905221, 0.0) + viewCenter2 = NXOpen.Point3d(1166.5036737539749, -604.96096231906211, 0.0) + displaySimPart.ModelingViews.WorkView.ZoomAboutPoint(0.80000000000000004, scaleAboutPoint2, viewCenter2) + + duplicateNodesCheckBuilder1.MergeDuplicateNodes() + + theSession.SetUndoMarkName(markId19, "Duplicate Nodes") + + duplicateNodesCheckBuilder1.Destroy() + + theSession.DeleteUndoMark(markId19, None) + + markId20 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start") + + assyFemPart1 = workAssyFemPart + assemblyLabelManagerBuilder1 = assyFemPart1.CreateAssemblyLabelManagerBuilder() + + theSession.SetUndoMarkName(markId20, "Assembly Label Manager Dialog") + + # ---------------------------------------------- + # Dialog Begin Assembly Label Manager + # ---------------------------------------------- + markId21 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Assembly Label Manager") + + theSession.DeleteUndoMark(markId21, None) + + markId22 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Assembly Label Manager") + + fEModelOccurrence1 = workAssyFemPart.FindObject("FEModelOccurrence[3]") + entitytypes1 = [None] * 7 + entitytypes1[0] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Node + entitytypes1[1] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Element + entitytypes1[2] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Csys + entitytypes1[3] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Physical + entitytypes1[4] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Group + entitytypes1[5] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Ply + entitytypes1[6] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Ssmo + offsetentitytypelabeltonearest1 = [None] * 7 + offsetentitytypelabeltonearest1[0] = 2 + offsetentitytypelabeltonearest1[1] = 2 + offsetentitytypelabeltonearest1[2] = 2 + offsetentitytypelabeltonearest1[3] = 2 + offsetentitytypelabeltonearest1[4] = 2 + offsetentitytypelabeltonearest1[5] = 2 + offsetentitytypelabeltonearest1[6] = 2 + assemblyLabelManagerBuilder1.SetFEModelOccOffsets(fEModelOccurrence1, entitytypes1, offsetentitytypelabeltonearest1) + + fEModelOccurrence2 = workAssyFemPart.FindObject("FEModelOccurrence[4]") + entitytypes2 = [None] * 7 + entitytypes2[0] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Node + entitytypes2[1] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Element + entitytypes2[2] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Csys + entitytypes2[3] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Physical + entitytypes2[4] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Group + entitytypes2[5] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Ply + entitytypes2[6] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Ssmo + offsetentitytypelabeltonearest2 = [None] * 7 + offsetentitytypelabeltonearest2[0] = 74 + offsetentitytypelabeltonearest2[1] = 74 + offsetentitytypelabeltonearest2[2] = 74 + offsetentitytypelabeltonearest2[3] = 74 + offsetentitytypelabeltonearest2[4] = 74 + offsetentitytypelabeltonearest2[5] = 74 + offsetentitytypelabeltonearest2[6] = 74 + assemblyLabelManagerBuilder1.SetFEModelOccOffsets(fEModelOccurrence2, entitytypes2, offsetentitytypelabeltonearest2) + + fEModelOccurrence3 = workAssyFemPart.FindObject("FEModelOccurrence[5]") + entitytypes3 = [None] * 7 + entitytypes3[0] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Node + entitytypes3[1] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Element + entitytypes3[2] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Csys + entitytypes3[3] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Physical + entitytypes3[4] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Group + entitytypes3[5] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Ply + entitytypes3[6] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Ssmo + offsetentitytypelabeltonearest3 = [None] * 7 + offsetentitytypelabeltonearest3[0] = 146 + offsetentitytypelabeltonearest3[1] = 146 + offsetentitytypelabeltonearest3[2] = 146 + offsetentitytypelabeltonearest3[3] = 146 + offsetentitytypelabeltonearest3[4] = 146 + offsetentitytypelabeltonearest3[5] = 146 + offsetentitytypelabeltonearest3[6] = 146 + assemblyLabelManagerBuilder1.SetFEModelOccOffsets(fEModelOccurrence3, entitytypes3, offsetentitytypelabeltonearest3) + + fEModelOccurrence4 = workAssyFemPart.FindObject("FEModelOccurrence[7]") + entitytypes4 = [None] * 7 + entitytypes4[0] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Node + entitytypes4[1] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Element + entitytypes4[2] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Csys + entitytypes4[3] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Physical + entitytypes4[4] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Group + entitytypes4[5] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Ply + entitytypes4[6] = NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Ssmo + offsetentitytypelabeltonearest4 = [None] * 7 + offsetentitytypelabeltonearest4[0] = 218 + offsetentitytypelabeltonearest4[1] = 218 + offsetentitytypelabeltonearest4[2] = 218 + offsetentitytypelabeltonearest4[3] = 218 + offsetentitytypelabeltonearest4[4] = 218 + offsetentitytypelabeltonearest4[5] = 218 + offsetentitytypelabeltonearest4[6] = 218 + assemblyLabelManagerBuilder1.SetFEModelOccOffsets(fEModelOccurrence4, entitytypes4, offsetentitytypelabeltonearest4) + + nXObject1 = assemblyLabelManagerBuilder1.Commit() + + theSession.DeleteUndoMark(markId22, None) + + theSession.SetUndoMarkName(markId20, "Assembly Label Manager") + + assemblyLabelManagerBuilder1.Destroy() + + markId23 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Make Work Part") + + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + # No object found for the subject of the next call. + # This may be because of previous exceptions + # # .Dispose() + partLoadStatus9 = theSession.Parts.SetWorkComponent(NXOpen.Assemblies.Component.Null, NXOpen.PartCollection.RefsetOption.Entire, NXOpen.PartCollection.WorkComponentOption.Visible) + + workSimPart = theSession.Parts.BaseWork # ASSY_M1_assyfem1_sim1 + partLoadStatus9.Dispose() + # ---------------------------------------------- + # Menu: Analysis->Solve... + # ---------------------------------------------- + markId24 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start") + + theSession.SetUndoMarkName(markId24, "Solve Dialog") + + markId25 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Solve") + + theSession.DeleteUndoMark(markId25, None) + + markId26 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Solve") + + theCAESimSolveManager = NXOpen.CAE.SimSolveManager.GetSimSolveManager(theSession) + + psolutions1 = [NXOpen.CAE.SimSolution.Null] * 1 + simSimulation1 = workSimPart.FindObject("Simulation") + simSolution1 = simSimulation1.FindObject("Solution[Solution 1]") + psolutions1[0] = simSolution1 + numsolutionssolved1, numsolutionsfailed1, numsolutionsskipped1 = theCAESimSolveManager.SolveChainOfSolutions(psolutions1, NXOpen.CAE.SimSolution.SolveOption.Solve, NXOpen.CAE.SimSolution.SetupCheckOption.CompleteCheckAndOutputErrors, NXOpen.CAE.SimSolution.SolveMode.Background) + + theSession.DeleteUndoMark(markId26, None) + + theSession.SetUndoMarkName(markId24, "Solve") + + # ---------------------------------------------- + # Menu: Tools->Automation->Journal->Stop Recording + # ---------------------------------------------- + +if __name__ == '__main__': + main(sys.argv[1:]) \ No newline at end of file diff --git a/optimization_engine/extractors/__init__.py b/optimization_engine/extractors/__init__.py index 6ab63361..d38c7cae 100644 --- a/optimization_engine/extractors/__init__.py +++ b/optimization_engine/extractors/__init__.py @@ -1 +1,25 @@ -"""Core extractor library for Atomizer.""" +"""Core extractor library for Atomizer. + +Available extractors: +- Displacement: extract_displacement +- Stress: extract_solid_stress (von Mises) +- Frequency: extract_frequency +- Mass: extract_mass_from_expression, extract_mass_from_op2 +- Zernike: extract_zernike_from_op2, ZernikeExtractor (telescope mirrors) +""" + +# Zernike extractor for telescope mirror optimization +from optimization_engine.extractors.extract_zernike import ( + ZernikeExtractor, + extract_zernike_from_op2, + extract_zernike_filtered_rms, + extract_zernike_relative_rms, +) + +__all__ = [ + # Zernike (telescope mirrors) + 'ZernikeExtractor', + 'extract_zernike_from_op2', + 'extract_zernike_filtered_rms', + 'extract_zernike_relative_rms', +] diff --git a/optimization_engine/extractors/extract_zernike.py b/optimization_engine/extractors/extract_zernike.py new file mode 100644 index 00000000..834bdf47 --- /dev/null +++ b/optimization_engine/extractors/extract_zernike.py @@ -0,0 +1,860 @@ +""" +Zernike Coefficient Extractor for Telescope Mirror Optimization +================================================================ + +Extracts Zernike polynomial coefficients from OP2 displacement results +for optical surface quality analysis. Designed for telescope mirror +optimization where wavefront error (WFE) metrics are critical. + +Key Features: +- Noll-indexed Zernike polynomials (standard optical convention) +- Multi-subcase support (different gravity orientations: 20, 40, 60, 90 deg) +- Global and filtered RMS wavefront error +- Individual aberration magnitudes (astigmatism, coma, trefoil, spherical) +- Relative metrics between subcases (e.g., operational vs polishing orientation) + +Usage: + from optimization_engine.extractors.extract_zernike import ( + extract_zernike_from_op2, + ZernikeExtractor + ) + + # Simple usage - get filtered RMS for optimization objective + result = extract_zernike_from_op2(op2_file, subcase=20) + rms_filtered = result['filtered_rms_nm'] + + # Full extractor for detailed analysis + extractor = ZernikeExtractor(op2_file, bdf_file) + metrics = extractor.extract_all_subcases() + +Author: Atomizer Framework (adapted from telescope mirror analysis scripts) +""" + +from pathlib import Path +from typing import Dict, Any, Optional, List, Tuple, Union +import numpy as np +from math import factorial +from numpy.linalg import LinAlgError + +try: + from pyNastran.op2.op2 import OP2 + from pyNastran.bdf.bdf import BDF +except ImportError: + raise ImportError("pyNastran is required. Install with: pip install pyNastran") + + +# ============================================================================ +# Configuration +# ============================================================================ + +DEFAULT_N_MODES = 50 # Number of Zernike modes to fit +DEFAULT_FILTER_ORDERS = 4 # Filter first N modes (piston, tip, tilt, defocus) +DEFAULT_CHUNK_SIZE = 100000 # For memory-efficient processing of large meshes + +# Standard telescope orientations (gravity angles in degrees) +STANDARD_SUBCASES = [20, 40, 60, 90] + +# Displacement unit conversions (to nanometers for WFE) +UNIT_TO_NM = { + 'mm': 1e6, # 1 mm = 1e6 nm + 'm': 1e9, # 1 m = 1e9 nm + 'um': 1e3, # 1 um = 1e3 nm + 'nm': 1.0, # already nm +} + + +# ============================================================================ +# Zernike Polynomial Mathematics +# ============================================================================ + +def noll_indices(j: int) -> Tuple[int, int]: + """ + Convert Noll index j to radial order n and azimuthal frequency m. + + The Noll indexing scheme is the standard convention in optics. + j=1: Piston, j=2,3: Tip/Tilt, j=4: Defocus, j=5,6: Astigmatism, etc. + + Args: + j: Noll index (1-based) + + Returns: + (n, m): Radial order and azimuthal frequency + """ + if j < 1: + raise ValueError("Noll index j must be >= 1") + + count = 0 + n = 0 + while True: + if n == 0: + ms = [0] + elif n % 2 == 0: + ms = [0] + [m for k in range(1, n//2 + 1) for m in (-2*k, 2*k)] + else: + ms = [m for k in range(0, (n+1)//2) for m in (-(2*k+1), (2*k+1))] + for m in ms: + count += 1 + if count == j: + return n, m + n += 1 + + +def zernike_radial(n: int, m: int, r: np.ndarray) -> np.ndarray: + """ + Compute the radial component R_n^m(r) of the Zernike polynomial. + + Args: + n: Radial order + m: Azimuthal frequency (absolute value used) + r: Radial coordinates (normalized to unit disk) + + Returns: + Radial polynomial evaluated at r + """ + R = np.zeros_like(r) + m_abs = abs(m) + + for s in range((n - m_abs) // 2 + 1): + coef = ((-1)**s * factorial(n - s) / + (factorial(s) * + factorial((n + m_abs) // 2 - s) * + factorial((n - m_abs) // 2 - s))) + R += coef * r**(n - 2*s) + + return R + + +def zernike_noll(j: int, r: np.ndarray, theta: np.ndarray) -> np.ndarray: + """ + Evaluate Noll-indexed Zernike polynomial Z_j(r, theta). + + Args: + j: Noll index + r: Radial coordinates (normalized to unit disk) + theta: Angular coordinates (radians) + + Returns: + Zernike polynomial values at (r, theta) + """ + n, m = noll_indices(j) + R = zernike_radial(n, m, r) + + if m == 0: + return R + elif m > 0: + return R * np.cos(m * theta) + else: + return R * np.sin(-m * theta) + + +def zernike_name(j: int) -> str: + """ + Get common optical name for Zernike mode. + + Args: + j: Noll index + + Returns: + Human-readable name (e.g., "Defocus", "Astigmatism 0 deg") + """ + n, m = noll_indices(j) + + names = { + (0, 0): "Piston", + (1, -1): "Tilt X", + (1, 1): "Tilt Y", + (2, 0): "Defocus", + (2, -2): "Astigmatism 45 deg", + (2, 2): "Astigmatism 0 deg", + (3, -1): "Coma X", + (3, 1): "Coma Y", + (3, -3): "Trefoil X", + (3, 3): "Trefoil Y", + (4, 0): "Primary Spherical", + (4, -2): "Secondary Astig X", + (4, 2): "Secondary Astig Y", + (4, -4): "Quadrafoil X", + (4, 4): "Quadrafoil Y", + (5, -1): "Secondary Coma X", + (5, 1): "Secondary Coma Y", + (5, -3): "Secondary Trefoil X", + (5, 3): "Secondary Trefoil Y", + (5, -5): "Pentafoil X", + (5, 5): "Pentafoil Y", + (6, 0): "Secondary Spherical", + } + + return names.get((n, m), f"Z(n={n}, m={m})") + + +def zernike_label(j: int) -> str: + """Full label for Zernike mode: J{j} - Name (n=, m=)""" + n, m = noll_indices(j) + return f"J{j:02d} - {zernike_name(j)} (n={n}, m={m})" + + +# ============================================================================ +# Zernike Coefficient Fitting +# ============================================================================ + +def compute_zernike_coefficients( + x: np.ndarray, + y: np.ndarray, + values: np.ndarray, + n_modes: int = DEFAULT_N_MODES, + chunk_size: int = DEFAULT_CHUNK_SIZE +) -> Tuple[np.ndarray, float]: + """ + Fit Zernike coefficients to surface data using least-squares. + + Uses chunked processing for memory efficiency with large meshes. + Points outside the unit disk (after centering/normalization) are excluded. + + Args: + x, y: Node coordinates (will be centered and normalized) + values: Surface values at each node (e.g., WFE in nm) + n_modes: Number of Zernike modes to fit + chunk_size: Chunk size for memory-efficient processing + + Returns: + (coefficients, R_max): Zernike coefficients and normalization radius + """ + # Center coordinates + x_centered = x - np.mean(x) + y_centered = y - np.mean(y) + + # Normalize to unit disk + R_max = float(np.max(np.hypot(x_centered, y_centered))) + r = np.hypot(x_centered / R_max, y_centered / R_max).astype(np.float32) + theta = np.arctan2(y_centered, x_centered).astype(np.float32) + + # Mask: inside unit disk and valid values + mask = (r <= 1.0) & ~np.isnan(values) + if not np.any(mask): + raise RuntimeError("No valid points inside unit disk for Zernike fitting.") + + idx = np.nonzero(mask)[0] + m = int(n_modes) + + # Normal equations: (Z^T Z) c = Z^T v + # Build incrementally for memory efficiency + G = np.zeros((m, m), dtype=np.float64) # Z^T Z + h = np.zeros((m,), dtype=np.float64) # Z^T v + v = values.astype(np.float64) + + for start in range(0, len(idx), chunk_size): + chunk_idx = idx[start:start + chunk_size] + r_chunk = r[chunk_idx] + theta_chunk = theta[chunk_idx] + v_chunk = v[chunk_idx] + + # Build Zernike basis for this chunk + Z_chunk = np.column_stack([ + zernike_noll(j, r_chunk, theta_chunk).astype(np.float32) + for j in range(1, m + 1) + ]) + + # Accumulate normal equations + G += (Z_chunk.T @ Z_chunk).astype(np.float64) + h += (Z_chunk.T @ v_chunk).astype(np.float64) + + # Solve normal equations + try: + coeffs = np.linalg.solve(G, h) + except LinAlgError: + coeffs = np.linalg.lstsq(G, h, rcond=None)[0] + + return coeffs, R_max + + +# ============================================================================ +# RMS Calculations +# ============================================================================ + +def compute_rms_metrics( + x: np.ndarray, + y: np.ndarray, + wfe: np.ndarray, + n_modes: int = DEFAULT_N_MODES, + filter_orders: int = DEFAULT_FILTER_ORDERS +) -> Dict[str, float]: + """ + Compute global and filtered RMS wavefront error. + + Args: + x, y: Node coordinates + wfe: Wavefront error values (nm) + n_modes: Number of Zernike modes to fit + filter_orders: Number of low-order modes to filter (typically 4) + + Returns: + Dict with 'global_rms_nm' and 'filtered_rms_nm' + """ + coeffs, R_max = compute_zernike_coefficients(x, y, wfe, n_modes) + + # Reconstruct filtered WFE (remove low-order modes) + x_c = x - np.mean(x) + y_c = y - np.mean(y) + r = np.hypot(x_c / R_max, y_c / R_max) + theta = np.arctan2(y_c, x_c) + + # Build Zernike basis for low-order modes only + Z_low = np.column_stack([ + zernike_noll(j, r, theta) for j in range(1, filter_orders + 1) + ]) + + # Subtract low-order contribution + wfe_filtered = wfe - Z_low @ coeffs[:filter_orders] + + global_rms = float(np.sqrt(np.mean(wfe**2))) + filtered_rms = float(np.sqrt(np.mean(wfe_filtered**2))) + + return { + 'global_rms_nm': global_rms, + 'filtered_rms_nm': filtered_rms, + 'coefficients': coeffs, + 'R_max': R_max + } + + +def compute_aberration_magnitudes(coeffs: np.ndarray) -> Dict[str, float]: + """ + Compute RMS magnitudes of common optical aberrations. + + Args: + coeffs: Zernike coefficients (at least 11 modes) + + Returns: + Dict with aberration RMS values in nm + """ + if len(coeffs) < 11: + raise ValueError("Need at least 11 Zernike modes for aberration analysis") + + return { + 'defocus_nm': float(abs(coeffs[3])), # J4 + 'astigmatism_rms_nm': float(np.sqrt(coeffs[4]**2 + coeffs[5]**2)), # J5+J6 + 'coma_rms_nm': float(np.sqrt(coeffs[6]**2 + coeffs[7]**2)), # J7+J8 + 'trefoil_rms_nm': float(np.sqrt(coeffs[8]**2 + coeffs[9]**2)), # J9+J10 + 'spherical_nm': float(abs(coeffs[10])), # J11 + } + + +# ============================================================================ +# OP2/BDF Data Extraction +# ============================================================================ + +def read_node_geometry(bdf_path: Path) -> Dict[int, np.ndarray]: + """ + Read node coordinates from BDF/DAT file. + + Args: + bdf_path: Path to .bdf or .dat file + + Returns: + Dict mapping node ID to [x, y, z] coordinates + """ + bdf = BDF() + bdf.read_bdf(str(bdf_path)) + + return { + int(nid): node.get_position() + for nid, node in bdf.nodes.items() + } + + +def find_geometry_file(op2_path: Path) -> Path: + """ + Find matching BDF/DAT file for an OP2. + + Looks for same-basename first, then any .dat/.bdf in same folder. + + Args: + op2_path: Path to OP2 file + + Returns: + Path to geometry file + """ + folder = op2_path.parent + base = op2_path.stem + + # Try same basename + for ext in ['.dat', '.bdf']: + cand = folder / (base + ext) + if cand.exists(): + return cand + + # Try any geometry file + for name in folder.iterdir(): + if name.suffix.lower() in ['.dat', '.bdf']: + return name + + raise FileNotFoundError(f"No .dat or .bdf geometry file found for {op2_path}") + + +def extract_displacements_by_subcase( + op2_path: Path, + required_subcases: Optional[List[int]] = None +) -> Dict[str, Dict[str, np.ndarray]]: + """ + Extract displacement data from OP2 organized by subcase. + + Args: + op2_path: Path to OP2 file + required_subcases: List of required subcases (e.g., [20, 40, 60, 90]) + + Returns: + Dict keyed by subcase label: {'20': {'node_ids': array, 'disp': array}, ...} + """ + op2 = OP2() + op2.read_op2(str(op2_path)) + + if not op2.displacements: + raise RuntimeError("No displacement data found in OP2 file") + + result = {} + + for key, darr in op2.displacements.items(): + data = darr.data + dmat = data[0] if data.ndim == 3 else (data if data.ndim == 2 else None) + if dmat is None: + continue + + ngt = darr.node_gridtype.astype(int) + node_ids = ngt if ngt.ndim == 1 else ngt[:, 0] + + # Try to identify subcase from subtitle or isubcase + subtitle = getattr(darr, 'subtitle', None) + isubcase = getattr(darr, 'isubcase', None) + + # Extract numeric from subtitle + label = None + if isinstance(subtitle, str): + import re + m = re.search(r'-?\d+', subtitle) + if m: + label = m.group(0) + + if label is None and isinstance(isubcase, int): + label = str(isubcase) + + if label: + result[label] = { + 'node_ids': node_ids.astype(int), + 'disp': dmat.copy() + } + + # Validate required subcases if specified + if required_subcases: + missing = [str(s) for s in required_subcases if str(s) not in result] + if missing: + available = list(result.keys()) + raise RuntimeError( + f"Required subcases {missing} not found. Available: {available}" + ) + + return result + + +# ============================================================================ +# Main Extractor Class +# ============================================================================ + +class ZernikeExtractor: + """ + Complete Zernike analysis extractor for telescope mirror optimization. + + This class handles: + - Loading OP2 displacement results + - Matching with BDF geometry + - Computing Zernike coefficients and RMS metrics + - Multi-subcase analysis (different gravity orientations) + - Relative metrics between subcases + + Example usage in optimization: + extractor = ZernikeExtractor(op2_file, bdf_file) + + # For single-objective optimization (minimize filtered RMS at 20 deg) + result = extractor.extract_subcase('20') + objective = result['filtered_rms_nm'] + + # For multi-subcase optimization + all_results = extractor.extract_all_subcases() + """ + + def __init__( + self, + op2_path: Union[str, Path], + bdf_path: Optional[Union[str, Path]] = None, + displacement_unit: str = 'mm', + n_modes: int = DEFAULT_N_MODES, + filter_orders: int = DEFAULT_FILTER_ORDERS + ): + """ + Initialize Zernike extractor. + + Args: + op2_path: Path to OP2 results file + bdf_path: Path to BDF/DAT geometry file (auto-detected if None) + displacement_unit: Unit of displacement in OP2 ('mm', 'm', 'um', 'nm') + n_modes: Number of Zernike modes to fit + filter_orders: Number of low-order modes to filter + """ + self.op2_path = Path(op2_path) + self.bdf_path = Path(bdf_path) if bdf_path else find_geometry_file(self.op2_path) + self.displacement_unit = displacement_unit + self.n_modes = n_modes + self.filter_orders = filter_orders + + # Unit conversion factor (displacement to nm) + self.nm_scale = UNIT_TO_NM.get(displacement_unit.lower(), 1e6) + + # WFE = 2 * surface displacement (optical convention) + self.wfe_factor = 2.0 * self.nm_scale + + # Lazy-loaded data + self._node_geo = None + self._displacements = None + + @property + def node_geometry(self) -> Dict[int, np.ndarray]: + """Lazy-load node geometry from BDF.""" + if self._node_geo is None: + self._node_geo = read_node_geometry(self.bdf_path) + return self._node_geo + + @property + def displacements(self) -> Dict[str, Dict[str, np.ndarray]]: + """Lazy-load displacements from OP2.""" + if self._displacements is None: + self._displacements = extract_displacements_by_subcase(self.op2_path) + return self._displacements + + def _build_dataframe( + self, + subcase_label: str + ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + Build coordinate and WFE arrays for a subcase. + + Returns: + (X, Y, WFE_nm): Arrays of coordinates and wavefront error + """ + if subcase_label not in self.displacements: + available = list(self.displacements.keys()) + raise ValueError(f"Subcase '{subcase_label}' not found. Available: {available}") + + data = self.displacements[subcase_label] + node_ids = data['node_ids'] + disp = data['disp'] + + # Build arrays + X, Y, WFE = [], [], [] + for nid, vec in zip(node_ids, disp): + geo = self.node_geometry.get(int(nid)) + if geo is None: + continue + + X.append(geo[0]) + Y.append(geo[1]) + # Z-displacement to WFE (nm) + wfe = vec[2] * self.wfe_factor + WFE.append(wfe) + + return np.array(X), np.array(Y), np.array(WFE) + + def extract_subcase( + self, + subcase_label: str, + include_coefficients: bool = False + ) -> Dict[str, Any]: + """ + Extract Zernike metrics for a single subcase. + + Args: + subcase_label: Subcase identifier (e.g., '20', '90') + include_coefficients: Whether to include all Zernike coefficients + + Returns: + Dict with RMS metrics, aberrations, and optionally coefficients + """ + X, Y, WFE = self._build_dataframe(subcase_label) + + # Compute RMS metrics + rms_result = compute_rms_metrics( + X, Y, WFE, self.n_modes, self.filter_orders + ) + + # Compute aberration magnitudes + aberrations = compute_aberration_magnitudes(rms_result['coefficients']) + + result = { + 'subcase': subcase_label, + 'global_rms_nm': rms_result['global_rms_nm'], + 'filtered_rms_nm': rms_result['filtered_rms_nm'], + 'n_nodes': len(X), + **aberrations + } + + if include_coefficients: + result['coefficients'] = rms_result['coefficients'].tolist() + result['coefficient_labels'] = [zernike_label(j) for j in range(1, self.n_modes + 1)] + + return result + + def extract_relative( + self, + target_subcase: str, + reference_subcase: str + ) -> Dict[str, Any]: + """ + Extract Zernike metrics relative to a reference subcase. + + Computes: WFE_relative = WFE_target - WFE_reference + + Args: + target_subcase: Subcase to analyze + reference_subcase: Reference subcase to subtract + + Returns: + Dict with relative RMS metrics and aberrations + """ + X_t, Y_t, WFE_t = self._build_dataframe(target_subcase) + X_r, Y_r, WFE_r = self._build_dataframe(reference_subcase) + + # Build node-to-index mapping for reference + target_data = self.displacements[target_subcase] + ref_data = self.displacements[reference_subcase] + + ref_node_to_idx = { + int(nid): i for i, nid in enumerate(ref_data['node_ids']) + } + + # Compute relative WFE for common nodes + X_rel, Y_rel, WFE_rel = [], [], [] + + for i, nid in enumerate(target_data['node_ids']): + nid = int(nid) + if nid not in ref_node_to_idx: + continue + + ref_idx = ref_node_to_idx[nid] + geo = self.node_geometry.get(nid) + if geo is None: + continue + + X_rel.append(geo[0]) + Y_rel.append(geo[1]) + + target_wfe = target_data['disp'][i, 2] * self.wfe_factor + ref_wfe = ref_data['disp'][ref_idx, 2] * self.wfe_factor + WFE_rel.append(target_wfe - ref_wfe) + + X_rel = np.array(X_rel) + Y_rel = np.array(Y_rel) + WFE_rel = np.array(WFE_rel) + + # Compute metrics on relative WFE + rms_result = compute_rms_metrics( + X_rel, Y_rel, WFE_rel, self.n_modes, self.filter_orders + ) + aberrations = compute_aberration_magnitudes(rms_result['coefficients']) + + return { + 'target_subcase': target_subcase, + 'reference_subcase': reference_subcase, + 'relative_global_rms_nm': rms_result['global_rms_nm'], + 'relative_filtered_rms_nm': rms_result['filtered_rms_nm'], + 'n_common_nodes': len(X_rel), + **{f'relative_{k}': v for k, v in aberrations.items()} + } + + def extract_all_subcases( + self, + reference_subcase: Optional[str] = '20' + ) -> Dict[str, Dict[str, Any]]: + """ + Extract metrics for all available subcases. + + Args: + reference_subcase: Reference for relative calculations (None to skip) + + Returns: + Dict mapping subcase label to metrics dict + """ + results = {} + + for label in self.displacements.keys(): + results[label] = self.extract_subcase(label) + + # Add relative metrics if reference specified + if reference_subcase and label != reference_subcase: + try: + rel = self.extract_relative(label, reference_subcase) + results[label].update({ + f'rel_{reference_subcase}_{k}': v + for k, v in rel.items() + if k.startswith('relative_') + }) + except Exception as e: + results[label][f'rel_{reference_subcase}_error'] = str(e) + + return results + + +# ============================================================================ +# Convenience Functions for Optimization +# ============================================================================ + +def extract_zernike_from_op2( + op2_file: Union[str, Path], + bdf_file: Optional[Union[str, Path]] = None, + subcase: Union[int, str] = 1, + displacement_unit: str = 'mm', + n_modes: int = DEFAULT_N_MODES, + filter_orders: int = DEFAULT_FILTER_ORDERS +) -> Dict[str, Any]: + """ + Convenience function to extract Zernike metrics from OP2. + + This is the main entry point for optimization objectives. + + Args: + op2_file: Path to OP2 results file + bdf_file: Path to BDF geometry (auto-detected if None) + subcase: Subcase identifier + displacement_unit: Unit of displacement in OP2 + n_modes: Number of Zernike modes + filter_orders: Low-order modes to filter + + Returns: + Dict with: + - 'global_rms_nm': Global RMS WFE in nanometers + - 'filtered_rms_nm': Filtered RMS (low orders removed) + - 'defocus_nm', 'astigmatism_rms_nm', etc.: Individual aberrations + """ + extractor = ZernikeExtractor( + op2_file, bdf_file, displacement_unit, n_modes, filter_orders + ) + return extractor.extract_subcase(str(subcase)) + + +def extract_zernike_filtered_rms( + op2_file: Union[str, Path], + bdf_file: Optional[Union[str, Path]] = None, + subcase: Union[int, str] = 1, + **kwargs +) -> float: + """ + Extract filtered RMS WFE - the primary metric for mirror optimization. + + Filtered RMS removes piston, tip, tilt, and defocus (modes 1-4), + which can be corrected by alignment and focus adjustment. + + Args: + op2_file: Path to OP2 file + bdf_file: Path to BDF geometry (auto-detected if None) + subcase: Subcase identifier + **kwargs: Additional arguments for ZernikeExtractor + + Returns: + Filtered RMS WFE in nanometers + """ + result = extract_zernike_from_op2(op2_file, bdf_file, subcase, **kwargs) + return result['filtered_rms_nm'] + + +def extract_zernike_relative_rms( + op2_file: Union[str, Path], + target_subcase: Union[int, str], + reference_subcase: Union[int, str], + bdf_file: Optional[Union[str, Path]] = None, + **kwargs +) -> float: + """ + Extract relative filtered RMS between two subcases. + + Useful for analyzing gravity-induced deformation relative to + a reference orientation (e.g., polishing position). + + Args: + op2_file: Path to OP2 file + target_subcase: Subcase to analyze + reference_subcase: Reference subcase + bdf_file: Path to BDF geometry + **kwargs: Additional arguments for ZernikeExtractor + + Returns: + Relative filtered RMS WFE in nanometers + """ + extractor = ZernikeExtractor(op2_file, bdf_file, **kwargs) + result = extractor.extract_relative(str(target_subcase), str(reference_subcase)) + return result['relative_filtered_rms_nm'] + + +# ============================================================================ +# Module Exports +# ============================================================================ + +__all__ = [ + # Main extractor class + 'ZernikeExtractor', + + # Convenience functions for optimization + 'extract_zernike_from_op2', + 'extract_zernike_filtered_rms', + 'extract_zernike_relative_rms', + + # Zernike utilities (for advanced use) + 'compute_zernike_coefficients', + 'compute_rms_metrics', + 'compute_aberration_magnitudes', + 'noll_indices', + 'zernike_noll', + 'zernike_name', + 'zernike_label', +] + + +if __name__ == '__main__': + # Example/test usage + import sys + + if len(sys.argv) > 1: + op2_file = Path(sys.argv[1]) + + print(f"Analyzing: {op2_file}") + + try: + extractor = ZernikeExtractor(op2_file) + + print(f"\nAvailable subcases: {list(extractor.displacements.keys())}") + + results = extractor.extract_all_subcases() + + for label, metrics in results.items(): + print(f"\n=== Subcase {label} ===") + print(f" Global RMS: {metrics['global_rms_nm']:.2f} nm") + print(f" Filtered RMS: {metrics['filtered_rms_nm']:.2f} nm") + print(f" Astigmatism: {metrics['astigmatism_rms_nm']:.2f} nm") + print(f" Coma: {metrics['coma_rms_nm']:.2f} nm") + print(f" Trefoil: {metrics['trefoil_rms_nm']:.2f} nm") + print(f" Spherical: {metrics['spherical_nm']:.2f} nm") + + except Exception as e: + print(f"Error: {e}") + sys.exit(1) + else: + print("Usage: python extract_zernike.py ") + print("\nThis module provides Zernike coefficient extraction for telescope mirror optimization.") + print("\nExample in optimization config:") + print(' "objectives": [') + print(' {') + print(' "name": "filtered_rms",') + print(' "extractor": "zernike",') + print(' "direction": "minimize",') + print(' "extractor_config": {') + print(' "subcase": "20",') + print(' "metric": "filtered_rms_nm"') + print(' }') + print(' }') + print(' ]') diff --git a/optimization_engine/extractors/zernike_helpers.py b/optimization_engine/extractors/zernike_helpers.py new file mode 100644 index 00000000..3995176c --- /dev/null +++ b/optimization_engine/extractors/zernike_helpers.py @@ -0,0 +1,403 @@ +""" +Zernike Helper Functions for Atomizer Optimization +=================================================== + +Convenience wrappers and utilities for using Zernike analysis +in optimization studies. These helpers simplify integration with +the standard Atomizer optimization patterns. + +Usage in run_optimization.py: + from optimization_engine.extractors.zernike_helpers import ( + create_zernike_objective, + ZernikeObjectiveBuilder + ) + + # Simple: create objective function + zernike_obj = create_zernike_objective( + op2_finder=lambda: sim_dir / "model-solution_1.op2", + subcase="20", + metric="filtered_rms_nm" + ) + + # Use in Optuna trial + rms = zernike_obj() +""" + +from pathlib import Path +from typing import Callable, Dict, Any, Optional, Union, List +import logging + +from optimization_engine.extractors.extract_zernike import ( + ZernikeExtractor, + extract_zernike_from_op2, + extract_zernike_filtered_rms, +) + +logger = logging.getLogger(__name__) + + +def create_zernike_objective( + op2_finder: Callable[[], Path], + bdf_finder: Optional[Callable[[], Path]] = None, + subcase: Union[int, str] = "20", + metric: str = "filtered_rms_nm", + displacement_unit: str = "mm", + **kwargs +) -> Callable[[], float]: + """ + Create a Zernike objective function for optimization. + + This factory creates a callable that: + 1. Finds the OP2 file (using op2_finder) + 2. Extracts Zernike metrics + 3. Returns the specified metric value + + Args: + op2_finder: Callable that returns path to current OP2 file + bdf_finder: Callable that returns path to BDF file (auto-detect if None) + subcase: Subcase to analyze (e.g., "20" for 20 deg elevation) + metric: Metric to return (see available_metrics below) + displacement_unit: Unit of displacement in OP2 file + **kwargs: Additional arguments for ZernikeExtractor + + Returns: + Callable that returns the metric value + + Available metrics: + - global_rms_nm: Global RMS wavefront error + - filtered_rms_nm: Filtered RMS (low orders removed) + - defocus_nm: Defocus aberration + - astigmatism_rms_nm: Combined astigmatism + - coma_rms_nm: Combined coma + - trefoil_rms_nm: Combined trefoil + - spherical_nm: Primary spherical aberration + + Example: + op2_finder = lambda: Path("sim_dir") / "model-solution_1.op2" + objective = create_zernike_objective(op2_finder, subcase="20") + + # In optimization loop + rms_value = objective() # Returns filtered RMS in nm + """ + def evaluate() -> float: + op2_path = op2_finder() + bdf_path = bdf_finder() if bdf_finder else None + + result = extract_zernike_from_op2( + op2_path, + bdf_path, + subcase=subcase, + displacement_unit=displacement_unit, + **kwargs + ) + + if metric not in result: + available = [k for k in result.keys() if isinstance(result[k], (int, float))] + raise ValueError(f"Metric '{metric}' not found. Available: {available}") + + return result[metric] + + return evaluate + + +def create_relative_zernike_objective( + op2_finder: Callable[[], Path], + target_subcase: Union[int, str], + reference_subcase: Union[int, str], + bdf_finder: Optional[Callable[[], Path]] = None, + metric: str = "relative_filtered_rms_nm", + **kwargs +) -> Callable[[], float]: + """ + Create objective for relative Zernike metrics between subcases. + + Useful for minimizing gravity-induced deformation relative to + a reference orientation (e.g., polishing position at 90 deg). + + Args: + op2_finder: Callable returning OP2 path + target_subcase: Subcase to analyze + reference_subcase: Reference subcase to subtract + bdf_finder: Optional BDF path finder + metric: Relative metric to return + **kwargs: Additional ZernikeExtractor arguments + + Returns: + Callable that returns relative metric value + """ + def evaluate() -> float: + op2_path = op2_finder() + bdf_path = bdf_finder() if bdf_finder else None + + extractor = ZernikeExtractor(op2_path, bdf_path, **kwargs) + result = extractor.extract_relative( + str(target_subcase), + str(reference_subcase) + ) + + if metric not in result: + available = [k for k in result.keys() if isinstance(result[k], (int, float))] + raise ValueError(f"Metric '{metric}' not found. Available: {available}") + + return result[metric] + + return evaluate + + +class ZernikeObjectiveBuilder: + """ + Builder for complex Zernike objectives with multiple subcases. + + This is useful for multi-subcase optimization where you want + to combine metrics from different gravity orientations. + + Example: + builder = ZernikeObjectiveBuilder( + op2_finder=lambda: sim_dir / "model.op2" + ) + + # Add objectives for different subcases + builder.add_subcase_objective("20", "filtered_rms_nm", weight=1.0) + builder.add_subcase_objective("40", "filtered_rms_nm", weight=0.5) + builder.add_subcase_objective("60", "filtered_rms_nm", weight=0.5) + + # Create combined objective + objective = builder.build_weighted_sum() + combined_rms = objective() # Returns weighted sum + """ + + def __init__( + self, + op2_finder: Callable[[], Path], + bdf_finder: Optional[Callable[[], Path]] = None, + displacement_unit: str = "mm", + **kwargs + ): + self.op2_finder = op2_finder + self.bdf_finder = bdf_finder + self.displacement_unit = displacement_unit + self.kwargs = kwargs + self.objectives: List[Dict[str, Any]] = [] + self._extractor = None + + def add_subcase_objective( + self, + subcase: Union[int, str], + metric: str = "filtered_rms_nm", + weight: float = 1.0, + name: Optional[str] = None + ) -> "ZernikeObjectiveBuilder": + """Add a subcase objective to the builder.""" + self.objectives.append({ + "subcase": str(subcase), + "metric": metric, + "weight": weight, + "name": name or f"{metric}_{subcase}" + }) + return self + + def add_relative_objective( + self, + target_subcase: Union[int, str], + reference_subcase: Union[int, str], + metric: str = "relative_filtered_rms_nm", + weight: float = 1.0, + name: Optional[str] = None + ) -> "ZernikeObjectiveBuilder": + """Add a relative objective between subcases.""" + self.objectives.append({ + "target_subcase": str(target_subcase), + "reference_subcase": str(reference_subcase), + "metric": metric, + "weight": weight, + "name": name or f"rel_{target_subcase}_vs_{reference_subcase}", + "is_relative": True + }) + return self + + def _get_extractor(self) -> ZernikeExtractor: + """Lazy-create extractor (reused for all objectives).""" + if self._extractor is None: + op2_path = self.op2_finder() + bdf_path = self.bdf_finder() if self.bdf_finder else None + self._extractor = ZernikeExtractor( + op2_path, bdf_path, + displacement_unit=self.displacement_unit, + **self.kwargs + ) + return self._extractor + + def _reset_extractor(self): + """Reset extractor (call after OP2 changes).""" + self._extractor = None + + def evaluate_all(self) -> Dict[str, float]: + """ + Evaluate all objectives and return dict of values. + + Returns: + Dict mapping objective name to value + """ + self._reset_extractor() + extractor = self._get_extractor() + results = {} + + for obj in self.objectives: + try: + if obj.get("is_relative"): + rel_result = extractor.extract_relative( + obj["target_subcase"], + obj["reference_subcase"] + ) + results[obj["name"]] = rel_result.get(obj["metric"], 0.0) + else: + sub_result = extractor.extract_subcase(obj["subcase"]) + results[obj["name"]] = sub_result.get(obj["metric"], 0.0) + except Exception as e: + logger.warning(f"Failed to evaluate {obj['name']}: {e}") + results[obj["name"]] = float("inf") + + return results + + def build_weighted_sum(self) -> Callable[[], float]: + """ + Build a callable that returns weighted sum of all objectives. + + Returns: + Callable returning combined objective value + """ + def evaluate() -> float: + values = self.evaluate_all() + total = 0.0 + for obj in self.objectives: + val = values.get(obj["name"], 0.0) + total += obj["weight"] * val + return total + + return evaluate + + def build_max(self) -> Callable[[], float]: + """ + Build a callable that returns maximum of all objectives. + + Useful for worst-case optimization across subcases. + """ + def evaluate() -> float: + values = self.evaluate_all() + weighted = [ + obj["weight"] * values.get(obj["name"], 0.0) + for obj in self.objectives + ] + return max(weighted) if weighted else 0.0 + + return evaluate + + def build_individual(self) -> Callable[[], Dict[str, float]]: + """ + Build a callable that returns dict of individual objective values. + + Useful for multi-objective optimization (NSGA-II). + """ + return self.evaluate_all + + +def extract_zernike_for_trial( + op2_path: Path, + bdf_path: Optional[Path] = None, + subcases: Optional[List[str]] = None, + reference_subcase: str = "20", + metrics: Optional[List[str]] = None, + **kwargs +) -> Dict[str, Any]: + """ + Extract comprehensive Zernike data for a trial. + + This is a high-level function for logging/exporting trial data. + It extracts all metrics for specified subcases and computes + relative metrics vs the reference. + + Args: + op2_path: Path to OP2 file + bdf_path: Path to BDF file (auto-detect if None) + subcases: List of subcases to extract (None = all available) + reference_subcase: Reference for relative calculations + metrics: Specific metrics to extract (None = all) + **kwargs: Additional ZernikeExtractor arguments + + Returns: + Dict with complete trial Zernike data: + { + 'subcases': { + '20': {'global_rms_nm': ..., 'filtered_rms_nm': ..., ...}, + '40': {...}, + ... + }, + 'relative': { + '40_vs_20': {'relative_filtered_rms_nm': ..., ...}, + ... + }, + 'summary': { + 'best_filtered_rms': ..., + 'worst_filtered_rms': ..., + ... + } + } + """ + extractor = ZernikeExtractor(op2_path, bdf_path, **kwargs) + + # Get available subcases + available = list(extractor.displacements.keys()) + if subcases: + subcases = [s for s in subcases if str(s) in available] + else: + subcases = available + + # Extract per-subcase data + subcase_data = {} + for sc in subcases: + try: + subcase_data[sc] = extractor.extract_subcase(str(sc)) + except Exception as e: + logger.warning(f"Failed to extract subcase {sc}: {e}") + + # Extract relative data + relative_data = {} + if reference_subcase in subcases: + for sc in subcases: + if sc != reference_subcase: + try: + key = f"{sc}_vs_{reference_subcase}" + relative_data[key] = extractor.extract_relative( + str(sc), str(reference_subcase) + ) + except Exception as e: + logger.warning(f"Failed to extract relative {key}: {e}") + + # Summary statistics + filtered_rms_values = [ + d.get('filtered_rms_nm', float('inf')) + for d in subcase_data.values() + ] + + summary = { + 'best_filtered_rms': min(filtered_rms_values) if filtered_rms_values else None, + 'worst_filtered_rms': max(filtered_rms_values) if filtered_rms_values else None, + 'mean_filtered_rms': sum(filtered_rms_values) / len(filtered_rms_values) if filtered_rms_values else None, + 'n_subcases': len(subcases), + 'reference_subcase': reference_subcase, + } + + return { + 'subcases': subcase_data, + 'relative': relative_data, + 'summary': summary, + } + + +# Export all helpers +__all__ = [ + 'create_zernike_objective', + 'create_relative_zernike_objective', + 'ZernikeObjectiveBuilder', + 'extract_zernike_for_trial', +] diff --git a/optimization_engine/nx_solver.py b/optimization_engine/nx_solver.py index b1cf1dd7..952ba0be 100644 --- a/optimization_engine/nx_solver.py +++ b/optimization_engine/nx_solver.py @@ -285,14 +285,11 @@ sys.argv = ['', {argv_str}] # Set argv for the main function # Set up environment for Simcenter/NX env = os.environ.copy() - # Set license server (use 29000 for Simcenter) - # Override any incorrect license server settings - env['SPLM_LICENSE_SERVER'] = '29000@AntoineThinkpad' - - # Force desktop licensing instead of enterprise - # User has nx_nas_bn_basic_dsk (desktop) not nx_nas_basic_ent (enterprise) - env['NXNA_LICENSE_FILE'] = '29000@AntoineThinkpad' - env['NXNASTRAN_LICENSE_FILE'] = '29000@AntoineThinkpad' + # Use existing SPLM_LICENSE_SERVER from environment if set + # Only set if not already defined (respects user's license configuration) + if 'SPLM_LICENSE_SERVER' not in env or not env['SPLM_LICENSE_SERVER']: + env['SPLM_LICENSE_SERVER'] = '29000@localhost' + print(f"[NX SOLVER] WARNING: SPLM_LICENSE_SERVER not set, using default: {env['SPLM_LICENSE_SERVER']}") # Add NX/Simcenter paths to environment nx_bin = self.nx_install_dir / "NXBIN" diff --git a/optimization_engine/solve_simulation.py b/optimization_engine/solve_simulation.py index a78e9955..a71451fc 100644 --- a/optimization_engine/solve_simulation.py +++ b/optimization_engine/solve_simulation.py @@ -1,13 +1,53 @@ """ NX Journal Script to Solve Simulation in Batch Mode -This script opens a .sim file, updates the FEM, and solves it through the NX API. -Usage: run_journal.exe solve_simulation.py +This script handles BOTH single-part simulations AND multi-part assembly FEMs. -Based on recorded NX journal pattern for solving simulations. +============================================================================= +MULTI-PART ASSEMBLY FEM WORKFLOW (for .afm-based simulations) +============================================================================= + +Based on recorded NX journal from interactive session (Nov 28, 2025). + +The correct workflow for assembly FEM updates: + +1. LOAD PARTS + - Open ASSY_M1.prt and M1_Blank_fem1_i.prt to have geometry loaded + - Find and switch to M1_Blank part for expression editing + +2. UPDATE EXPRESSIONS + - Switch to modeling application + - Edit expressions with units + - Call MakeUpToDate() on modified expressions + - Call DoUpdate() to rebuild geometry + +3. SWITCH TO SIM AND UPDATE FEM COMPONENTS + - Open the .sim file + - Navigate component hierarchy via RootComponent.FindObject() + - For each component FEM: + - SetWorkComponent() to make it the work part + - FindObject("FEModel").UpdateFemodel() + +4. MERGE DUPLICATE NODES (critical for assembly FEM!) + - Switch to assembly FEM component + - CreateDuplicateNodesCheckBuilder() + - Set MergeOccurrenceNodes = True + - IdentifyDuplicateNodes() then MergeDuplicateNodes() + +5. RESOLVE LABEL CONFLICTS + - CreateAssemblyLabelManagerBuilder() + - SetFEModelOccOffsets() for each occurrence + - Commit() + +6. SOLVE + - SetWorkComponent(Null) to return to sim level + - SolveChainOfSolutions() + +============================================================================= """ import sys +import os import NXOpen import NXOpen.Assemblies import NXOpen.CAE @@ -15,341 +55,510 @@ import NXOpen.CAE def main(args): """ - Open and solve a simulation file with updated expression values. + Main entry point for NX journal. Args: args: Command line arguments args[0]: .sim file path - args[1]: solution_name (optional, e.g., "Solution_Normal_Modes" or None for default) + args[1]: solution_name (optional, or "None" for default) args[2+]: expression updates as "name=value" pairs """ if len(args) < 1: print("ERROR: No .sim file path provided") - print("Usage: run_journal.exe solve_simulation.py [solution_name] [expr1=val1] [expr2=val2] ...") + print("Usage: run_journal.exe solve_simulation.py [solution_name] [expr1=val1] ...") return False sim_file_path = args[0] - - # Parse solution name if provided (args[1]) solution_name = args[1] if len(args) > 1 and args[1] != 'None' else None - # Extract base name from sim file (e.g., "Beam_sim1.sim" -> "Beam") - import os - sim_filename = os.path.basename(sim_file_path) - part_base_name = sim_filename.split('_sim')[0] if '_sim' in sim_filename else sim_filename.split('.sim')[0] - - # Parse expression updates from args[2+] as "name=value" pairs + # Parse expression updates expression_updates = {} for arg in args[2:]: if '=' in arg: name, value = arg.split('=', 1) expression_updates[name] = float(value) - print(f"[JOURNAL] Opening simulation: {sim_file_path}") - print(f"[JOURNAL] Detected part base name: {part_base_name}") - if solution_name: - print(f"[JOURNAL] Will solve specific solution: {solution_name}") - else: - print(f"[JOURNAL] Will solve default solution (Solution 1)") - if expression_updates: - print(f"[JOURNAL] Will update expressions:") - for name, value in expression_updates.items(): - print(f"[JOURNAL] {name} = {value}") + # Get working directory + working_dir = os.path.dirname(os.path.abspath(sim_file_path)) + sim_filename = os.path.basename(sim_file_path) + + print(f"[JOURNAL] " + "="*60) + print(f"[JOURNAL] NX SIMULATION SOLVER (Assembly FEM Workflow)") + print(f"[JOURNAL] " + "="*60) + print(f"[JOURNAL] Simulation: {sim_filename}") + print(f"[JOURNAL] Working directory: {working_dir}") + print(f"[JOURNAL] Solution: {solution_name or 'Solution 1'}") + print(f"[JOURNAL] Expression updates: {len(expression_updates)}") + for name, value in expression_updates.items(): + print(f"[JOURNAL] {name} = {value}") try: theSession = NXOpen.Session.GetSession() - # Set load options to load linked parts from directory - print("[JOURNAL] Setting load options for linked parts...") - import os - working_dir = os.path.dirname(os.path.abspath(sim_file_path)) - - # Complete load options setup (from recorded journal) + # Set load options theSession.Parts.LoadOptions.LoadLatest = False theSession.Parts.LoadOptions.ComponentLoadMethod = NXOpen.LoadOptions.LoadMethod.FromDirectory - - searchDirectories = [working_dir] - searchSubDirs = [True] - theSession.Parts.LoadOptions.SetSearchDirectories(searchDirectories, searchSubDirs) - + theSession.Parts.LoadOptions.SetSearchDirectories([working_dir], [True]) theSession.Parts.LoadOptions.ComponentsToLoad = NXOpen.LoadOptions.LoadComponents.All theSession.Parts.LoadOptions.PartLoadOption = NXOpen.LoadOptions.LoadOption.FullyLoad theSession.Parts.LoadOptions.SetInterpartData(True, NXOpen.LoadOptions.Parent.All) - theSession.Parts.LoadOptions.AllowSubstitution = False - theSession.Parts.LoadOptions.GenerateMissingPartFamilyMembers = True theSession.Parts.LoadOptions.AbortOnFailure = False - referenceSets = ["As Saved", "Use Simplified", "Use Model", "Entire Part", "Empty"] - theSession.Parts.LoadOptions.SetDefaultReferenceSets(referenceSets) - theSession.Parts.LoadOptions.ReferenceSetOverride = False - - print(f"[JOURNAL] Load directory set to: {working_dir}") - - # Close any currently open sim file to force reload from disk - print("[JOURNAL] Checking for open parts...") + # Close any open parts try: - current_work = theSession.Parts.BaseWork - if current_work and hasattr(current_work, 'FullPath'): - current_path = current_work.FullPath - print(f"[JOURNAL] Closing currently open part: {current_path}") - # Close without saving (we want to reload from disk) - partCloseResponses1 = [NXOpen.BasePart.CloseWholeTree] - theSession.Parts.CloseAll(partCloseResponses1) - print("[JOURNAL] Parts closed") - except Exception as e: - print(f"[JOURNAL] No parts to close or error closing: {e}") + theSession.Parts.CloseAll([NXOpen.BasePart.CloseWholeTree]) + except: + pass - # Open the .sim file (now will load fresh from disk with updated .prt files) - print(f"[JOURNAL] Opening simulation fresh from disk...") - basePart1, partLoadStatus1 = theSession.Parts.OpenActiveDisplay( - sim_file_path, - NXOpen.DisplayPartOption.AllowAdditional - ) + # Check for assembly FEM files + afm_files = [f for f in os.listdir(working_dir) if f.endswith('.afm')] + is_assembly = len(afm_files) > 0 - workSimPart = theSession.Parts.BaseWork - displaySimPart = theSession.Parts.BaseDisplay - - print(f"[JOURNAL] Simulation opened successfully") - partLoadStatus1.Dispose() - - # Switch to simulation application - theSession.ApplicationSwitchImmediate("UG_APP_SFEM") - - simPart1 = workSimPart - theSession.Post.UpdateUserGroupsFromSimPart(simPart1) - - # STEP 1: Try to switch to part and update expressions (optional for some models) - print(f"[JOURNAL] STEP 1: Checking for {part_base_name}.prt geometry...") - geometry_updated = False - try: - # Find the main part (may not exist for embedded geometry models) - bracketPart = None - try: - bracketPart = theSession.Parts.FindObject(part_base_name) - except: - pass - - if bracketPart: - print(f"[JOURNAL] Found {part_base_name} part, updating geometry...") - # Make Bracket the active display part - status, partLoadStatus = theSession.Parts.SetActiveDisplay( - bracketPart, - NXOpen.DisplayPartOption.AllowAdditional, - NXOpen.PartDisplayPartWorkPartOption.UseLast - ) - partLoadStatus.Dispose() - - workPart = theSession.Parts.Work - - # CRITICAL: Apply expression changes BEFORE updating geometry - expressions_updated = [] - - # Apply all expression updates dynamically - for expr_name, expr_value in expression_updates.items(): - print(f"[JOURNAL] Applying {expr_name} = {expr_value}") - try: - expr_obj = workPart.Expressions.FindObject(expr_name) - if expr_obj: - # Use millimeters as default unit for geometric parameters - unit_mm = workPart.UnitCollection.FindObject("MilliMeter") - workPart.Expressions.EditExpressionWithUnits(expr_obj, unit_mm, str(expr_value)) - expressions_updated.append(expr_obj) - print(f"[JOURNAL] {expr_name} updated successfully") - else: - print(f"[JOURNAL] WARNING: {expr_name} expression not found!") - except Exception as e: - print(f"[JOURNAL] ERROR updating {expr_name}: {e}") - - # Make expressions up to date - if expressions_updated: - print(f"[JOURNAL] Making {len(expressions_updated)} expression(s) up to date...") - for expr in expressions_updated: - markId_expr = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Make Up to Date") - objects1 = [expr] - theSession.UpdateManager.MakeUpToDate(objects1, markId_expr) - theSession.DeleteUndoMark(markId_expr, None) - - # CRITICAL: Update the geometry model - rebuilds features with new expressions - print(f"[JOURNAL] Rebuilding geometry with new expression values...") - markId_update = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "NX update") - nErrs = theSession.UpdateManager.DoUpdate(markId_update) - theSession.DeleteUndoMark(markId_update, "NX update") - print(f"[JOURNAL] {part_base_name} geometry updated ({nErrs} errors)") - - # Extract mass from expression p173 if it exists and write to temp file - try: - mass_expr = workPart.Expressions.FindObject("p173") - if mass_expr: - mass_kg = mass_expr.Value - mass_output_file = os.path.join(working_dir, "_temp_mass.txt") - with open(mass_output_file, 'w') as f: - f.write(str(mass_kg)) - print(f"[JOURNAL] Mass from p173: {mass_kg:.6f} kg ({mass_kg * 1000:.2f} g)") - print(f"[JOURNAL] Mass written to: {mass_output_file}") - except: - pass # Expression p173 might not exist in all models - - geometry_updated = True - else: - print(f"[JOURNAL] {part_base_name} part not found - may be embedded in sim file") - except Exception as e: - print(f"[JOURNAL] Could not update {part_base_name}.prt: {e}") - print(f"[JOURNAL] Continuing with sim-only solve...") - - # STEP 2: Try to switch to FEM part and update (optional for some models) - fem_part_name = f"{part_base_name}_fem1" - print(f"[JOURNAL] STEP 2: Checking for {fem_part_name}.fem...") - fem_updated = False - try: - # Find the FEM part (may not exist or may have different name) - femPart1 = None - try: - femPart1 = theSession.Parts.FindObject(fem_part_name) - except: - # Try with _i suffix for idealized FEM - try: - femPart1 = theSession.Parts.FindObject(f"{fem_part_name}_i") - except: - pass - - if femPart1: - print(f"[JOURNAL] Found FEM part, updating...") - # Make FEM the active display part - status, partLoadStatus = theSession.Parts.SetActiveDisplay( - femPart1, - NXOpen.DisplayPartOption.AllowAdditional, - NXOpen.PartDisplayPartWorkPartOption.SameAsDisplay - ) - partLoadStatus.Dispose() - - workFemPart = theSession.Parts.BaseWork - - # CRITICAL: Update FE Model - regenerates FEM with new geometry - print("[JOURNAL] Updating FE Model...") - fEModel1 = workFemPart.FindObject("FEModel") - if fEModel1: - fEModel1.UpdateFemodel() - print("[JOURNAL] FE Model updated with new geometry!") - fem_updated = True - else: - print("[JOURNAL] WARNING: Could not find FEModel object") - else: - print(f"[JOURNAL] FEM part not found - may be embedded in sim file") - except Exception as e: - print(f"[JOURNAL] Could not update FEM: {e}") - print(f"[JOURNAL] Continuing with sim-only solve...") - - # STEP 3: Switch back to sim part - print("[JOURNAL] STEP 3: Switching back to sim part...") - try: - status, partLoadStatus = theSession.Parts.SetActiveDisplay( - simPart1, - NXOpen.DisplayPartOption.AllowAdditional, - NXOpen.PartDisplayPartWorkPartOption.UseLast - ) - partLoadStatus.Dispose() - workSimPart = theSession.Parts.BaseWork - print("[JOURNAL] Switched back to sim part") - except Exception as e: - print(f"[JOURNAL] WARNING: Error switching to sim part: {e}") - - # Note: Old output files are deleted by nx_solver.py before calling this journal - # This ensures NX performs a fresh solve - - # Solve the simulation - print("[JOURNAL] Starting solve...") - markId3 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start") - theSession.SetUndoMarkName(markId3, "Solve Dialog") - - markId5 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Solve") - - theCAESimSolveManager = NXOpen.CAE.SimSolveManager.GetSimSolveManager(theSession) - - # Get the simulation object - simSimulation1 = workSimPart.FindObject("Simulation") - - # CRITICAL: Disable solution monitor when solving multiple solutions - # This prevents NX from opening multiple monitor windows which superpose and cause usability issues - if not solution_name: - print("[JOURNAL] Disabling solution monitor for all solutions to prevent window pile-up...") - try: - # Get all solutions in the simulation - solutions_disabled = 0 - solution_num = 1 - while True: - try: - solution_obj_name = f"Solution[Solution {solution_num}]" - simSolution = simSimulation1.FindObject(solution_obj_name) - if simSolution: - propertyTable = simSolution.SolverOptionsPropertyTable - propertyTable.SetBooleanPropertyValue("solution monitor", False) - solutions_disabled += 1 - solution_num += 1 - else: - break - except: - break # No more solutions - print(f"[JOURNAL] Solution monitor disabled for {solutions_disabled} solution(s)") - except Exception as e: - print(f"[JOURNAL] WARNING: Could not disable solution monitor: {e}") - print(f"[JOURNAL] Continuing with solve anyway...") - - # Get the solution(s) to solve - either specific or all - if solution_name: - # Solve specific solution in background mode - solution_obj_name = f"Solution[{solution_name}]" - print(f"[JOURNAL] Looking for solution: {solution_obj_name}") - simSolution1 = simSimulation1.FindObject(solution_obj_name) - psolutions1 = [simSolution1] - - numsolutionssolved1, numsolutionsfailed1, numsolutionsskipped1 = theCAESimSolveManager.SolveChainOfSolutions( - psolutions1, - NXOpen.CAE.SimSolution.SolveOption.Solve, - NXOpen.CAE.SimSolution.SetupCheckOption.CompleteDeepCheckAndOutputErrors, - NXOpen.CAE.SimSolution.SolveMode.Background + if is_assembly and expression_updates: + print(f"[JOURNAL] ") + print(f"[JOURNAL] DETECTED: Multi-part Assembly FEM") + print(f"[JOURNAL] Using ASSEMBLY FEM WORKFLOW") + print(f"[JOURNAL] ") + return solve_assembly_fem_workflow( + theSession, sim_file_path, solution_name, expression_updates, working_dir ) else: - # Solve ALL solutions using SolveAllSolutions API (Foreground mode) - # This ensures all solutions (static + modal, etc.) complete before returning - print(f"[JOURNAL] Solving all solutions using SolveAllSolutions API (Foreground mode)...") - - numsolutionssolved1, numsolutionsfailed1, numsolutionsskipped1 = theCAESimSolveManager.SolveAllSolutions( - NXOpen.CAE.SimSolution.SolveOption.Solve, - NXOpen.CAE.SimSolution.SetupCheckOption.CompleteCheckAndOutputErrors, - NXOpen.CAE.SimSolution.SolveMode.Foreground, - False + print(f"[JOURNAL] ") + print(f"[JOURNAL] Using SIMPLE WORKFLOW (no expression updates or single-part)") + print(f"[JOURNAL] ") + return solve_simple_workflow( + theSession, sim_file_path, solution_name, expression_updates, working_dir ) - theSession.DeleteUndoMark(markId5, None) - theSession.SetUndoMarkName(markId3, "Solve") - - print(f"[JOURNAL] Solve completed!") - print(f"[JOURNAL] Solutions solved: {numsolutionssolved1}") - print(f"[JOURNAL] Solutions failed: {numsolutionsfailed1}") - print(f"[JOURNAL] Solutions skipped: {numsolutionsskipped1}") - - # NOTE: When solution_name=None, we use Foreground mode to ensure all solutions - # complete before returning. When solution_name is specified, Background mode is used. - - # Save the simulation to write all output files - print("[JOURNAL] Saving simulation to ensure output files are written...") - simPart2 = workSimPart - partSaveStatus1 = simPart2.Save( - NXOpen.BasePart.SaveComponents.TrueValue, - NXOpen.BasePart.CloseAfterSave.FalseValue - ) - partSaveStatus1.Dispose() - print("[JOURNAL] Save complete!") - - return True - except Exception as e: - print(f"[JOURNAL] ERROR: {e}") + print(f"[JOURNAL] FATAL ERROR: {e}") import traceback traceback.print_exc() return False +def solve_assembly_fem_workflow(theSession, sim_file_path, solution_name, expression_updates, working_dir): + """ + Full assembly FEM workflow based on recorded NX journal. + + This is the correct workflow for multi-part assembly FEMs. + """ + sim_filename = os.path.basename(sim_file_path) + + # ========================================================================== + # STEP 1: LOAD REQUIRED PARTS + # ========================================================================== + print(f"[JOURNAL] STEP 1: Loading required parts...") + + # Load ASSY_M1.prt (to have the geometry assembly available) + assy_prt_path = os.path.join(working_dir, "ASSY_M1.prt") + if os.path.exists(assy_prt_path): + print(f"[JOURNAL] Loading ASSY_M1.prt...") + markId1 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Load Part") + part1, partLoadStatus1 = theSession.Parts.Open(assy_prt_path) + partLoadStatus1.Dispose() + else: + print(f"[JOURNAL] WARNING: ASSY_M1.prt not found, continuing anyway...") + + # Load M1_Blank_fem1_i.prt (idealized geometry) + idealized_prt_path = os.path.join(working_dir, "M1_Blank_fem1_i.prt") + if os.path.exists(idealized_prt_path): + print(f"[JOURNAL] Loading M1_Blank_fem1_i.prt...") + markId2 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Load Part") + part2, partLoadStatus2 = theSession.Parts.Open(idealized_prt_path) + partLoadStatus2.Dispose() + + # ========================================================================== + # STEP 2: UPDATE EXPRESSIONS IN M1_BLANK + # ========================================================================== + print(f"[JOURNAL] STEP 2: Updating expressions in M1_Blank...") + + # Find and switch to M1_Blank part + try: + part3 = theSession.Parts.FindObject("M1_Blank") + markId3 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Change Displayed Part") + status1, partLoadStatus3 = theSession.Parts.SetActiveDisplay( + part3, + NXOpen.DisplayPartOption.AllowAdditional, + NXOpen.PartDisplayPartWorkPartOption.UseLast + ) + partLoadStatus3.Dispose() + + # Switch to modeling application for expression editing + theSession.ApplicationSwitchImmediate("UG_APP_MODELING") + + workPart = theSession.Parts.Work + + # Create undo mark for expressions + markId4 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start") + theSession.SetUndoMarkName(markId4, "Expressions Dialog") + + # Write expressions to a temp file and import (more reliable than editing one by one) + exp_file_path = os.path.join(working_dir, "_temp_expressions.exp") + with open(exp_file_path, 'w') as f: + for expr_name, expr_value in expression_updates.items(): + # Determine unit + if 'angle' in expr_name.lower() or 'vertical' in expr_name.lower(): + unit_str = "Degrees" + else: + unit_str = "MilliMeter" + f.write(f"[{unit_str}]{expr_name}={expr_value}\n") + print(f"[JOURNAL] {expr_name} = {expr_value} ({unit_str})") + + print(f"[JOURNAL] Importing expressions from file...") + markId_import = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Import Expressions") + + try: + expModified, errorMessages = workPart.Expressions.ImportFromFile( + exp_file_path, + NXOpen.ExpressionCollection.ImportMode.Replace + ) + print(f"[JOURNAL] Expressions imported: {expModified} modified") + if errorMessages: + print(f"[JOURNAL] Import errors: {errorMessages}") + + # Update geometry after import + print(f"[JOURNAL] Rebuilding geometry...") + markId_update = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "NX update") + nErrs = theSession.UpdateManager.DoUpdate(markId_update) + theSession.DeleteUndoMark(markId_update, "NX update") + print(f"[JOURNAL] Geometry rebuilt ({nErrs} errors)") + + updated_expressions = list(expression_updates.keys()) + + except Exception as e: + print(f"[JOURNAL] ERROR importing expressions: {e}") + updated_expressions = [] + + # Clean up temp file + try: + os.remove(exp_file_path) + except: + pass + + theSession.SetUndoMarkName(markId4, "Expressions") + + except Exception as e: + print(f"[JOURNAL] ERROR updating expressions: {e}") + + # ========================================================================== + # STEP 3: OPEN SIM AND UPDATE COMPONENT FEMs + # ========================================================================== + print(f"[JOURNAL] STEP 3: Opening sim and updating component FEMs...") + + # Try to find the sim part first (like the recorded journal does) + # This ensures we're working with the same loaded sim part context + sim_part_name = os.path.splitext(sim_filename)[0] # e.g., "ASSY_M1_assyfem1_sim1" + print(f"[JOURNAL] Looking for sim part: {sim_part_name}") + + markId_sim = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Change Displayed Part") + + try: + # First try to find it among loaded parts (like recorded journal) + simPart1 = theSession.Parts.FindObject(sim_part_name) + status_sim, partLoadStatus = theSession.Parts.SetActiveDisplay( + simPart1, + NXOpen.DisplayPartOption.AllowAdditional, + NXOpen.PartDisplayPartWorkPartOption.UseLast + ) + partLoadStatus.Dispose() + print(f"[JOURNAL] Found and activated existing sim part") + except: + # Fallback: Open fresh if not found + print(f"[JOURNAL] Sim part not found, opening fresh: {sim_filename}") + basePart, partLoadStatus = theSession.Parts.OpenActiveDisplay( + sim_file_path, + NXOpen.DisplayPartOption.AllowAdditional + ) + partLoadStatus.Dispose() + + workSimPart = theSession.Parts.BaseWork + displaySimPart = theSession.Parts.BaseDisplay + theSession.ApplicationSwitchImmediate("UG_APP_SFEM") + theSession.Post.UpdateUserGroupsFromSimPart(workSimPart) + + # Navigate component hierarchy + try: + rootComponent = workSimPart.ComponentAssembly.RootComponent + component1 = rootComponent.FindObject("COMPONENT ASSY_M1_assyfem1 1") + + # Update M1_Blank_fem1 + print(f"[JOURNAL] Updating M1_Blank_fem1...") + try: + component2 = component1.FindObject("COMPONENT M1_Blank_fem1 1") + markId_fem1 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Make Work Part") + partLoadStatus5 = theSession.Parts.SetWorkComponent( + component2, + NXOpen.PartCollection.RefsetOption.Entire, + NXOpen.PartCollection.WorkComponentOption.Visible + ) + workFemPart = theSession.Parts.BaseWork + partLoadStatus5.Dispose() + + markId_update1 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Update FE Model") + fEModel1 = workFemPart.FindObject("FEModel") + fEModel1.UpdateFemodel() + print(f"[JOURNAL] M1_Blank_fem1 updated") + except Exception as e: + print(f"[JOURNAL] WARNING: M1_Blank_fem1: {e}") + + # Update M1_Vertical_Support_Skeleton_fem1 + print(f"[JOURNAL] Updating M1_Vertical_Support_Skeleton_fem1...") + try: + component3 = component1.FindObject("COMPONENT M1_Vertical_Support_Skeleton_fem1 3") + markId_fem2 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Make Work Part") + partLoadStatus6 = theSession.Parts.SetWorkComponent( + component3, + NXOpen.PartCollection.RefsetOption.Entire, + NXOpen.PartCollection.WorkComponentOption.Visible + ) + workFemPart = theSession.Parts.BaseWork + partLoadStatus6.Dispose() + + markId_update2 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Update FE Model") + fEModel2 = workFemPart.FindObject("FEModel") + fEModel2.UpdateFemodel() + print(f"[JOURNAL] M1_Vertical_Support_Skeleton_fem1 updated") + except Exception as e: + print(f"[JOURNAL] WARNING: M1_Vertical_Support_Skeleton_fem1: {e}") + + except Exception as e: + print(f"[JOURNAL] ERROR navigating component hierarchy: {e}") + + # ========================================================================== + # STEP 4: MERGE DUPLICATE NODES + # ========================================================================== + print(f"[JOURNAL] STEP 4: Merging duplicate nodes...") + + try: + # Switch to assembly FEM + partLoadStatus8 = theSession.Parts.SetWorkComponent( + component1, + NXOpen.PartCollection.RefsetOption.Entire, + NXOpen.PartCollection.WorkComponentOption.Visible + ) + workAssyFemPart = theSession.Parts.BaseWork + displaySimPart = theSession.Parts.BaseDisplay + partLoadStatus8.Dispose() + print(f"[JOURNAL] Switched to assembly FEM: {workAssyFemPart.Name}") + + markId_merge = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start") + + caePart1 = workAssyFemPart + duplicateNodesCheckBuilder1 = caePart1.ModelCheckMgr.CreateDuplicateNodesCheckBuilder() + + # Set tolerance + unit_tol = duplicateNodesCheckBuilder1.Tolerance.Units + duplicateNodesCheckBuilder1.Tolerance.Units = unit_tol + duplicateNodesCheckBuilder1.Tolerance.SetFormula("0.01") + print(f"[JOURNAL] Tolerance: 0.01 mm") + + # Enable occurrence node merge - CRITICAL for assembly FEM + duplicateNodesCheckBuilder1.MergeOccurrenceNodes = True + print(f"[JOURNAL] MergeOccurrenceNodes: True") + + theSession.SetUndoMarkName(markId_merge, "Duplicate Nodes Dialog") + + # Configure display settings + displaysettings1 = NXOpen.CAE.ModelCheck.DuplicateNodesCheckBuilder.DisplaySettings() + displaysettings1.ShowDuplicateNodes = True + displaysettings1.ShowMergedNodeLabels = False + displaysettings1.ShowRetainedNodeLabels = False + displaysettings1.KeepNodesColor = displaySimPart.Colors.Find("Blue") + displaysettings1.MergeNodesColor = displaySimPart.Colors.Find("Yellow") + displaysettings1.UnableToMergeNodesColor = displaySimPart.Colors.Find("Red") + duplicateNodesCheckBuilder1.DisplaySettingsData = displaysettings1 + + # Check scope + duplicateNodesCheckBuilder1.CheckScopeOption = NXOpen.CAE.ModelCheck.CheckScope.Displayed + print(f"[JOURNAL] CheckScope: Displayed") + + # Identify duplicates + print(f"[JOURNAL] Identifying duplicate nodes...") + numDuplicates = duplicateNodesCheckBuilder1.IdentifyDuplicateNodes() + print(f"[JOURNAL] Found {numDuplicates} duplicate node sets") + + # Merge duplicates + if numDuplicates > 0: + print(f"[JOURNAL] Merging duplicate nodes...") + numMerged = duplicateNodesCheckBuilder1.MergeDuplicateNodes() + print(f"[JOURNAL] Merged {numMerged} duplicate node sets") + else: + print(f"[JOURNAL] WARNING: No duplicate nodes found to merge!") + print(f"[JOURNAL] This may indicate mesh update didn't work properly") + + theSession.SetUndoMarkName(markId_merge, "Duplicate Nodes") + duplicateNodesCheckBuilder1.Destroy() + theSession.DeleteUndoMark(markId_merge, None) + + except Exception as e: + print(f"[JOURNAL] WARNING: Node merge: {e}") + import traceback + traceback.print_exc() + + # ========================================================================== + # STEP 5: RESOLVE LABEL CONFLICTS + # ========================================================================== + print(f"[JOURNAL] STEP 5: Resolving label conflicts...") + + try: + markId_labels = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start") + + assyFemPart1 = workAssyFemPart + assemblyLabelManagerBuilder1 = assyFemPart1.CreateAssemblyLabelManagerBuilder() + + theSession.SetUndoMarkName(markId_labels, "Assembly Label Manager Dialog") + + markId_labels2 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Assembly Label Manager") + + # Set offsets for each FE model occurrence + # These offsets ensure unique node/element labels across components + entitytypes = [ + NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Node, + NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Element, + NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Csys, + NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Physical, + NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Group, + NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Ply, + NXOpen.CAE.AssemblyLabelManagerBuilder.EntityType.Ssmo, + ] + + # Apply offsets to each occurrence (values from recorded journal) + occurrence_offsets = [ + ("FEModelOccurrence[3]", 2), + ("FEModelOccurrence[4]", 74), + ("FEModelOccurrence[5]", 146), + ("FEModelOccurrence[7]", 218), + ] + + for occ_name, offset_val in occurrence_offsets: + try: + fEModelOcc = workAssyFemPart.FindObject(occ_name) + offsets = [offset_val] * 7 + assemblyLabelManagerBuilder1.SetFEModelOccOffsets(fEModelOcc, entitytypes, offsets) + except: + pass # Some occurrences may not exist + + nXObject1 = assemblyLabelManagerBuilder1.Commit() + + theSession.DeleteUndoMark(markId_labels2, None) + theSession.SetUndoMarkName(markId_labels, "Assembly Label Manager") + assemblyLabelManagerBuilder1.Destroy() + + print(f"[JOURNAL] Label conflicts resolved") + + except Exception as e: + print(f"[JOURNAL] WARNING: Label management: {e}") + + # ========================================================================== + # STEP 6: SOLVE + # ========================================================================== + print(f"[JOURNAL] STEP 6: Solving simulation...") + + try: + # Return to sim level by setting null component + partLoadStatus9 = theSession.Parts.SetWorkComponent( + NXOpen.Assemblies.Component.Null, + NXOpen.PartCollection.RefsetOption.Entire, + NXOpen.PartCollection.WorkComponentOption.Visible + ) + workSimPart = theSession.Parts.BaseWork + partLoadStatus9.Dispose() + + # Set up solve + markId_solve = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start") + theSession.SetUndoMarkName(markId_solve, "Solve Dialog") + + markId_solve2 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Solve") + + theCAESimSolveManager = NXOpen.CAE.SimSolveManager.GetSimSolveManager(theSession) + + simSimulation1 = workSimPart.FindObject("Simulation") + sol_name = solution_name if solution_name else "Solution 1" + simSolution1 = simSimulation1.FindObject(f"Solution[{sol_name}]") + + psolutions1 = [simSolution1] + + print(f"[JOURNAL] Solving: {sol_name} (Foreground mode)") + numsolved, numfailed, numskipped = theCAESimSolveManager.SolveChainOfSolutions( + psolutions1, + NXOpen.CAE.SimSolution.SolveOption.Solve, + NXOpen.CAE.SimSolution.SetupCheckOption.CompleteCheckAndOutputErrors, + NXOpen.CAE.SimSolution.SolveMode.Foreground # Use Foreground to ensure OP2 is complete + ) + + theSession.DeleteUndoMark(markId_solve2, None) + theSession.SetUndoMarkName(markId_solve, "Solve") + + print(f"[JOURNAL] Solve completed: {numsolved} solved, {numfailed} failed, {numskipped} skipped") + + return numfailed == 0 + + except Exception as e: + print(f"[JOURNAL] ERROR solving: {e}") + import traceback + traceback.print_exc() + return False + + +def solve_simple_workflow(theSession, sim_file_path, solution_name, expression_updates, working_dir): + """ + Simple workflow for single-part simulations or when no expression updates needed. + """ + print(f"[JOURNAL] Opening simulation: {sim_file_path}") + + # Open the .sim file + basePart1, partLoadStatus1 = theSession.Parts.OpenActiveDisplay( + sim_file_path, + NXOpen.DisplayPartOption.AllowAdditional + ) + partLoadStatus1.Dispose() + + workSimPart = theSession.Parts.BaseWork + theSession.ApplicationSwitchImmediate("UG_APP_SFEM") + theSession.Post.UpdateUserGroupsFromSimPart(workSimPart) + + # Set up solve + markId_solve = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start") + theSession.SetUndoMarkName(markId_solve, "Solve Dialog") + + markId_solve2 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Solve") + + theCAESimSolveManager = NXOpen.CAE.SimSolveManager.GetSimSolveManager(theSession) + + simSimulation1 = workSimPart.FindObject("Simulation") + sol_name = solution_name if solution_name else "Solution 1" + simSolution1 = simSimulation1.FindObject(f"Solution[{sol_name}]") + + psolutions1 = [simSolution1] + + print(f"[JOURNAL] Solving: {sol_name}") + numsolved, numfailed, numskipped = theCAESimSolveManager.SolveChainOfSolutions( + psolutions1, + NXOpen.CAE.SimSolution.SolveOption.Solve, + NXOpen.CAE.SimSolution.SetupCheckOption.CompleteCheckAndOutputErrors, + NXOpen.CAE.SimSolution.SolveMode.Background + ) + + theSession.DeleteUndoMark(markId_solve2, None) + theSession.SetUndoMarkName(markId_solve, "Solve") + + print(f"[JOURNAL] Solve completed: {numsolved} solved, {numfailed} failed, {numskipped} skipped") + + # Save + try: + partSaveStatus = workSimPart.Save( + NXOpen.BasePart.SaveComponents.TrueValue, + NXOpen.BasePart.CloseAfterSave.FalseValue + ) + partSaveStatus.Dispose() + print(f"[JOURNAL] Saved!") + except: + pass + + return numfailed == 0 + + if __name__ == '__main__': success = main(sys.argv[1:]) sys.exit(0 if success else 1) diff --git a/studies/m1_mirror_zernike_optimization/1_setup/optimization_config.json b/studies/m1_mirror_zernike_optimization/1_setup/optimization_config.json new file mode 100644 index 00000000..be1108a1 --- /dev/null +++ b/studies/m1_mirror_zernike_optimization/1_setup/optimization_config.json @@ -0,0 +1,236 @@ +{ + "$schema": "Atomizer M1 Mirror Zernike Optimization", + "study_name": "m1_mirror_zernike_optimization", + "description": "Telescope primary mirror support structure optimization using Zernike wavefront error metrics with neural acceleration", + + "design_variables": [ + { + "name": "lateral_inner_angle", + "expression_name": "lateral_inner_angle", + "min": 25.0, + "max": 28.5, + "baseline": 26.79, + "units": "degrees", + "description": "Lateral support inner angle", + "enabled": false + }, + { + "name": "lateral_outer_angle", + "expression_name": "lateral_outer_angle", + "min": 13.0, + "max": 17.0, + "baseline": 14.64, + "units": "degrees", + "description": "Lateral support outer angle", + "enabled": false + }, + { + "name": "lateral_outer_pivot", + "expression_name": "lateral_outer_pivot", + "min": 9.0, + "max": 12.0, + "baseline": 10.40, + "units": "mm", + "description": "Lateral outer pivot position", + "enabled": false + }, + { + "name": "lateral_inner_pivot", + "expression_name": "lateral_inner_pivot", + "min": 9.0, + "max": 12.0, + "baseline": 10.07, + "units": "mm", + "description": "Lateral inner pivot position", + "enabled": false + }, + { + "name": "lateral_middle_pivot", + "expression_name": "lateral_middle_pivot", + "min": 18.0, + "max": 23.0, + "baseline": 20.73, + "units": "mm", + "description": "Lateral middle pivot position", + "enabled": false + }, + { + "name": "lateral_closeness", + "expression_name": "lateral_closeness", + "min": 9.5, + "max": 12.5, + "baseline": 11.02, + "units": "mm", + "description": "Lateral support closeness parameter", + "enabled": false + }, + { + "name": "whiffle_min", + "expression_name": "whiffle_min", + "min": 35.0, + "max": 55.0, + "baseline": 40.55, + "units": "mm", + "description": "Whiffle tree minimum parameter", + "enabled": true + }, + { + "name": "whiffle_outer_to_vertical", + "expression_name": "whiffle_outer_to_vertical", + "min": 68.0, + "max": 80.0, + "baseline": 75.67, + "units": "degrees", + "description": "Whiffle tree outer to vertical angle", + "enabled": true + }, + { + "name": "whiffle_triangle_closeness", + "expression_name": "whiffle_triangle_closeness", + "min": 50.0, + "max": 65.0, + "baseline": 60.00, + "units": "mm", + "description": "Whiffle tree triangle closeness", + "enabled": false + }, + { + "name": "blank_backface_angle", + "expression_name": "blank_backface_angle", + "min": 3.5, + "max": 5.0, + "baseline": 4.23, + "units": "degrees", + "description": "Mirror blank backface angle", + "enabled": false + }, + { + "name": "inner_circular_rib_dia", + "expression_name": "inner_circular_rib_dia", + "min": 480.0, + "max": 620.0, + "baseline": 534.00, + "units": "mm", + "description": "Inner circular rib diameter", + "enabled": true + } + ], + + "objectives": [ + { + "name": "rel_filtered_rms_40_vs_20", + "description": "Filtered RMS WFE at 40 deg relative to 20 deg reference", + "direction": "minimize", + "weight": 5.0, + "target": 4.0, + "units": "nm", + "extractor": "zernike_relative", + "extractor_config": { + "target_subcase": "40", + "reference_subcase": "20", + "metric": "relative_filtered_rms_nm" + } + }, + { + "name": "rel_filtered_rms_60_vs_20", + "description": "Filtered RMS WFE at 60 deg relative to 20 deg reference", + "direction": "minimize", + "weight": 5.0, + "target": 10.0, + "units": "nm", + "extractor": "zernike_relative", + "extractor_config": { + "target_subcase": "60", + "reference_subcase": "20", + "metric": "relative_filtered_rms_nm" + } + }, + { + "name": "mfg_90_optician_workload", + "description": "Optician workload at 90 deg polishing orientation (filtered RMS with J1-J3)", + "direction": "minimize", + "weight": 1.0, + "target": 20.0, + "units": "nm", + "extractor": "zernike", + "extractor_config": { + "subcase": "90", + "metric": "rms_filter_j1to3", + "reference_subcase": "20" + } + } + ], + + "constraints": [ + { + "name": "max_stress", + "description": "Maximum von Mises stress in mirror assembly", + "type": "upper_bound", + "threshold": 10.0, + "units": "MPa", + "enabled": false + } + ], + + "zernike_settings": { + "n_modes": 50, + "filter_low_orders": 4, + "displacement_unit": "mm", + "subcases": ["1", "2", "3", "4"], + "subcase_labels": {"1": "90deg", "2": "20deg", "3": "40deg", "4": "60deg"}, + "reference_subcase": "2", + "polishing_subcase": "1", + "output_full_coefficients": true, + "_note": "Subcase mapping matches NX: 1=90deg, 2=20deg(ref), 3=40deg, 4=60deg" + }, + + "optimization_settings": { + "n_trials": 100, + "n_fea_trials": 40, + "n_neural_trials": 500, + "sampler": "TPE", + "seed": 42, + "n_startup_trials": 15, + "tpe_n_ei_candidates": 150, + "tpe_multivariate": true, + "objective_strategy": "weighted_sum", + "objective_direction": "minimize" + }, + + "surrogate_settings": { + "enabled": true, + "model_type": "ParametricZernikePredictor", + "training_config": { + "hidden_channels": 128, + "num_layers": 4, + "learning_rate": 0.001, + "epochs": 200, + "batch_size": 8, + "train_split": 0.8 + }, + "outputs": { + "description": "50 Zernike coefficients x 4 subcases = 200 outputs", + "coefficients_per_subcase": 50, + "subcases": ["20", "40", "60", "90"] + } + }, + + "nx_settings": { + "nx_install_path": "C:\\Program Files\\Siemens\\NX2506", + "model_dir": "C:\\Users\\Antoine\\CADTOMASTE\\Atomizer\\M1-Gigabit\\Latest", + "sim_file": "ASSY_M1_assyfem1_sim1.sim", + "solution_name": "Solution 1", + "solve_all_subcases": true, + "op2_pattern": "*-solution_1.op2", + "op2_timeout_s": 1800, + "op2_stable_s": 5, + "post_solve_delay_s": 5 + }, + + "output_settings": { + "save_zernike_coefficients": true, + "save_field_data": true, + "generate_reports": true, + "archive_results": true + } +} diff --git a/studies/m1_mirror_zernike_optimization/2_results/study.db b/studies/m1_mirror_zernike_optimization/2_results/study.db new file mode 100644 index 00000000..b333a5fe Binary files /dev/null and b/studies/m1_mirror_zernike_optimization/2_results/study.db differ diff --git a/studies/m1_mirror_zernike_optimization/DASHBOARD.md b/studies/m1_mirror_zernike_optimization/DASHBOARD.md new file mode 100644 index 00000000..a09a2703 --- /dev/null +++ b/studies/m1_mirror_zernike_optimization/DASHBOARD.md @@ -0,0 +1,134 @@ +# M1 Mirror Zernike Optimization Dashboard + +## Study Overview + +**Objective**: Optimize telescope primary mirror (M1) support structure to minimize wavefront error across different gravity orientations. + +**Method**: Hybrid FEA + Neural Network acceleration using Zernike polynomial decomposition. + +## Quick Start + +### 1. Prepare Model Files + +Copy your NX model files to: +``` +studies/m1_mirror_zernike_optimization/1_setup/model/ +``` + +Required files: +- `ASSY_M1.prt` (or your assembly name) +- `ASSY_M1_assyfem1.afm` +- `ASSY_M1_assyfem1_sim1.sim` +- Associated `.fem` and `_i.prt` files + +### 2. Run FEA Trials (Build Training Data) + +```bash +cd studies/m1_mirror_zernike_optimization +python run_optimization.py --run --trials 40 +``` + +This will: +- Run ~40 FEA trials (10-15 min each = ~8-10 hours) +- Extract 50 Zernike coefficients for each subcase (20/40/60/90 deg) +- Store all data in Optuna database + +### 3. Train Neural Surrogate + +```bash +python run_optimization.py --train-surrogate +``` + +Trains MLP to predict 200 outputs (50 coefficients x 4 subcases) from design variables. + +### 4. Run Neural-Accelerated Optimization + +```bash +python run_optimization.py --run --trials 1000 --enable-nn +``` + +1000 trials in ~seconds! + +### 5. View Results + +**Optuna Dashboard:** +```bash +optuna-dashboard sqlite:///2_results/study.db --port 8081 +``` +Open http://localhost:8081 + +## Design Variables + +| Variable | Range | Baseline | Units | Status | +|----------|-------|----------|-------|--------| +| whiffle_min | 35-55 | 40.55 | mm | **Enabled** | +| whiffle_outer_to_vertical | 68-80 | 75.67 | deg | **Enabled** | +| inner_circular_rib_dia | 480-620 | 534.00 | mm | **Enabled** | +| whiffle_triangle_closeness | 50-65 | 60.00 | mm | Disabled | +| blank_backface_angle | 3.5-5.0 | 4.23 | deg | Disabled | +| lateral_inner_angle | 25-28.5 | 26.79 | deg | Disabled | +| lateral_outer_angle | 13-17 | 14.64 | deg | Disabled | +| lateral_outer_pivot | 9-12 | 10.40 | mm | Disabled | +| lateral_inner_pivot | 9-12 | 10.07 | mm | Disabled | +| lateral_middle_pivot | 18-23 | 20.73 | mm | Disabled | +| lateral_closeness | 9.5-12.5 | 11.02 | mm | Disabled | + +Edit `1_setup/optimization_config.json` to enable/disable variables. + +## Objectives + +| Objective | Weight | Target | Description | +|-----------|--------|--------|-------------| +| rel_filtered_rms_40_vs_20 | 5 | 4 nm | WFE at 40° relative to 20° reference | +| rel_filtered_rms_60_vs_20 | 5 | 10 nm | WFE at 60° relative to 20° reference | +| mfg_90_optician_workload | 1 | 20 nm | Polishing workload at 90° orientation | + +**Strategy**: Weighted sum minimization (normalized by targets) + +## Neural Surrogate Architecture + +- **Input**: Design variables (3-11 depending on enabled) +- **Output**: 200 values (50 Zernike coefficients × 4 subcases) +- **Architecture**: MLP with 4 layers, 128 hidden units +- **Training**: ~40 FEA samples, 200 epochs + +## File Structure + +``` +m1_mirror_zernike_optimization/ +├── 1_setup/ +│ ├── optimization_config.json # Configuration +│ └── model/ # NX model files (add yours here) +├── 2_results/ +│ ├── study.db # Optuna database +│ └── zernike_surrogate/ # Trained neural model +│ └── checkpoint_best.pt +├── run_optimization.py # Main script +└── DASHBOARD.md # This file +``` + +## Commands Reference + +```bash +# Run FEA optimization +python run_optimization.py --run --trials 40 + +# Train neural surrogate +python run_optimization.py --train-surrogate + +# Run with neural acceleration +python run_optimization.py --run --trials 1000 --enable-nn + +# Check status +python run_optimization.py --status + +# Launch Optuna dashboard +optuna-dashboard sqlite:///2_results/study.db --port 8081 +``` + +## Tips + +1. **Start small**: Run 5-10 FEA trials first to verify everything works +2. **Check Zernike extraction**: Verify OP2 has correct subcases (20/40/60/90) +3. **Enable variables gradually**: Start with 3, add more after initial exploration +4. **Neural validation**: After finding good neural designs, verify top candidates with FEA diff --git a/studies/m1_mirror_zernike_optimization/README.md b/studies/m1_mirror_zernike_optimization/README.md new file mode 100644 index 00000000..2f76ea2a --- /dev/null +++ b/studies/m1_mirror_zernike_optimization/README.md @@ -0,0 +1,429 @@ +# M1 Mirror Zernike Optimization + +Multi-objective telescope primary mirror support structure optimization using Zernike wavefront error decomposition with neural network acceleration. + +**Created**: 2025-11-28 +**Protocol**: Protocol 12 (Hybrid FEA/Neural with Zernike) +**Status**: Setup Complete - Requires Expression Path Fix + +--- + +## 1. Engineering Problem + +### 1.1 Objective + +Optimize the telescope primary mirror (M1) support structure to minimize wavefront error (WFE) across different gravity orientations (zenith angles), ensuring consistent optical performance from 20° to 90° elevation. + +### 1.2 Physical System + +- **Component**: M1 primary mirror assembly with whiffle tree support +- **Material**: Borosilicate glass (mirror blank), steel (support structure) +- **Loading**: Gravity at multiple zenith angles (20°, 40°, 60°, 90°) +- **Boundary Conditions**: Whiffle tree kinematic mount +- **Analysis Type**: Linear static multi-subcase (Nastran SOL 101) +- **Output**: Surface deformation → Zernike polynomial decomposition + +--- + +## 2. Mathematical Formulation + +### 2.1 Objectives + +| Objective | Goal | Weight | Formula | Units | Target | +|-----------|------|--------|---------|-------|--------| +| rel_filtered_rms_40_vs_20 | minimize | 5.0 | $\sigma_{40/20} = \sqrt{\sum_{j=5}^{50} (Z_j^{rel})^2}$ | nm | 4 nm | +| rel_filtered_rms_60_vs_20 | minimize | 5.0 | $\sigma_{60/20} = \sqrt{\sum_{j=5}^{50} (Z_j^{rel})^2}$ | nm | 10 nm | +| mfg_90_optician_workload | minimize | 1.0 | $\sigma_{90}^{J4+} = \sqrt{\sum_{j=4}^{50} (Z_j^{rel})^2}$ | nm | 20 nm | + +Where: +- $Z_j^{rel}$ = Relative Zernike coefficient (target subcase minus reference) +- Filtered RMS excludes J1-J4 (piston, tip, tilt, defocus) - correctable by alignment +- Manufacturing workload keeps J4 (defocus) since it represents optician correction effort + +### 2.2 Zernike Decomposition + +The wavefront error $W(r,\theta)$ is decomposed into Zernike polynomials: + +$$W(r,\theta) = \sum_{j=1}^{50} Z_j \cdot P_j(r,\theta)$$ + +Where $P_j$ are Noll-indexed Zernike polynomials on the unit disk. + +**WFE from Displacement**: +$$W_{nm} = 2 \cdot \delta_z \cdot 10^6$$ + +Where $\delta_z$ is the Z-displacement in mm (factor of 2 for reflection). + +### 2.3 Design Variables + +| Parameter | Symbol | Bounds | Baseline | Units | Description | +|-----------|--------|--------|----------|-------|-------------| +| whiffle_min | $w_{min}$ | [35, 55] | 40.55 | mm | Whiffle tree minimum parameter | +| whiffle_outer_to_vertical | $\alpha$ | [68, 80] | 75.67 | deg | Outer support angle to vertical | +| inner_circular_rib_dia | $D_{rib}$ | [480, 620] | 534.00 | mm | Inner circular rib diameter | + +**Design Space**: +$$\mathbf{x} = [w_{min}, \alpha, D_{rib}]^T \in \mathbb{R}^3$$ + +**Additional Variables (Disabled)**: +- lateral_inner_angle, lateral_outer_angle (lateral support angles) +- lateral_outer_pivot, lateral_inner_pivot, lateral_middle_pivot (pivot positions) +- lateral_closeness (lateral support spacing) +- whiffle_triangle_closeness (whiffle tree geometry) +- blank_backface_angle (mirror blank geometry) + +### 2.4 Objective Strategy + +**Weighted Sum Minimization**: +$$J(\mathbf{x}) = \sum_{i=1}^{3} w_i \cdot \frac{f_i(\mathbf{x})}{t_i}$$ + +Where: +- $w_i$ = weight for objective $i$ +- $f_i(\mathbf{x})$ = objective value +- $t_i$ = target value (normalization) + +--- + +## 3. Optimization Algorithm + +### 3.1 TPE Configuration + +| Parameter | Value | Description | +|-----------|-------|-------------| +| Algorithm | TPE | Tree-structured Parzen Estimator | +| Sampler | `TPESampler` | Bayesian optimization | +| n_startup_trials | 15 | Random exploration before modeling | +| n_ei_candidates | 150 | Expected improvement candidates | +| multivariate | true | Model parameter correlations | +| Trials | 100 | 40 FEA + neural acceleration | +| Seed | 42 | Reproducibility | + +**TPE Properties**: +- Models $p(x|y Tuple[float, dict]: + # ... simulation and Zernike extraction ... + weighted_obj = compute_weighted_objective(objectives, config) + return weighted_obj, trial_data +``` + +--- + +## 4. Simulation Pipeline + +### 4.1 Trial Execution Flow + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ TRIAL n EXECUTION │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. OPTUNA SAMPLES (TPE) │ +│ whiffle_min = trial.suggest_float("whiffle_min", 35, 55) │ +│ whiffle_outer_to_vertical = trial.suggest_float(..., 68, 80) │ +│ inner_circular_rib_dia = trial.suggest_float(..., 480, 620) │ +│ │ +│ 2. NX PARAMETER UPDATE │ +│ Module: optimization_engine/solve_simulation.py │ +│ Target Part: M1_Blank.prt │ +│ Action: Update expressions with new design values │ +│ │ +│ 3. NX SIMULATION (Nastran SOL 101 - 4 Subcases) │ +│ Module: optimization_engine/solve_simulation.py │ +│ Input: ASSY_M1_assyfem1_sim1.sim │ +│ Subcases: 1=20°, 2=40°, 3=60°, 4=90° zenith │ +│ Output: .dat, .op2, .f06 │ +│ │ +│ 4. ZERNIKE EXTRACTION (Displacement-Based) │ +│ a. Read node coordinates from BDF/DAT │ +│ b. Read Z-displacements from OP2 for each subcase │ +│ c. Compute RELATIVE displacement (subcase - reference) │ +│ d. Convert to WFE: W = 2 * Δδz * 10^6 nm │ +│ e. Fit 50 Zernike coefficients via least-squares │ +│ f. Compute filtered RMS (exclude J1-J4) │ +│ │ +│ 5. OBJECTIVE COMPUTATION │ +│ rel_filtered_rms_40_vs_20 ← Zernike RMS (subcase 2 - 1) │ +│ rel_filtered_rms_60_vs_20 ← Zernike RMS (subcase 3 - 1) │ +│ mfg_90_optician_workload ← Zernike RMS J4+ (subcase 4 - 1) │ +│ │ +│ 6. WEIGHTED SUM │ +│ J = Σ (weight × objective / target) │ +│ │ +│ 7. RETURN TO OPTUNA │ +│ return weighted_objective │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### 4.2 Multi-Subcase Structure + +| Subcase | Zenith Angle | Role | Description | +|---------|--------------|------|-------------| +| 1 | 20° | Reference | Near-zenith baseline orientation | +| 2 | 40° | Target | Mid-elevation performance | +| 3 | 60° | Target | Low-elevation performance | +| 4 | 90° | Polishing | Horizontal (manufacturing reference) | + +--- + +## 5. Result Extraction Methods + +### 5.1 Zernike Extraction (Displacement-Based Subtraction) + +| Attribute | Value | +|-----------|-------| +| **Method** | `extract_zernike_with_relative()` | +| **Location** | `run_optimization.py` (inline) | +| **Geometry Source** | `.dat` (BDF format) | +| **Displacement Source** | `.op2` (OP2 binary) | +| **Output** | 50 Zernike coefficients per subcase | + +**Algorithm (Correct Approach - Matches Original Script)**: + +1. **Load Geometry**: Read node coordinates $(X_i, Y_i)$ from BDF +2. **Load Displacements**: Read $\delta_{z,i}$ from OP2 for each subcase +3. **Compute Relative Displacement** (node-by-node): + $$\Delta\delta_{z,i} = \delta_{z,i}^{target} - \delta_{z,i}^{reference}$$ +4. **Convert to WFE**: + $$W_i = 2 \cdot \Delta\delta_{z,i} \cdot 10^6 \text{ nm}$$ +5. **Fit Zernike** (least-squares on unit disk): + $$\min_{\mathbf{Z}} \| \mathbf{W} - \mathbf{P} \mathbf{Z} \|^2$$ +6. **Compute RMS**: + $$\sigma_{filtered} = \sqrt{\sum_{j=5}^{50} Z_j^2}$$ + +**Critical Implementation Note**: +The relative calculation MUST subtract displacements first, then fit Zernike - NOT subtract Zernike coefficients directly. This matches the original `zernike_Post_Script_NX.py` implementation. + +### 5.2 Code Pattern + +```python +from pyNastran.op2.op2 import OP2 +from pyNastran.bdf.bdf import BDF + +# Read geometry +bdf = BDF() +bdf.read_bdf(str(bdf_path)) +node_geo = {nid: node.get_position() for nid, node in bdf.nodes.items()} + +# Read displacements +op2 = OP2() +op2.read_op2(str(op2_path)) + +# Compute relative displacement (node-by-node) +for i, nid in enumerate(node_ids): + rel_dz = disp_z_target[i] - disp_z_reference[nid] + +# Convert to WFE and fit Zernike +rel_wfe_nm = 2.0 * rel_disp_z * 1e6 +coeffs, R_max = compute_zernike_from_wfe(X, Y, rel_wfe_nm, n_modes=50) +``` + +--- + +## 6. Neural Acceleration (AtomizerField) + +### 6.1 Configuration + +| Setting | Value | Description | +|---------|-------|-------------| +| `enabled` | `true` | Neural surrogate active | +| `model_type` | `ParametricZernikePredictor` | Predicts Zernike coefficients | +| `hidden_channels` | 128 | MLP width | +| `num_layers` | 4 | MLP depth | +| `learning_rate` | 0.001 | Adam optimizer | +| `epochs` | 200 | Training iterations | +| `batch_size` | 8 | Mini-batch size | +| `train_split` | 0.8 | Training fraction | + +### 6.2 Surrogate Model + +**Input**: $\mathbf{x} = [w_{min}, \alpha, D_{rib}]^T \in \mathbb{R}^3$ + +**Output**: $\hat{\mathbf{Z}} \in \mathbb{R}^{200}$ (50 coefficients × 4 subcases) + +**Architecture**: Multi-Layer Perceptron +``` +Input(3) → Linear(128) → ReLU → Linear(128) → ReLU → +Linear(128) → ReLU → Linear(128) → ReLU → Linear(200) +``` + +**Training Objective**: +$$\mathcal{L} = \frac{1}{N} \sum_{i=1}^{N} \| \mathbf{Z}_i - \hat{\mathbf{Z}}_i \|^2$$ + +### 6.3 Training Data Location + +``` +studies/m1_mirror_zernike_optimization/2_results/zernike_surrogate/ +├── checkpoint_best.pt # Best model weights +├── training_history.json # Loss curves +└── validation_metrics.json # R², MAE per coefficient +``` + +### 6.4 Expected Performance + +| Metric | Value | +|--------|-------| +| FEA time per trial | 10-15 min | +| Neural time per trial | ~10 ms | +| Speedup | ~60,000x | +| Expected R² | > 0.95 (after 40 samples) | + +--- + +## 7. Study File Structure + +``` +m1_mirror_zernike_optimization/ +│ +├── 1_setup/ # INPUT CONFIGURATION +│ ├── model/ # NX Model Files (symlinked/referenced) +│ │ └── → C:\Users\Antoine\CADTOMASTE\Atomizer\M1-Gigabit\Latest\ +│ │ ├── ASSY_M1.prt # Top-level assembly +│ │ ├── M1_Blank.prt # Mirror blank (EXPRESSIONS HERE) +│ │ ├── ASSY_M1_assyfem1.afm # Assembly FEM +│ │ ├── ASSY_M1_assyfem1_sim1.sim # Simulation file +│ │ └── assy_m1_assyfem1_sim1-solution_1.op2 # Results +│ │ +│ └── optimization_config.json # Study configuration +│ +├── 2_results/ # OUTPUT (auto-generated) +│ ├── study.db # Optuna SQLite database +│ ├── zernike_surrogate/ # Neural model checkpoints +│ └── reports/ # Generated reports +│ +├── run_optimization.py # Main entry point +├── DASHBOARD.md # Quick reference +└── README.md # This blueprint +``` + +--- + +## 8. Results Location + +After optimization completes, results are stored in `2_results/`: + +| File | Description | Format | +|------|-------------|--------| +| `study.db` | Optuna database with all trials | SQLite | +| `zernike_surrogate/checkpoint_best.pt` | Trained neural model | PyTorch | +| `reports/optimization_report.md` | Full results report | Markdown | + +### 8.1 Results Report Contents + +The generated report will contain: + +1. **Optimization Summary** - Best WFE configurations found +2. **Zernike Analysis** - Coefficient distributions per subcase +3. **Parameter Sensitivity** - Design variable vs WFE relationships +4. **Convergence History** - Weighted objective over trials +5. **Neural Surrogate Performance** - R² per Zernike mode +6. **Recommended Configurations** - Top designs for production + +### 8.2 Zernike-Specific Analysis + +| Mode | Name | Physical Meaning | +|------|------|------------------| +| J1 | Piston | Constant offset (ignored) | +| J2, J3 | Tip/Tilt | Angular misalignment (correctable) | +| J4 | Defocus | Power error (correctable) | +| J5, J6 | Astigmatism | Cylindrical error | +| J7, J8 | Coma | Off-axis aberration | +| J9-J11 | Trefoil, Spherical | Higher-order terms | + +--- + +## 9. Quick Start + +### Staged Workflow (Recommended) + +```bash +cd studies/m1_mirror_zernike_optimization + +# Check current status +python run_optimization.py --status + +# Run FEA trials (builds training data) +python run_optimization.py --run --trials 40 + +# Train neural surrogate +python run_optimization.py --train-surrogate + +# Run neural-accelerated optimization +python run_optimization.py --run --trials 500 --enable-nn +``` + +### Stage Descriptions + +| Stage | Command | Purpose | When to Use | +|-------|---------|---------|-------------| +| **STATUS** | `--status` | Check database, trial count | Anytime | +| **RUN** | `--run --trials N` | Run FEA optimization | Initial exploration | +| **TRAIN** | `--train-surrogate` | Train neural model | After ~40 FEA trials | +| **NEURAL** | `--run --enable-nn` | Fast neural trials | After training | + +### Dashboard Access + +| Dashboard | URL | Purpose | +|-----------|-----|---------| +| **Optuna Dashboard** | `optuna-dashboard sqlite:///2_results/study.db` | Trial history | + +```bash +# Launch Optuna dashboard +cd studies/m1_mirror_zernike_optimization +optuna-dashboard sqlite:///2_results/study.db --port 8081 +# Open http://localhost:8081 +``` + +--- + +## 10. Configuration Reference + +**File**: `1_setup/optimization_config.json` + +| Section | Key | Description | +|---------|-----|-------------| +| `design_variables[]` | 11 parameters | 3 enabled, 8 disabled | +| `objectives[]` | 3 WFE metrics | Relative filtered RMS | +| `zernike_settings.n_modes` | 50 | Zernike polynomial count | +| `zernike_settings.filter_low_orders` | 4 | Exclude J1-J4 | +| `zernike_settings.subcases` | ["1","2","3","4"] | OP2 subcase IDs | +| `zernike_settings.reference_subcase` | "1" | 20° baseline | +| `optimization_settings.n_trials` | 100 | Total FEA trials | +| `surrogate_settings.model_type` | ParametricZernikePredictor | Neural architecture | +| `nx_settings.model_dir` | M1-Gigabit/Latest | NX model location | +| `nx_settings.sim_file` | ASSY_M1_assyfem1_sim1.sim | Simulation file | + +--- + +## 11. Known Issues & Solutions + +### 11.1 Expression Update Failure + +**Issue**: NX journal cannot find expressions in assembly FEM. + +**Cause**: Expressions are in component part `M1_Blank.prt`, not in `ASSY_M1_assyfem1`. + +**Solution**: The `solve_simulation.py` journal now searches for `M1_Blank` part to update expressions. If still failing, verify: +1. `M1_Blank.prt` is loaded in the assembly +2. Expression names match exactly (case-sensitive) +3. Part is not read-only + +### 11.2 Subcase Numbering + +**Issue**: OP2 file uses numeric subcases (1,2,3,4) not angle labels (20,40,60,90). + +**Solution**: Config uses `subcases: ["1","2","3","4"]` with `subcase_labels` mapping. + +--- + +## 12. References + +- **Noll, R.J.** (1976). Zernike polynomials and atmospheric turbulence. *JOSA*. +- **Wilson, R.N.** (2004). *Reflecting Telescope Optics I*. Springer. +- **pyNastran Documentation**: BDF/OP2 parsing for FEA post-processing +- **Optuna Documentation**: TPE sampler for black-box optimization diff --git a/studies/m1_mirror_zernike_optimization/run_optimization.py b/studies/m1_mirror_zernike_optimization/run_optimization.py new file mode 100644 index 00000000..db823132 --- /dev/null +++ b/studies/m1_mirror_zernike_optimization/run_optimization.py @@ -0,0 +1,1377 @@ +#!/usr/bin/env python3 +""" +M1 Mirror Zernike Optimization with Neural Acceleration +======================================================== + +Optimizes telescope primary mirror support structure using Zernike +wavefront error metrics. Supports hybrid FEA/Neural workflow for +accelerated design space exploration. + +Objectives: +- Minimize relative filtered RMS at 40 deg vs 20 deg +- Minimize relative filtered RMS at 60 deg vs 20 deg +- Minimize optician workload at 90 deg (polishing orientation) + +Design Variables (11 total, selectively enabled): +- Whiffle tree parameters (min, outer_to_vertical, triangle_closeness) +- Lateral support parameters (angles, pivots, closeness) +- Mirror blank parameters (backface_angle, inner_circular_rib_dia) + +Usage: + python run_optimization.py --run --trials 40 # FEA only + python run_optimization.py --run --trials 500 --enable-nn # With neural + python run_optimization.py --train-surrogate # Train NN from data + python run_optimization.py --status # Check progress +""" + +import sys +import json +import time +import argparse +from pathlib import Path +from datetime import datetime +from typing import Dict, Any, List, Optional, Tuple +import numpy as np + +# Add parent directories to path +sys.path.insert(0, str(Path(__file__).parent.parent.parent)) + +import optuna +from optuna.samplers import TPESampler, NSGAIISampler + +# Atomizer imports +from optimization_engine.nx_solver import NXSolver +# Note: We use our own Zernike implementation with displacement-based subtraction +# instead of the generic ZernikeExtractor to match the original zernike_Post_Script_NX.py + + +# ============================================================================== +# Configuration +# ============================================================================== + +STUDY_DIR = Path(__file__).parent +SETUP_DIR = STUDY_DIR / "1_setup" +RESULTS_DIR = STUDY_DIR / "2_results" +MODEL_DIR = SETUP_DIR / "model" +CONFIG_PATH = SETUP_DIR / "optimization_config.json" + +# Ensure directories exist +RESULTS_DIR.mkdir(exist_ok=True) + + +def load_config() -> Dict[str, Any]: + """Load optimization configuration.""" + with open(CONFIG_PATH, 'r') as f: + return json.load(f) + + +# ============================================================================== +# Zernike Extraction Utilities +# ============================================================================== + +# Displacement unit conversion: mm -> nm for WFE = 2 * Disp_Z +NM_PER_MM = 1e6 # 1 mm = 1e6 nm + +def extract_displacements_from_op2( + op2_path: Path, + subcases: List[str] = ["1", "2", "3", "4"] +) -> Dict[str, Dict[str, np.ndarray]]: + """ + Extract raw displacement data from OP2 for all subcases. + + Returns dict with structure: + { + '1': {'node_ids': array, 'disp_z': array, 'x': array, 'y': array}, + '2': {...}, + ... + } + """ + from pyNastran.op2.op2 import OP2 + + op2 = OP2() + op2.read_op2(str(op2_path)) + + if not op2.displacements: + raise RuntimeError("No displacement subcases found in OP2.") + + results = {} + + for key, darr in op2.displacements.items(): + data = darr.data + dmat = data[0] if data.ndim == 3 else (data if data.ndim == 2 else None) + if dmat is None: + continue + + ngt = darr.node_gridtype.astype(int) + node_ids = ngt if ngt.ndim == 1 else ngt[:, 0] + + # Try to identify subcase by isubcase + isub = getattr(darr, 'isubcase', None) + isub_str = str(int(isub)) if isinstance(isub, int) else None + + if isub_str and isub_str in subcases: + results[isub_str] = { + 'node_ids': node_ids.astype(int), + 'disp_z': dmat[:, 2].astype(np.float64), # Z displacement + } + + return results + + +def compute_zernike_from_wfe( + X: np.ndarray, + Y: np.ndarray, + W_nm: np.ndarray, + n_modes: int = 50 +) -> Tuple[np.ndarray, float]: + """ + Compute Zernike coefficients from WFE surface data. + + Uses least-squares fit on unit disk. + + Returns (coefficients, R_max) + """ + from math import factorial + + def noll_indices(j: int): + if j < 1: + raise ValueError("Noll index j must be >= 1") + count = 0 + n = 0 + while True: + if n == 0: + ms = [0] + elif n % 2 == 0: + ms = [0] + [m for k in range(1, n//2 + 1) for m in (-2*k, 2*k)] + else: + ms = [m for k in range(0, (n+1)//2) for m in (-(2*k+1), (2*k+1))] + for m in ms: + count += 1 + if count == j: + return n, m + n += 1 + + def zernike_noll(j, r, th): + n, m = noll_indices(j) + R = np.zeros_like(r) + for s in range((n - abs(m)) // 2 + 1): + c = ((-1)**s * factorial(n - s) / + (factorial(s) * + factorial((n + abs(m)) // 2 - s) * + factorial((n - abs(m)) // 2 - s))) + R += c * r**(n - 2*s) + if m == 0: + return R + return R * (np.cos(m * th) if m > 0 else np.sin(-m * th)) + + # Center and normalize coordinates + Xc = X - np.mean(X) + Yc = Y - np.mean(Y) + R_max = float(np.max(np.hypot(Xc, Yc))) + + r = np.hypot(Xc / R_max, Yc / R_max).astype(np.float32) + th = np.arctan2(Yc, Xc).astype(np.float32) + + # Mask: only use points inside unit disk + mask = (r <= 1.0) & ~np.isnan(W_nm) + if not np.any(mask): + raise RuntimeError("No valid points inside unit disk.") + + idx = np.nonzero(mask)[0] + + # Build design matrix in chunks + m = int(n_modes) + G = np.zeros((m, m), dtype=np.float64) # Z^T Z + h = np.zeros((m,), dtype=np.float64) # Z^T v + v = W_nm.astype(np.float64) + + chunk_size = 100000 + for start in range(0, len(idx), chunk_size): + sl = idx[start:start + chunk_size] + r_b, th_b, v_b = r[sl], th[sl], v[sl] + Zb = np.column_stack([zernike_noll(j, r_b, th_b).astype(np.float32) + for j in range(1, m + 1)]) + G += (Zb.T @ Zb).astype(np.float64) + h += (Zb.T @ v_b).astype(np.float64) + + try: + coeffs = np.linalg.solve(G, h) + except np.linalg.LinAlgError: + coeffs = np.linalg.lstsq(G, h, rcond=None)[0] + + return coeffs, R_max + + +def compute_rms_from_surface( + X: np.ndarray, + Y: np.ndarray, + W_nm: np.ndarray, + coeffs: np.ndarray, + R_max: float, + filter_low_orders: int = 4 +) -> Tuple[float, float]: + """ + Compute global and filtered RMS from actual WFE surface data. + + This is the CORRECT approach matching zernike_Post_Script_NX.py: + - Global RMS = sqrt(mean(W^2)) - RMS of the actual WFE values + - Filtered RMS = sqrt(mean(W_residual^2)) where W_residual = W - low_order_fit + + The low-order modes (piston, tip, tilt, defocus) are reconstructed from + coefficients and subtracted from the original WFE to get the residual. + + Returns (global_rms, filtered_rms) + """ + from math import factorial + + def noll_indices(j: int): + if j < 1: + raise ValueError("Noll index j must be >= 1") + count = 0 + n = 0 + while True: + if n == 0: + ms = [0] + elif n % 2 == 0: + ms = [0] + [m for k in range(1, n//2 + 1) for m in (-2*k, 2*k)] + else: + ms = [m for k in range(0, (n+1)//2) for m in (-(2*k+1), (2*k+1))] + for m in ms: + count += 1 + if count == j: + return n, m + n += 1 + + def zernike_noll(j, r, th): + n, m = noll_indices(j) + R = np.zeros_like(r) + for s in range((n - abs(m)) // 2 + 1): + c = ((-1)**s * factorial(n - s) / + (factorial(s) * + factorial((n + abs(m)) // 2 - s) * + factorial((n - abs(m)) // 2 - s))) + R += c * r**(n - 2*s) + if m == 0: + return R + return R * (np.cos(m * th) if m > 0 else np.sin(-m * th)) + + # Center and normalize coordinates + Xc = X - np.mean(X) + Yc = Y - np.mean(Y) + r = np.hypot(Xc / R_max, Yc / R_max) + th = np.arctan2(Yc, Xc) + + # Build Zernike basis matrix for low-order modes + n_modes = len(coeffs) + Z = np.column_stack([zernike_noll(j, r, th) for j in range(1, n_modes + 1)]) + + # Compute residual by removing low-order modes from original WFE + # W_res_filt = W_nm - Z[:, :filter_low_orders] @ coeffs[:filter_low_orders] + W_res_filt = W_nm - Z[:, :filter_low_orders].dot(coeffs[:filter_low_orders]) + + # RMS = sqrt(mean(W^2)) - this is the CORRECT formula + global_rms = float(np.sqrt((W_nm ** 2).mean())) + filtered_rms = float(np.sqrt((W_res_filt ** 2).mean())) + + return global_rms, filtered_rms + + +def compute_rms_filter_j1to3( + X: np.ndarray, + Y: np.ndarray, + W_nm: np.ndarray, + coeffs: np.ndarray, + R_max: float +) -> float: + """ + Compute RMS with only J1-J3 removed (keeps defocus J4). + This is the "optician workload" metric for manufacturing. + + Returns filtered RMS in nm. + """ + from math import factorial + + def noll_indices(j: int): + if j < 1: + raise ValueError("Noll index j must be >= 1") + count = 0 + n = 0 + while True: + if n == 0: + ms = [0] + elif n % 2 == 0: + ms = [0] + [m for k in range(1, n//2 + 1) for m in (-2*k, 2*k)] + else: + ms = [m for k in range(0, (n+1)//2) for m in (-(2*k+1), (2*k+1))] + for m in ms: + count += 1 + if count == j: + return n, m + n += 1 + + def zernike_noll(j, r, th): + n, m = noll_indices(j) + R = np.zeros_like(r) + for s in range((n - abs(m)) // 2 + 1): + c = ((-1)**s * factorial(n - s) / + (factorial(s) * + factorial((n + abs(m)) // 2 - s) * + factorial((n - abs(m)) // 2 - s))) + R += c * r**(n - 2*s) + if m == 0: + return R + return R * (np.cos(m * th) if m > 0 else np.sin(-m * th)) + + # Center and normalize coordinates + Xc = X - np.mean(X) + Yc = Y - np.mean(Y) + r = np.hypot(Xc / R_max, Yc / R_max) + th = np.arctan2(Yc, Xc) + + # Build Zernike basis matrix for first 3 modes only + Z = np.column_stack([zernike_noll(j, r, th) for j in range(1, 4)]) + + # Remove only J1-J3 (piston, tip, tilt) - keeps defocus + W_res_filt3 = W_nm - Z.dot(coeffs[:3]) + + return float(np.sqrt((W_res_filt3 ** 2).mean())) + + +def extract_zernike_with_relative( + op2_path: Path, + bdf_path: Optional[Path] = None, + n_modes: int = 50, + subcases: List[str] = ["1", "2", "3", "4"], + subcase_labels: Dict[str, str] = {"1": "20deg", "2": "40deg", "3": "60deg", "4": "90deg"}, + reference_subcase: str = "1" +) -> Dict[str, Dict[str, Any]]: + """ + Extract Zernike coefficients using displacement-based subtraction for relative metrics. + + This is the CORRECT approach matching the original zernike_Post_Script_NX.py: + 1. Subtract displacements node-by-node between subcases + 2. Convert relative displacement to WFE (nm) + 3. Fit Zernike to the relative WFE surface + 4. Compute RMS from that fit + + Returns dict with structure: + { + '1': { + 'coefficients': [50 values], # Absolute + 'global_rms_nm': float, + 'filtered_rms_nm': float, + 'relative_coefficients': None, # Reference has no relative + 'relative_global_rms_nm': None, + 'relative_filtered_rms_nm': None, + }, + '2': { + 'coefficients': [50 values], # Absolute + 'relative_coefficients': [50 values], # Relative to reference + 'relative_global_rms_nm': float, + 'relative_filtered_rms_nm': float, + }, + ... + } + """ + from pyNastran.op2.op2 import OP2 + from pyNastran.bdf.bdf import BDF + + # Read geometry for node coordinates + if bdf_path is None: + # Try to find matching BDF/DAT file + folder = op2_path.parent + base = op2_path.stem + for ext in [".dat", ".bdf"]: + candidate = folder / (base + ext) + if candidate.exists(): + bdf_path = candidate + break + if bdf_path is None: + # Search for any BDF/DAT in folder + for f in folder.iterdir(): + if f.suffix.lower() in [".dat", ".bdf"]: + bdf_path = f + break + + if bdf_path is None or not bdf_path.exists(): + raise FileNotFoundError(f"No BDF/DAT file found for geometry. OP2: {op2_path}") + + print(f"[ZERNIKE] Reading geometry from: {bdf_path.name}") + bdf = BDF() + bdf.read_bdf(str(bdf_path)) + node_geo = {int(nid): node.get_position() for nid, node in bdf.nodes.items()} + + # Read displacements from OP2 + print(f"[ZERNIKE] Reading displacements from: {op2_path.name}") + op2 = OP2() + op2.read_op2(str(op2_path)) + + if not op2.displacements: + raise RuntimeError("No displacement subcases found in OP2.") + + # Extract displacement data for each subcase + disp_data = {} + for key, darr in op2.displacements.items(): + data = darr.data + dmat = data[0] if data.ndim == 3 else (data if data.ndim == 2 else None) + if dmat is None: + continue + + ngt = darr.node_gridtype.astype(int) + node_ids = ngt if ngt.ndim == 1 else ngt[:, 0] + + isub = getattr(darr, 'isubcase', None) + isub_str = str(int(isub)) if isinstance(isub, int) else None + + if isub_str and isub_str in subcases: + # Build dataframe-like structure with coordinates + X = np.array([node_geo.get(int(nid), (np.nan, np.nan, np.nan))[0] for nid in node_ids]) + Y = np.array([node_geo.get(int(nid), (np.nan, np.nan, np.nan))[1] for nid in node_ids]) + disp_z = dmat[:, 2].astype(np.float64) + + disp_data[isub_str] = { + 'node_ids': node_ids.astype(int), + 'X': X, + 'Y': Y, + 'disp_z': disp_z, + } + print(f"[ZERNIKE] Extracted subcase {isub_str} ({subcase_labels.get(isub_str, '?')}): {len(node_ids)} nodes") + + if reference_subcase not in disp_data: + raise ValueError(f"Reference subcase {reference_subcase} not found in OP2") + + # Reference data + ref = disp_data[reference_subcase] + ref_node_set = set(ref['node_ids']) + + results = {} + + for subcase in subcases: + if subcase not in disp_data: + print(f"[WARN] Subcase {subcase} not found in OP2") + results[subcase] = None + continue + + data = disp_data[subcase] + X = data['X'] + Y = data['Y'] + disp_z = data['disp_z'] + node_ids = data['node_ids'] + + # Mask out nodes with invalid geometry + valid_mask = ~np.isnan(X) & ~np.isnan(Y) + X = X[valid_mask] + Y = Y[valid_mask] + disp_z = disp_z[valid_mask] + node_ids = node_ids[valid_mask] + + # Absolute WFE = 2 * Disp_Z * scale + wfe_abs_nm = 2.0 * disp_z * NM_PER_MM + + # Fit Zernike to absolute surface + coeffs_abs, R_max = compute_zernike_from_wfe(X, Y, wfe_abs_nm, n_modes) + # Use CORRECT RMS calculation: sqrt(mean(W^2)) from actual surface + global_rms_abs, filtered_rms_abs = compute_rms_from_surface( + X, Y, wfe_abs_nm, coeffs_abs, R_max, filter_low_orders=4 + ) + + result = { + 'coefficients': coeffs_abs.tolist(), + 'global_rms_nm': global_rms_abs, + 'filtered_rms_nm': filtered_rms_abs, + 'relative_coefficients': None, + 'relative_global_rms_nm': None, + 'relative_filtered_rms_nm': None, + # Store surface data for optician workload calculation + 'X': X, + 'Y': Y, + 'wfe_nm': wfe_abs_nm, + 'R_max': R_max, + } + + # Compute relative (vs reference) if not the reference subcase + if subcase != reference_subcase: + # Find common nodes + common_nodes = [] + ref_disp_map = dict(zip(ref['node_ids'], ref['disp_z'])) + + rel_disp_z = [] + rel_X = [] + rel_Y = [] + + for i, nid in enumerate(node_ids): + if nid in ref_disp_map: + # SUBTRACT DISPLACEMENT NODE-BY-NODE (the correct approach!) + rel_dz = disp_z[i] - ref_disp_map[nid] + rel_disp_z.append(rel_dz) + rel_X.append(X[i]) + rel_Y.append(Y[i]) + + rel_disp_z = np.array(rel_disp_z) + rel_X = np.array(rel_X) + rel_Y = np.array(rel_Y) + + # Convert relative displacement to WFE + rel_wfe_nm = 2.0 * rel_disp_z * NM_PER_MM + + # Fit Zernike to relative surface + coeffs_rel, R_max_rel = compute_zernike_from_wfe(rel_X, rel_Y, rel_wfe_nm, n_modes) + # Use CORRECT RMS calculation: sqrt(mean(W^2)) from actual surface + global_rms_rel, filtered_rms_rel = compute_rms_from_surface( + rel_X, rel_Y, rel_wfe_nm, coeffs_rel, R_max_rel, filter_low_orders=4 + ) + + result['relative_coefficients'] = coeffs_rel.tolist() + result['relative_global_rms_nm'] = global_rms_rel + result['relative_filtered_rms_nm'] = filtered_rms_rel + # Store relative surface data for optician workload + result['rel_X'] = rel_X + result['rel_Y'] = rel_Y + result['rel_wfe_nm'] = rel_wfe_nm + result['rel_R_max'] = R_max_rel + + label = subcase_labels.get(subcase, subcase) + ref_label = subcase_labels.get(reference_subcase, reference_subcase) + print(f"[ZERNIKE] {label} vs {ref_label}: Global RMS = {global_rms_rel:.2f} nm, Filtered RMS = {filtered_rms_rel:.2f} nm") + + results[subcase] = result + + return results + + +def compute_objectives_from_zernike( + zernike_data: Dict[str, Dict[str, Any]], + config: Dict[str, Any] +) -> Dict[str, float]: + """ + Compute optimization objectives from Zernike extraction results. + + Uses the RELATIVE metrics (displacement-based subtraction) for the main objectives. + + Returns dict with objective values. + """ + objectives = {} + + zernike_settings = config.get('zernike_settings', {}) + reference_subcase = zernike_settings.get('reference_subcase', '2') + polishing_subcase = zernike_settings.get('polishing_subcase', '1') + + # Map subcase IDs based on config: 1=90deg, 2=20deg(ref), 3=40deg, 4=60deg + # Find subcases by their labels + subcase_labels = zernike_settings.get('subcase_labels', {}) + label_to_subcase = {v: k for k, v in subcase_labels.items()} + + subcase_40 = label_to_subcase.get('40deg', '3') + subcase_60 = label_to_subcase.get('60deg', '4') + subcase_90 = label_to_subcase.get('90deg', '1') + + # Objective 1: Relative filtered RMS at 40 deg vs 20 deg (reference) + data_40 = zernike_data.get(subcase_40) + if data_40 and data_40.get('relative_filtered_rms_nm') is not None: + objectives['rel_filtered_rms_40_vs_20'] = float(data_40['relative_filtered_rms_nm']) + else: + objectives['rel_filtered_rms_40_vs_20'] = float('inf') + + # Objective 2: Relative filtered RMS at 60 deg vs 20 deg (reference) + data_60 = zernike_data.get(subcase_60) + if data_60 and data_60.get('relative_filtered_rms_nm') is not None: + objectives['rel_filtered_rms_60_vs_20'] = float(data_60['relative_filtered_rms_nm']) + else: + objectives['rel_filtered_rms_60_vs_20'] = float('inf') + + # Objective 3: Optician workload at 90 deg (polishing subcase) + # This is the relative (90 deg - 20 deg) with filter J1-J3 removed (keep defocus) + # CORRECT: Use surface-based RMS calculation, not coefficient sum + data_90 = zernike_data.get(subcase_90) + if (data_90 and data_90.get('relative_coefficients') is not None + and data_90.get('rel_X') is not None): + rel_coeffs_90 = np.array(data_90['relative_coefficients']) + rel_X = data_90['rel_X'] + rel_Y = data_90['rel_Y'] + rel_wfe = data_90['rel_wfe_nm'] + rel_R_max = data_90['rel_R_max'] + # Use CORRECT surface-based RMS with J1-J3 filter (keeps defocus) + rms_filter_j1to3 = compute_rms_filter_j1to3( + rel_X, rel_Y, rel_wfe, rel_coeffs_90, rel_R_max + ) + objectives['mfg_90_optician_workload'] = float(rms_filter_j1to3) + else: + objectives['mfg_90_optician_workload'] = float('inf') + + return objectives + + +def compute_weighted_objective( + objectives: Dict[str, float], + config: Dict[str, Any] +) -> float: + """ + Compute weighted sum objective from individual metrics. + """ + obj_configs = {o['name']: o for o in config.get('objectives', [])} + + total = 0.0 + total_weight = 0.0 + + for name, value in objectives.items(): + if name in obj_configs: + obj_config = obj_configs[name] + weight = obj_config.get('weight', 1.0) + target = obj_config.get('target', 1.0) + + # Normalize by target + if target and target > 0: + normalized = value / target + else: + normalized = value + + total += weight * normalized + total_weight += weight + + if total_weight > 0: + return total / total_weight + return total + + +# ============================================================================== +# FEA Objective Function +# ============================================================================== + +def fea_objective( + trial: optuna.Trial, + config: Dict[str, Any], + nx_solver: NXSolver +) -> Tuple[float, Dict[str, Any]]: + """ + FEA-based objective function. + + 1. Suggest design variables + 2. Update NX model + 3. Run Nastran solve + 4. Extract Zernike coefficients + 5. Compute objectives + + Returns (weighted_objective, trial_data) + """ + # Get enabled design variables + design_vars = {} + for var in config['design_variables']: + if var.get('enabled', False): + value = trial.suggest_float( + var['name'], + var['min'], + var['max'] + ) + design_vars[var['name']] = value + else: + # Use baseline for disabled variables + design_vars[var['name']] = var.get('baseline', (var['min'] + var['max']) / 2) + + print(f"\n[Trial {trial.number}] Design variables:") + for name, value in design_vars.items(): + print(f" {name}: {value:.4f}") + + # Prepare expression updates (map to expression names) + t_start = time.time() + print(f"[Trial {trial.number}] Running NX simulation...") + + try: + expressions = { + var['expression_name']: design_vars[var['name']] + for var in config['design_variables'] + } + + # Get file paths + nx_settings = config.get('nx_settings', {}) + model_dir = Path(nx_settings.get('model_dir', str(MODEL_DIR))) + sim_file = model_dir / nx_settings.get('sim_file', 'ASSY_M1_assyfem1_sim1.sim') + solution_name = nx_settings.get('solution_name', 'Solution 1') + + # Run simulation via NXSolver (handles expressions + solve in one call) + result = nx_solver.run_simulation( + sim_file=sim_file, + working_dir=model_dir, + expression_updates=expressions, + solution_name=solution_name, + cleanup=False # Keep OP2 for Zernike extraction + ) + + if not result['success']: + error_msg = result.get('error', 'Unknown error') + print(f"[Trial {trial.number}] ERROR in solve: {error_msg}") + raise optuna.TrialPruned(f"Solve failed: {error_msg}") + + op2_path = Path(result['op2_file']) + + except optuna.TrialPruned: + raise + except Exception as e: + print(f"[Trial {trial.number}] ERROR in simulation: {e}") + raise optuna.TrialPruned(f"Simulation failed: {e}") + + solve_time = time.time() - t_start + print(f"[Trial {trial.number}] Solve completed in {solve_time:.1f}s") + + # Extract Zernike coefficients using displacement-based subtraction + print(f"[Trial {trial.number}] Extracting Zernike coefficients (displacement-based)...") + try: + zernike_settings = config.get('zernike_settings', {}) + subcases = zernike_settings.get('subcases', ['1', '2', '3', '4']) + subcase_labels = zernike_settings.get('subcase_labels', {"1": "20deg", "2": "40deg", "3": "60deg", "4": "90deg"}) + reference_subcase = zernike_settings.get('reference_subcase', '1') + n_modes = zernike_settings.get('n_modes', 50) + + zernike_data = extract_zernike_with_relative( + op2_path, + bdf_path=None, # Will auto-detect + n_modes=n_modes, + subcases=subcases, + subcase_labels=subcase_labels, + reference_subcase=reference_subcase + ) + except Exception as e: + print(f"[Trial {trial.number}] ERROR extracting Zernike: {e}") + import traceback + traceback.print_exc() + raise optuna.TrialPruned(f"Zernike extraction failed: {e}") + + # Compute objectives + objectives = compute_objectives_from_zernike(zernike_data, config) + weighted_obj = compute_weighted_objective(objectives, config) + + print(f"[Trial {trial.number}] Objectives:") + for name, value in objectives.items(): + print(f" {name}: {value:.2f} nm") + print(f"[Trial {trial.number}] Weighted objective: {weighted_obj:.4f}") + + # Store trial data + trial_data = { + 'design_vars': design_vars, + 'objectives': objectives, + 'weighted_objective': weighted_obj, + 'solve_time': solve_time, + 'zernike_coefficients': { + subcase: data.get('coefficients') if data else None + for subcase, data in zernike_data.items() + }, + 'zernike_relative_coefficients': { + subcase: data.get('relative_coefficients') if data else None + for subcase, data in zernike_data.items() + }, + 'source': 'FEA' + } + + # Store in trial user attributes + trial.set_user_attr('objectives', objectives) + trial.set_user_attr('solve_time', solve_time) + trial.set_user_attr('source', 'FEA') + + # Store Zernike data for neural training (absolute coefficients) + for subcase, data in zernike_data.items(): + if data and 'coefficients' in data: + trial.set_user_attr(f'zernike_coeffs_{subcase}', data['coefficients']) + # Also store relative coefficients + if data and data.get('relative_coefficients'): + trial.set_user_attr(f'zernike_rel_coeffs_{subcase}', data['relative_coefficients']) + + return weighted_obj, trial_data + + +# ============================================================================== +# Neural Surrogate +# ============================================================================== + +class ZernikeSurrogate: + """ + Neural surrogate for Zernike coefficient prediction. + + Predicts 50 Zernike coefficients for each of 4 subcases (200 outputs) + from design variables input. + """ + + def __init__(self, model_path: Optional[Path] = None): + self.model = None + self.model_path = model_path + self.design_var_names = [] + self.design_mean = None + self.design_std = None + self.output_mean = None + self.output_std = None + self.subcases = ['20', '40', '60', '90'] + self.n_modes = 50 + + if model_path and model_path.exists(): + self.load(model_path) + + def load(self, model_path: Path): + """Load trained model from checkpoint.""" + import torch + + checkpoint = torch.load(model_path, map_location='cpu') + + self.design_var_names = checkpoint.get('design_var_names', []) + self.design_mean = torch.tensor(checkpoint['normalization']['design_mean']) + self.design_std = torch.tensor(checkpoint['normalization']['design_std']) + self.output_mean = torch.tensor(checkpoint['normalization']['output_mean']) + self.output_std = torch.tensor(checkpoint['normalization']['output_std']) + self.subcases = checkpoint.get('subcases', ['20', '40', '60', '90']) + self.n_modes = checkpoint.get('n_modes', 50) + + # Build model architecture + config = checkpoint['config'] + self.model = self._build_model(config) + self.model.load_state_dict(checkpoint['model_state_dict']) + self.model.eval() + + print(f"[Surrogate] Loaded model from {model_path}") + print(f"[Surrogate] Design vars: {self.design_var_names}") + print(f"[Surrogate] Outputs: {len(self.subcases)} subcases x {self.n_modes} coefficients") + + def _build_model(self, config: Dict[str, Any]): + """Build MLP model for Zernike prediction.""" + import torch + import torch.nn as nn + + input_dim = len(self.design_var_names) + output_dim = len(self.subcases) * self.n_modes + hidden_dim = config.get('hidden_channels', 128) + num_layers = config.get('num_layers', 4) + + layers = [] + layers.append(nn.Linear(input_dim, hidden_dim)) + layers.append(nn.ReLU()) + + for _ in range(num_layers - 1): + layers.append(nn.Linear(hidden_dim, hidden_dim)) + layers.append(nn.ReLU()) + + layers.append(nn.Linear(hidden_dim, output_dim)) + + return nn.Sequential(*layers) + + def predict(self, design_vars: Dict[str, float]) -> Dict[str, np.ndarray]: + """ + Predict Zernike coefficients for all subcases. + + Returns dict mapping subcase to coefficient array. + """ + import torch + + if self.model is None: + raise RuntimeError("Model not loaded") + + # Build input tensor + input_values = [design_vars.get(name, 0.0) for name in self.design_var_names] + x = torch.tensor(input_values, dtype=torch.float32) + + # Normalize + x_norm = (x - self.design_mean) / self.design_std + + # Predict + with torch.no_grad(): + y_norm = self.model(x_norm.unsqueeze(0)) + + # Denormalize + y = y_norm * self.output_std + self.output_mean + y = y.squeeze(0).numpy() + + # Split into subcases + results = {} + for i, subcase in enumerate(self.subcases): + start = i * self.n_modes + end = start + self.n_modes + results[subcase] = y[start:end] + + return results + + def predict_objectives( + self, + design_vars: Dict[str, float], + config: Dict[str, Any] + ) -> Dict[str, float]: + """Predict objectives from design variables.""" + coeffs = self.predict(design_vars) + + # Convert to format expected by compute_objectives_from_zernike + zernike_data = { + subcase: {'coefficients': coeffs[subcase].tolist()} + for subcase in self.subcases + } + + return compute_objectives_from_zernike(zernike_data, config) + + +def neural_objective( + trial: optuna.Trial, + config: Dict[str, Any], + surrogate: ZernikeSurrogate +) -> float: + """Neural surrogate objective function.""" + + # Suggest design variables + design_vars = {} + for var in config['design_variables']: + if var.get('enabled', False): + value = trial.suggest_float(var['name'], var['min'], var['max']) + design_vars[var['name']] = value + else: + design_vars[var['name']] = var.get('baseline', (var['min'] + var['max']) / 2) + + # Predict with surrogate + t_start = time.time() + objectives = surrogate.predict_objectives(design_vars, config) + inference_time = (time.time() - t_start) * 1000 + + weighted_obj = compute_weighted_objective(objectives, config) + + # Store trial attributes + trial.set_user_attr('objectives', objectives) + trial.set_user_attr('inference_time_ms', inference_time) + trial.set_user_attr('source', 'Neural') + trial.set_user_attr('neural_predicted', True) + + return weighted_obj + + +# ============================================================================== +# Training +# ============================================================================== + +def train_zernike_surrogate(config: Dict[str, Any]) -> Path: + """ + Train neural surrogate on FEA trial data. + + Extracts Zernike coefficients from completed trials and trains + an MLP to predict them from design variables. + """ + import torch + import torch.nn as nn + from torch.utils.data import DataLoader, TensorDataset + + print("\n" + "="*60) + print("Training Zernike Surrogate Model") + print("="*60) + + # Load study + db_path = RESULTS_DIR / "study.db" + if not db_path.exists(): + raise FileNotFoundError(f"No study database found at {db_path}") + + storage = optuna.storages.RDBStorage(f'sqlite:///{db_path}') + study = optuna.load_study( + study_name=config['study_name'], + storage=storage + ) + + # Get FEA trials with Zernike data + fea_trials = [ + t for t in study.trials + if t.state == optuna.trial.TrialState.COMPLETE + and t.user_attrs.get('source') == 'FEA' + and t.user_attrs.get('zernike_coeffs_20') is not None + ] + + print(f"Found {len(fea_trials)} FEA trials with Zernike data") + + if len(fea_trials) < 10: + raise ValueError(f"Need at least 10 FEA trials for training, have {len(fea_trials)}") + + # Get design variable names (enabled ones) + design_var_names = [ + var['name'] for var in config['design_variables'] + if var.get('enabled', False) + ] + + subcases = config.get('zernike_settings', {}).get('subcases', ['20', '40', '60', '90']) + n_modes = config.get('zernike_settings', {}).get('n_modes', 50) + + # Build training data + X = [] + Y = [] + + for trial in fea_trials: + # Design variables + x = [trial.params.get(name, 0.0) for name in design_var_names] + X.append(x) + + # Zernike coefficients for all subcases + y = [] + for subcase in subcases: + coeffs = trial.user_attrs.get(f'zernike_coeffs_{subcase}', [0.0] * n_modes) + y.extend(coeffs[:n_modes]) + Y.append(y) + + X = np.array(X, dtype=np.float32) + Y = np.array(Y, dtype=np.float32) + + print(f"Training data shape: X={X.shape}, Y={Y.shape}") + + # Normalize + X_mean = X.mean(axis=0) + X_std = X.std(axis=0) + 1e-8 + Y_mean = Y.mean(axis=0) + Y_std = Y.std(axis=0) + 1e-8 + + X_norm = (X - X_mean) / X_std + Y_norm = (Y - Y_mean) / Y_std + + # Train/val split + n_train = int(0.8 * len(X)) + indices = np.random.permutation(len(X)) + train_idx = indices[:n_train] + val_idx = indices[n_train:] + + X_train = torch.tensor(X_norm[train_idx]) + Y_train = torch.tensor(Y_norm[train_idx]) + X_val = torch.tensor(X_norm[val_idx]) + Y_val = torch.tensor(Y_norm[val_idx]) + + # Build model + train_config = config.get('surrogate_settings', {}).get('training_config', {}) + hidden_dim = train_config.get('hidden_channels', 128) + num_layers = train_config.get('num_layers', 4) + + input_dim = len(design_var_names) + output_dim = len(subcases) * n_modes + + layers = [] + layers.append(nn.Linear(input_dim, hidden_dim)) + layers.append(nn.ReLU()) + for _ in range(num_layers - 1): + layers.append(nn.Linear(hidden_dim, hidden_dim)) + layers.append(nn.ReLU()) + layers.append(nn.Linear(hidden_dim, output_dim)) + + model = nn.Sequential(*layers) + + # Training + optimizer = torch.optim.Adam( + model.parameters(), + lr=train_config.get('learning_rate', 0.001) + ) + criterion = nn.MSELoss() + + epochs = train_config.get('epochs', 200) + batch_size = train_config.get('batch_size', 8) + + train_dataset = TensorDataset(X_train, Y_train) + train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) + + best_val_loss = float('inf') + best_state = None + + print(f"\nTraining for {epochs} epochs...") + + for epoch in range(epochs): + # Train + model.train() + train_loss = 0.0 + for xb, yb in train_loader: + optimizer.zero_grad() + pred = model(xb) + loss = criterion(pred, yb) + loss.backward() + optimizer.step() + train_loss += loss.item() + train_loss /= len(train_loader) + + # Validate + model.eval() + with torch.no_grad(): + val_pred = model(X_val) + val_loss = criterion(val_pred, Y_val).item() + + if val_loss < best_val_loss: + best_val_loss = val_loss + best_state = model.state_dict().copy() + + if (epoch + 1) % 20 == 0: + print(f" Epoch {epoch+1}/{epochs}: train_loss={train_loss:.6f}, val_loss={val_loss:.6f}") + + print(f"\nBest validation loss: {best_val_loss:.6f}") + + # Save model + model.load_state_dict(best_state) + + model_dir = RESULTS_DIR / "zernike_surrogate" + model_dir.mkdir(exist_ok=True) + model_path = model_dir / "checkpoint_best.pt" + + checkpoint = { + 'model_state_dict': best_state, + 'config': train_config, + 'design_var_names': design_var_names, + 'subcases': subcases, + 'n_modes': n_modes, + 'normalization': { + 'design_mean': X_mean.tolist(), + 'design_std': X_std.tolist(), + 'output_mean': Y_mean.tolist(), + 'output_std': Y_std.tolist(), + }, + 'best_val_loss': best_val_loss, + 'n_training_samples': len(fea_trials), + } + + torch.save(checkpoint, model_path) + print(f"\nModel saved to: {model_path}") + + return model_path + + +# ============================================================================== +# Main Optimization +# ============================================================================== + +def run_optimization( + n_trials: int = 100, + enable_nn: bool = False, + resume: bool = True +): + """ + Run the optimization study. + + Args: + n_trials: Number of trials to run + enable_nn: Whether to use neural surrogate + resume: Whether to resume existing study + """ + config = load_config() + study_name = config['study_name'] + + print("\n" + "="*70) + print(f"M1 MIRROR ZERNIKE OPTIMIZATION") + print("="*70) + print(f"Study: {study_name}") + print(f"Trials: {n_trials}") + print(f"Neural: {'Enabled' if enable_nn else 'Disabled'}") + print("="*70) + + # Print enabled design variables + enabled_vars = [v for v in config['design_variables'] if v.get('enabled')] + print(f"\nEnabled design variables ({len(enabled_vars)}):") + for var in enabled_vars: + print(f" {var['name']}: [{var['min']}, {var['max']}] {var.get('units', '')}") + + # Print objectives + print(f"\nObjectives:") + for obj in config['objectives']: + print(f" {obj['name']}: weight={obj.get('weight', 1)}, target={obj.get('target', 'N/A')} {obj.get('units', '')}") + + # Setup storage + db_path = RESULTS_DIR / "study.db" + storage = optuna.storages.RDBStorage( + f'sqlite:///{db_path}', + heartbeat_interval=60, + failed_trial_callback=optuna.storages.RetryFailedTrialCallback(max_retry=3) + ) + + # Create or load study + sampler = TPESampler( + seed=config['optimization_settings'].get('seed', 42), + n_startup_trials=config['optimization_settings'].get('n_startup_trials', 15), + multivariate=config['optimization_settings'].get('tpe_multivariate', True) + ) + + if resume: + try: + study = optuna.load_study( + study_name=study_name, + storage=storage, + sampler=sampler + ) + print(f"\nResumed study with {len(study.trials)} existing trials") + except KeyError: + study = optuna.create_study( + study_name=study_name, + storage=storage, + sampler=sampler, + direction='minimize' + ) + print(f"\nCreated new study: {study_name}") + else: + study = optuna.create_study( + study_name=study_name, + storage=storage, + sampler=sampler, + direction='minimize' + ) + print(f"\nCreated new study: {study_name}") + + # Initialize NX solver (lazy - only when needed for FEA) + nx_solver = None + + def get_nx_solver(): + nonlocal nx_solver + if nx_solver is None: + import re + nx_settings = config.get('nx_settings', {}) + + # Use model_dir from config if specified, otherwise default to MODEL_DIR + model_dir = Path(nx_settings.get('model_dir', str(MODEL_DIR))) + sim_file = model_dir / nx_settings.get('sim_file', 'ASSY_M1_assyfem1_sim1.sim') + + if not sim_file.exists(): + raise FileNotFoundError( + f"Simulation file not found: {sim_file}\n" + f"Please check model_dir in config or copy files to: {MODEL_DIR}" + ) + + # Get NX installation path and extract version + nx_install_path = nx_settings.get('nx_install_path', 'C:\\Program Files\\Siemens\\NX2506') + # Extract version from path (e.g., "NX2506" -> "2506") + version_match = re.search(r'NX(\d+)', nx_install_path) + nastran_version = version_match.group(1) if version_match else "2506" + + nx_solver = NXSolver( + nx_install_dir=Path(nx_install_path), + nastran_version=nastran_version, + timeout=nx_settings.get('op2_timeout_s', 1800), + study_name=config['study_name'] + ) + return nx_solver + + # Load neural surrogate if enabled + surrogate = None + if enable_nn: + surrogate_path = RESULTS_DIR / "zernike_surrogate" / "checkpoint_best.pt" + if surrogate_path.exists(): + surrogate = ZernikeSurrogate(surrogate_path) + print(f"\nLoaded neural surrogate from {surrogate_path}") + else: + print(f"\n[WARN] Neural surrogate not found at {surrogate_path}") + print("[WARN] Running FEA trials. Train surrogate with --train-surrogate") + enable_nn = False + + # Run trials + print(f"\nStarting optimization ({n_trials} trials)...") + + for i in range(n_trials): + trial = study.ask() + + try: + if enable_nn and surrogate is not None: + # Neural trial + value = neural_objective(trial, config, surrogate) + study.tell(trial, value) + + objectives = trial.user_attrs.get('objectives', {}) + print(f"[Neural Trial {trial.number}] weighted_obj={value:.4f} " + f"(40vs20={objectives.get('rel_filtered_rms_40_vs_20', 0):.1f}nm, " + f"60vs20={objectives.get('rel_filtered_rms_60_vs_20', 0):.1f}nm)") + else: + # FEA trial + solver = get_nx_solver() + value, trial_data = fea_objective(trial, config, solver) + study.tell(trial, value) + + except optuna.TrialPruned as e: + study.tell(trial, state=optuna.trial.TrialState.PRUNED) + print(f"[Trial {trial.number}] Pruned: {e}") + except Exception as e: + study.tell(trial, state=optuna.trial.TrialState.FAIL) + print(f"[Trial {trial.number}] Failed: {e}") + import traceback + traceback.print_exc() + + # Summary + print("\n" + "="*70) + print("OPTIMIZATION COMPLETE") + print("="*70) + + completed = [t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE] + print(f"Completed trials: {len(completed)}") + + if completed: + best = study.best_trial + print(f"\nBest trial: #{best.number}") + print(f"Best weighted objective: {best.value:.4f}") + print("\nBest parameters:") + for name, value in best.params.items(): + print(f" {name}: {value:.4f}") + + objectives = best.user_attrs.get('objectives', {}) + if objectives: + print("\nBest objectives:") + for name, value in objectives.items(): + print(f" {name}: {value:.2f} nm") + + +def show_status(): + """Show current optimization status.""" + config = load_config() + study_name = config['study_name'] + + db_path = RESULTS_DIR / "study.db" + if not db_path.exists(): + print("No study found. Run optimization first.") + return + + storage = optuna.storages.RDBStorage(f'sqlite:///{db_path}') + study = optuna.load_study(study_name=study_name, storage=storage) + + completed = [t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE] + fea_trials = [t for t in completed if t.user_attrs.get('source') == 'FEA'] + nn_trials = [t for t in completed if t.user_attrs.get('source') == 'Neural'] + + print("\n" + "="*60) + print(f"STUDY STATUS: {study_name}") + print("="*60) + print(f"Total trials: {len(study.trials)}") + print(f"Completed: {len(completed)}") + print(f" FEA trials: {len(fea_trials)}") + print(f" Neural trials: {len(nn_trials)}") + + if completed: + best = study.best_trial + print(f"\nBest trial: #{best.number} ({best.user_attrs.get('source', 'Unknown')})") + print(f"Best value: {best.value:.4f}") + + objectives = best.user_attrs.get('objectives', {}) + if objectives: + print("\nBest objectives:") + for name, value in objectives.items(): + print(f" {name}: {value:.2f} nm") + + +# ============================================================================== +# CLI +# ============================================================================== + +def main(): + parser = argparse.ArgumentParser( + description="M1 Mirror Zernike Optimization with Neural Acceleration" + ) + + parser.add_argument('--run', action='store_true', + help='Run optimization') + parser.add_argument('--trials', type=int, default=100, + help='Number of trials to run') + parser.add_argument('--enable-nn', action='store_true', + help='Enable neural surrogate') + parser.add_argument('--resume', action='store_true', default=True, + help='Resume existing study') + parser.add_argument('--train-surrogate', action='store_true', + help='Train neural surrogate from FEA data') + parser.add_argument('--status', action='store_true', + help='Show optimization status') + + args = parser.parse_args() + + if args.train_surrogate: + config = load_config() + train_zernike_surrogate(config) + elif args.run: + run_optimization( + n_trials=args.trials, + enable_nn=args.enable_nn, + resume=args.resume + ) + elif args.status: + show_status() + else: + parser.print_help() + + +if __name__ == '__main__': + main()