From 0229ce53bbbefedec1801646bacd7230def5ce83 Mon Sep 17 00:00:00 2001 From: Antoine Date: Wed, 11 Feb 2026 15:54:32 +0000 Subject: [PATCH] Fix NX version: DesigncenterNX2512 (was looking for NX2412) - Add DesigncenterNX{version} to install path search - Update default version to 2512 - Root cause of 'Part file is from a newer version' error --- optimization_engine/nx/solver.py | 2 + .../_temp_solve_journal~20260211-154152.py | 1126 +++++++++++++++++ .../results/optuna_study~20260211-152502.db | Bin 0 -> 114688 bytes .../results/optuna_study~20260211-154152.db | Bin 0 -> 114688 bytes .../results/optuna_study~20260211-154302.db | Bin 0 -> 114688 bytes .../models/_temp_solve_journal.py | 2 +- .../studies/01_doe_landscape/nx_interface.py | 2 +- 7 files changed, 1130 insertions(+), 2 deletions(-) create mode 100755 projects/.stversions/hydrotech-beam/models/_temp_solve_journal~20260211-154152.py create mode 100644 projects/.stversions/hydrotech-beam/studies/01_doe_landscape/results/optuna_study~20260211-152502.db create mode 100644 projects/.stversions/hydrotech-beam/studies/01_doe_landscape/results/optuna_study~20260211-154152.db create mode 100644 projects/.stversions/hydrotech-beam/studies/01_doe_landscape/results/optuna_study~20260211-154302.db diff --git a/optimization_engine/nx/solver.py b/optimization_engine/nx/solver.py index a468bb7a..edc8068d 100644 --- a/optimization_engine/nx/solver.py +++ b/optimization_engine/nx/solver.py @@ -90,6 +90,7 @@ class NXSolver: """Auto-detect NX installation directory.""" # Common installation paths possible_paths = [ + Path(f"C:/Program Files/Siemens/DesigncenterNX{self.nastran_version}"), Path(f"C:/Program Files/Siemens/NX{self.nastran_version}"), Path(f"C:/Program Files/Siemens/Simcenter3D_{self.nastran_version}"), Path(f"C:/Program Files (x86)/Siemens/NX{self.nastran_version}"), @@ -115,6 +116,7 @@ class NXSolver: # Fallback: check common installation paths possible_exes = [ + Path(f"C:/Program Files/Siemens/DesigncenterNX{self.nastran_version}/NXBIN/run_journal.exe"), Path(f"C:/Program Files/Siemens/Simcenter3D_{self.nastran_version}/NXBIN/run_journal.exe"), Path(f"C:/Program Files/Siemens/NX{self.nastran_version}/NXBIN/run_journal.exe"), Path(f"C:/Program Files/Siemens/DesigncenterNX{self.nastran_version}/NXBIN/run_journal.exe"), diff --git a/projects/.stversions/hydrotech-beam/models/_temp_solve_journal~20260211-154152.py b/projects/.stversions/hydrotech-beam/models/_temp_solve_journal~20260211-154152.py new file mode 100755 index 00000000..8f3bae0d --- /dev/null +++ b/projects/.stversions/hydrotech-beam/models/_temp_solve_journal~20260211-154152.py @@ -0,0 +1,1126 @@ +# Auto-generated journal for solving Beam_sim1.sim +import sys +sys.argv = ['', r'C:\Users\antoi\Atomizer\projects\hydrotech-beam\studies\01_doe_landscape\..\..\models\Beam_sim1.sim', None, 'beam_half_core_thickness=25.162', 'beam_face_thickness=21.504', 'holes_diameter=300.0', 'hole_count=10.0'] # Set argv for the main function +""" +NX Journal Script to Solve Simulation in Batch Mode + +This script handles BOTH single-part simulations AND multi-part assembly FEMs. + +============================================================================= +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 + + +def extract_part_mass(theSession, part, output_dir): + """ + Extract mass from a part using NX MeasureManager. + + Writes mass to _temp_mass.txt and _temp_part_properties.json in output_dir. + + Args: + theSession: NXOpen.Session + part: NXOpen.Part to extract mass from + output_dir: Directory to write temp files + + Returns: + Mass in kg (float) + """ + import json + + results = { + "part_file": part.Name, + "mass_kg": 0.0, + "mass_g": 0.0, + "volume_mm3": 0.0, + "surface_area_mm2": 0.0, + "center_of_gravity_mm": [0.0, 0.0, 0.0], + "num_bodies": 0, + "success": False, + "error": None, + } + + try: + # Get all solid bodies + bodies = [] + for body in part.Bodies: + if body.IsSolidBody: + bodies.append(body) + + results["num_bodies"] = len(bodies) + + if not bodies: + results["error"] = "No solid bodies found" + raise ValueError("No solid bodies found in part") + + # Get the measure manager + measureManager = part.MeasureManager + + # Get unit collection and build mass_units array + # API requires: [Area, Volume, Mass, Length] base units + uc = part.UnitCollection + mass_units = [ + uc.GetBase("Area"), + uc.GetBase("Volume"), + uc.GetBase("Mass"), + uc.GetBase("Length"), + ] + + # Create mass properties measurement + measureBodies = measureManager.NewMassProperties(mass_units, 0.99, bodies) + + if measureBodies: + results["mass_kg"] = measureBodies.Mass + results["mass_g"] = results["mass_kg"] * 1000.0 + + try: + results["volume_mm3"] = measureBodies.Volume + except: + pass + + try: + results["surface_area_mm2"] = measureBodies.Area + except: + pass + + try: + cog = measureBodies.Centroid + if cog: + results["center_of_gravity_mm"] = [cog.X, cog.Y, cog.Z] + except: + pass + + try: + measureBodies.Dispose() + except: + pass + + results["success"] = True + + except Exception as e: + results["error"] = str(e) + results["success"] = False + + # Write results to JSON file + output_file = os.path.join(output_dir, "_temp_part_properties.json") + with open(output_file, "w") as f: + json.dump(results, f, indent=2) + + # Write simple mass value for backward compatibility + mass_file = os.path.join(output_dir, "_temp_mass.txt") + with open(mass_file, "w") as f: + f.write(str(results["mass_kg"])) + + if not results["success"]: + raise ValueError(results["error"]) + + return results["mass_kg"] + + +def find_or_open_part(theSession, part_path): + """ + Find a part if already loaded, otherwise open it. + In NX, calling Parts.Open() on an already-loaded part raises 'File already exists'. + """ + part_name = os.path.splitext(os.path.basename(part_path))[0] + + # Try to find in already-loaded parts + for part in theSession.Parts: + if part.Name == part_name: + return part, True + try: + if part.FullPath and part.FullPath.lower() == part_path.lower(): + return part, True + except: + pass + + # Not found, open it + markId = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, f"Load {part_name}") + part, partLoadStatus = theSession.Parts.Open(part_path) + partLoadStatus.Dispose() + return part, False + + +def main(args): + """ + Main entry point for NX journal. + + Args: + args: Command line arguments + args[0]: .sim file path + 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] ..." + ) + return False + + sim_file_path = args[0] + solution_name = args[1] if len(args) > 1 and args[1] != "None" else None + + # Parse expression updates + expression_updates = {} + for arg in args[2:]: + if "=" in arg: + name, value = arg.split("=", 1) + expression_updates[name] = float(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 + theSession.Parts.LoadOptions.LoadLatest = False + theSession.Parts.LoadOptions.ComponentLoadMethod = ( + NXOpen.LoadOptions.LoadMethod.FromDirectory + ) + 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.AbortOnFailure = False + + # Close any open parts + try: + theSession.Parts.CloseAll([NXOpen.BasePart.CloseWholeTree]) + except: + pass + + # 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 + + 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: + 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 + ) + + except Exception as 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 SIM FILE FIRST (loads entire assembly hierarchy) + # ========================================================================== + print(f"[JOURNAL] STEP 1: Loading SIM file and all components...") + + # CRITICAL: Open the SIM file FIRST using OpenActiveDisplay + # This loads the entire assembly FEM hierarchy (.afm, .fem, associated .prt files) + # The sim file knows its component structure and will load everything it needs + sim_file_full_path = os.path.join(working_dir, sim_filename) + print(f"[JOURNAL] Opening SIM file: {sim_filename}") + basePart, partLoadStatus = theSession.Parts.OpenActiveDisplay( + sim_file_full_path, NXOpen.DisplayPartOption.AllowAdditional + ) + partLoadStatus.Dispose() + + workSimPart = theSession.Parts.BaseWork + displaySimPart = theSession.Parts.BaseDisplay + print(f"[JOURNAL] SIM loaded: {workSimPart.Name}") + + # List loaded parts + print(f"[JOURNAL] Currently loaded parts:") + for part in theSession.Parts: + print(f"[JOURNAL] - {part.Name}") + + # ========================================================================== + # STEP 1b: LOAD GEOMETRY PARTS FOR EXPRESSION EDITING + # ========================================================================== + print(f"[JOURNAL] STEP 1b: Loading geometry parts for expression editing...") + + # The recorded journal loads these geometry parts explicitly: + # 1. ASSY_M1.prt - the main geometry assembly + # 2. M1_Blank_fem1_i.prt - idealized geometry for M1_Blank FEM + # 3. M1_Vertical_Support_Skeleton_fem1_i.prt - idealized geometry for support FEM + + # 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...") + part1, was_loaded = find_or_open_part(theSession, assy_prt_path) + if was_loaded: + print(f"[JOURNAL] (already loaded)") + else: + print(f"[JOURNAL] WARNING: ASSY_M1.prt not found!") + + # Load M1_Blank_fem1_i.prt (idealized geometry for M1_Blank) + 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...") + part2, was_loaded = find_or_open_part(theSession, idealized_prt_path) + if was_loaded: + print(f"[JOURNAL] (already loaded)") + else: + print(f"[JOURNAL] WARNING: M1_Blank_fem1_i.prt not found!") + + # Load M1_Vertical_Support_Skeleton_fem1_i.prt (CRITICAL: idealized geometry for support) + skeleton_idealized_prt_path = os.path.join( + working_dir, "M1_Vertical_Support_Skeleton_fem1_i.prt" + ) + if os.path.exists(skeleton_idealized_prt_path): + print(f"[JOURNAL] Loading M1_Vertical_Support_Skeleton_fem1_i.prt...") + part3_skel, was_loaded = find_or_open_part(theSession, skeleton_idealized_prt_path) + if was_loaded: + print(f"[JOURNAL] (already loaded)") + else: + print(f"[JOURNAL] WARNING: M1_Vertical_Support_Skeleton_fem1_i.prt not found!") + + # ========================================================================== + # STEP 2: UPDATE EXPRESSIONS IN M1_BLANK AND REBUILD ALL GEOMETRY + # ========================================================================== + 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 M1_Blank 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] M1_Blank geometry rebuilt ({nErrs} errors)") + + # CRITICAL: Save M1_Blank after geometry update so FEM can read updated geometry + print(f"[JOURNAL] Saving M1_Blank...") + partSaveStatus_blank = workPart.Save( + NXOpen.BasePart.SaveComponents.TrueValue, NXOpen.BasePart.CloseAfterSave.FalseValue + ) + partSaveStatus_blank.Dispose() + print(f"[JOURNAL] M1_Blank saved") + + # STEP 2a: EXTRACT MASS FROM M1_BLANK + # Extract mass using MeasureManager after geometry is updated + print(f"[JOURNAL] Extracting mass from M1_Blank...") + try: + mass_kg = extract_part_mass(theSession, workPart, working_dir) + print(f"[JOURNAL] Mass extracted: {mass_kg:.6f} kg ({mass_kg * 1000:.2f} g)") + except Exception as mass_err: + print(f"[JOURNAL] WARNING: Mass extraction failed: {mass_err}") + + 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 2b: UPDATE ALL LINKED GEOMETRY PARTS + # ========================================================================== + # CRITICAL: Must update ALL geometry parts that have linked expressions + # before updating FEMs, otherwise interface nodes won't be coincident! + print(f"[JOURNAL] STEP 2b: Updating all linked geometry parts...") + + # List of geometry parts that may have linked expressions from M1_Blank + linked_geometry_parts = [ + "M1_Vertical_Support_Skeleton", + # Add more parts here if the assembly has additional linked geometry + ] + + for part_name in linked_geometry_parts: + try: + print(f"[JOURNAL] Updating {part_name}...") + linked_part = theSession.Parts.FindObject(part_name) + + markId_linked = theSession.SetUndoMark( + NXOpen.Session.MarkVisibility.Visible, f"Update {part_name}" + ) + status_linked, partLoadStatus_linked = theSession.Parts.SetActiveDisplay( + linked_part, + NXOpen.DisplayPartOption.AllowAdditional, + NXOpen.PartDisplayPartWorkPartOption.UseLast, + ) + partLoadStatus_linked.Dispose() + + # Switch to modeling application + theSession.ApplicationSwitchImmediate("UG_APP_MODELING") + + # Update to propagate linked expression changes + markId_linked_update = theSession.SetUndoMark( + NXOpen.Session.MarkVisibility.Invisible, "NX update" + ) + nErrs_linked = theSession.UpdateManager.DoUpdate(markId_linked_update) + theSession.DeleteUndoMark(markId_linked_update, "NX update") + print(f"[JOURNAL] {part_name} geometry rebuilt ({nErrs_linked} errors)") + + # CRITICAL: Save part after geometry update so FEM can read updated geometry + print(f"[JOURNAL] Saving {part_name}...") + partSaveStatus_linked = linked_part.Save( + NXOpen.BasePart.SaveComponents.TrueValue, NXOpen.BasePart.CloseAfterSave.FalseValue + ) + partSaveStatus_linked.Dispose() + print(f"[JOURNAL] {part_name} saved") + + except Exception as e: + print(f"[JOURNAL] WARNING: Could not update {part_name}: {e}") + print(f"[JOURNAL] (Part may not exist in this assembly)") + + # ========================================================================== + # 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") + + # CRITICAL: Save FEM file after update to persist mesh changes + print(f"[JOURNAL] Saving M1_Blank_fem1...") + partSaveStatus_fem1 = workFemPart.Save( + NXOpen.BasePart.SaveComponents.TrueValue, NXOpen.BasePart.CloseAfterSave.FalseValue + ) + partSaveStatus_fem1.Dispose() + print(f"[JOURNAL] M1_Blank_fem1 saved") + 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") + + # CRITICAL: Save FEM file after update to persist mesh changes + print(f"[JOURNAL] Saving M1_Vertical_Support_Skeleton_fem1...") + partSaveStatus_fem2 = workFemPart.Save( + NXOpen.BasePart.SaveComponents.TrueValue, NXOpen.BasePart.CloseAfterSave.FalseValue + ) + partSaveStatus_fem2.Dispose() + print(f"[JOURNAL] M1_Vertical_Support_Skeleton_fem1 saved") + 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") + + # WORKAROUND: Force display refresh before duplicate node check + # The recorded journal does zoom operations before checking - this may + # be needed to refresh the internal mesh representation + try: + displaySimPart.ModelingViews.WorkView.Fit() + print(f"[JOURNAL] Forced view Fit() to refresh display") + except Exception as fit_err: + print(f"[JOURNAL] View Fit() failed (non-critical): {fit_err}") + + 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] IdentifyDuplicateNodes returned: {numDuplicates}") + + # WORKAROUND: In batch mode, IdentifyDuplicateNodes() often returns None + # even when duplicates exist. The recorded NX journal doesn't check the + # return value - it just calls MergeDuplicateNodes unconditionally. + # So we do the same: always attempt to merge. + print(f"[JOURNAL] Attempting to merge duplicate nodes...") + try: + numMerged = duplicateNodesCheckBuilder1.MergeDuplicateNodes() + print(f"[JOURNAL] MergeDuplicateNodes returned: {numMerged}") + if numMerged is not None and numMerged > 0: + print(f"[JOURNAL] Successfully merged {numMerged} duplicate node sets") + elif numMerged == 0: + print(f"[JOURNAL] No nodes were merged (0 returned)") + if numDuplicates is None: + print( + f"[JOURNAL] WARNING: IdentifyDuplicateNodes returned None - mesh may need display refresh" + ) + else: + print(f"[JOURNAL] MergeDuplicateNodes returned None - batch mode limitation") + except Exception as merge_error: + print(f"[JOURNAL] MergeDuplicateNodes failed: {merge_error}") + if numDuplicates is None: + print( + f"[JOURNAL] This combined with IdentifyDuplicateNodes=None suggests display issue" + ) + + 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 5b: SAVE ASSEMBLY FEM + # ========================================================================== + print(f"[JOURNAL] STEP 5b: Saving assembly FEM after all updates...") + try: + # Save the assembly FEM to persist all mesh updates and node merges + partSaveStatus_afem = workAssyFemPart.Save( + NXOpen.BasePart.SaveComponents.TrueValue, NXOpen.BasePart.CloseAfterSave.FalseValue + ) + partSaveStatus_afem.Dispose() + print(f"[JOURNAL] Assembly FEM saved: {workAssyFemPart.Name}") + except Exception as e: + print(f"[JOURNAL] WARNING: Could not save assembly FEM: {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" + ) + + # ========================================================================== + # STEP 7: SAVE ALL - Save all modified parts (FEM, SIM, PRT) + # ========================================================================== + print(f"[JOURNAL] STEP 7: Saving all modified parts...") + try: + anyPartsModified, partSaveStatus_all = theSession.Parts.SaveAll() + partSaveStatus_all.Dispose() + print(f"[JOURNAL] SaveAll completed (parts modified: {anyPartsModified})") + except Exception as e: + print(f"[JOURNAL] WARNING: SaveAll failed: {e}") + + 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 +): + """ + Workflow for single-part simulations with optional expression updates. + + For single-part FEMs (Bracket.prt -> Bracket_fem1.fem -> Bracket_sim1.sim): + 1. Open the .sim file (this loads .fem and .prt) + 2. If expression_updates: find the geometry .prt, update expressions, rebuild + 3. Update the FEM mesh + 4. Solve + """ + 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 + + # ========================================================================= + # STEP 1: UPDATE EXPRESSIONS IN GEOMETRY PART (if any) + # ========================================================================= + if expression_updates: + print(f"[JOURNAL] STEP 1: Updating expressions in geometry part...") + + # List all loaded parts for debugging + print(f"[JOURNAL] Currently loaded parts:") + for part in theSession.Parts: + print(f"[JOURNAL] - {part.Name} (type: {type(part).__name__})") + + # NX doesn't automatically load the geometry .prt when opening a SIM file + # We need to find and load it explicitly from the working directory + geom_part = None + + # First, try to find an already loaded geometry part + for part in theSession.Parts: + part_name = part.Name.lower() + part_type = type(part).__name__ + + # Skip FEM and SIM parts by type + if "fem" in part_type.lower() or "sim" in part_type.lower(): + continue + + # Skip parts with _fem or _sim in name + if "_fem" in part_name or "_sim" in part_name: + continue + + geom_part = part + print(f"[JOURNAL] Found geometry part (already loaded): {part.Name}") + break + + # If not found, try to load the geometry .prt file from working directory + if geom_part is None: + print(f"[JOURNAL] Geometry part not loaded, searching for .prt file...") + for filename in os.listdir(working_dir): + # Skip idealized parts (_i.prt), FEM parts, and SIM parts + if ( + filename.endswith(".prt") + and "_fem" not in filename.lower() + and "_sim" not in filename.lower() + and "_i.prt" not in filename.lower() + ): + prt_path = os.path.join(working_dir, filename) + print(f"[JOURNAL] Loading geometry part: {filename}") + try: + loaded_part, partLoadStatus = theSession.Parts.Open(prt_path) + partLoadStatus.Dispose() + # Check if load actually succeeded (Parts.Open can return None) + if loaded_part is not None: + geom_part = loaded_part + print(f"[JOURNAL] Geometry part loaded: {geom_part.Name}") + break + else: + print(f"[JOURNAL] WARNING: Parts.Open returned None for {filename}") + except Exception as e: + print(f"[JOURNAL] WARNING: Could not load {filename}: {e}") + + if geom_part: + try: + # Switch to the geometry part for expression editing + markId_expr = theSession.SetUndoMark( + NXOpen.Session.MarkVisibility.Visible, "Update Expressions" + ) + status, partLoadStatus = theSession.Parts.SetActiveDisplay( + geom_part, + NXOpen.DisplayPartOption.AllowAdditional, + NXOpen.PartDisplayPartWorkPartOption.UseLast, + ) + partLoadStatus.Dispose() + + # Switch to modeling application for expression editing + theSession.ApplicationSwitchImmediate("UG_APP_MODELING") + + workPart = theSession.Parts.Work + + # Write expressions to temp file and import + 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 based on name + if "angle" 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...") + expModified, errorMessages = workPart.Expressions.ImportFromFile( + exp_file_path, NXOpen.ExpressionCollection.ImportMode.Replace + ) + print(f"[JOURNAL] Expressions modified: {expModified}") + if errorMessages: + print(f"[JOURNAL] Import messages: {errorMessages}") + + # Update geometry + 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)") + + # Save geometry part + print(f"[JOURNAL] Saving geometry part...") + partSaveStatus_geom = workPart.Save( + NXOpen.BasePart.SaveComponents.TrueValue, + NXOpen.BasePart.CloseAfterSave.FalseValue, + ) + partSaveStatus_geom.Dispose() + + # Clean up temp file + try: + os.remove(exp_file_path) + except: + pass + + except Exception as e: + print(f"[JOURNAL] ERROR updating expressions: {e}") + import traceback + + traceback.print_exc() + else: + print(f"[JOURNAL] WARNING: Could not find geometry part for expression updates!") + + # ========================================================================= + # STEP 2: UPDATE FEM MESH (if expressions were updated) + # ========================================================================= + if expression_updates: + print(f"[JOURNAL] STEP 2: Updating FEM mesh...") + + # First, load the idealized part if it exists (required for mesh update chain) + # The chain is: .prt (geometry) -> _i.prt (idealized) -> .fem (mesh) + idealized_part = None + for filename in os.listdir(working_dir): + if "_i.prt" in filename.lower(): + idealized_path = os.path.join(working_dir, filename) + print(f"[JOURNAL] Loading idealized part: {filename}") + try: + loaded_part, partLoadStatus = theSession.Parts.Open(idealized_path) + partLoadStatus.Dispose() + # Check if load actually succeeded (Parts.Open can return None) + if loaded_part is not None: + idealized_part = loaded_part + print(f"[JOURNAL] Idealized part loaded: {idealized_part.Name}") + else: + print(f"[JOURNAL] WARNING: Parts.Open returned None for idealized part") + except Exception as e: + print(f"[JOURNAL] WARNING: Could not load idealized part: {e}") + break + + # Find the FEM part + fem_part = None + for part in theSession.Parts: + if "_fem" in part.Name.lower() or part.Name.lower().endswith(".fem"): + fem_part = part + print(f"[JOURNAL] Found FEM part: {part.Name}") + break + + if fem_part: + try: + # Switch to FEM part - CRITICAL: Use SameAsDisplay to make FEM the work part + # This is required for UpdateFemodel() to properly regenerate the mesh + # Reference: tests/journal_with_regenerate.py line 76 + print(f"[JOURNAL] Switching to FEM part: {fem_part.Name}") + status, partLoadStatus = theSession.Parts.SetActiveDisplay( + fem_part, + NXOpen.DisplayPartOption.AllowAdditional, + NXOpen.PartDisplayPartWorkPartOption.SameAsDisplay, # Critical fix! + ) + partLoadStatus.Dispose() + + # Switch to FEM application + theSession.ApplicationSwitchImmediate("UG_APP_SFEM") + + # Update the FE model + workFemPart = theSession.Parts.BaseWork + feModel = workFemPart.FindObject("FEModel") + + print(f"[JOURNAL] Updating FE model...") + feModel.UpdateFemodel() + print(f"[JOURNAL] FE model updated") + + # Save FEM + partSaveStatus_fem = workFemPart.Save( + NXOpen.BasePart.SaveComponents.TrueValue, + NXOpen.BasePart.CloseAfterSave.FalseValue, + ) + partSaveStatus_fem.Dispose() + print(f"[JOURNAL] FEM saved") + + except Exception as e: + print(f"[JOURNAL] ERROR updating FEM: {e}") + import traceback + + traceback.print_exc() + + # ========================================================================= + # STEP 3: SWITCH BACK TO SIM AND SOLVE + # ========================================================================= + print(f"[JOURNAL] STEP 3: Solving simulation...") + + # Switch back to sim part + status, partLoadStatus = theSession.Parts.SetActiveDisplay( + workSimPart, + NXOpen.DisplayPartOption.AllowAdditional, + NXOpen.PartDisplayPartWorkPartOption.UseLast, + ) + partLoadStatus.Dispose() + + 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.Foreground, # Use Foreground to wait for completion + ) + + theSession.DeleteUndoMark(markId_solve2, None) + theSession.SetUndoMarkName(markId_solve, "Solve") + + print( + f"[JOURNAL] Solve completed: {numsolved} solved, {numfailed} failed, {numskipped} skipped" + ) + + # Save all + try: + anyPartsModified, partSaveStatus = theSession.Parts.SaveAll() + partSaveStatus.Dispose() + print(f"[JOURNAL] Saved all parts!") + except: + pass + + return numfailed == 0 + + +if __name__ == "__main__": + success = main(sys.argv[1:]) + # NOTE: Do NOT use sys.exit() here! + # run_journal.exe treats SystemExit (even code 0) as "Syntax errors" + # and returns a non-zero exit code, which makes the solver think + # the journal crashed. Instead, we just let the script end naturally. + # The solver checks for output files (.op2, .f06) to determine success. + if not success: + print("[JOURNAL] FAILED - solve did not complete successfully") + diff --git a/projects/.stversions/hydrotech-beam/studies/01_doe_landscape/results/optuna_study~20260211-152502.db b/projects/.stversions/hydrotech-beam/studies/01_doe_landscape/results/optuna_study~20260211-152502.db new file mode 100644 index 0000000000000000000000000000000000000000..3496112a14c7b635a2efbadd63a610762ea9db69 GIT binary patch literal 114688 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 Opd-)|J&`o8|NjdQ72;F? literal 0 HcmV?d00001 diff --git a/projects/.stversions/hydrotech-beam/studies/01_doe_landscape/results/optuna_study~20260211-154152.db b/projects/.stversions/hydrotech-beam/studies/01_doe_landscape/results/optuna_study~20260211-154152.db new file mode 100644 index 0000000000000000000000000000000000000000..00bdccac1276889fc3e9ba74eeecb956d87a7124 GIT binary patch literal 114688 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 literal 0 HcmV?d00001 diff --git a/projects/.stversions/hydrotech-beam/studies/01_doe_landscape/results/optuna_study~20260211-154302.db b/projects/.stversions/hydrotech-beam/studies/01_doe_landscape/results/optuna_study~20260211-154302.db new file mode 100644 index 0000000000000000000000000000000000000000..979fce9ec0f4e1d1367ea7aeb55040d5a3510d86 GIT binary patch literal 114688 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 Q0ubm5^hHl4&HMlV0?u;cPyhe` literal 0 HcmV?d00001 diff --git a/projects/hydrotech-beam/models/_temp_solve_journal.py b/projects/hydrotech-beam/models/_temp_solve_journal.py index 8f3bae0d..201f277b 100755 --- a/projects/hydrotech-beam/models/_temp_solve_journal.py +++ b/projects/hydrotech-beam/models/_temp_solve_journal.py @@ -1,6 +1,6 @@ # Auto-generated journal for solving Beam_sim1.sim import sys -sys.argv = ['', r'C:\Users\antoi\Atomizer\projects\hydrotech-beam\studies\01_doe_landscape\..\..\models\Beam_sim1.sim', None, 'beam_half_core_thickness=25.162', 'beam_face_thickness=21.504', 'holes_diameter=300.0', 'hole_count=10.0'] # Set argv for the main function +sys.argv = ['', r'C:\Users\antoi\Atomizer\projects\hydrotech-beam\models\Beam_sim1.sim', None, 'beam_half_core_thickness=25.162', 'beam_face_thickness=21.504', 'holes_diameter=300.0', 'hole_count=10.0'] # Set argv for the main function """ NX Journal Script to Solve Simulation in Batch Mode diff --git a/projects/hydrotech-beam/studies/01_doe_landscape/nx_interface.py b/projects/hydrotech-beam/studies/01_doe_landscape/nx_interface.py index 1d754ee2..7e40da27 100644 --- a/projects/hydrotech-beam/studies/01_doe_landscape/nx_interface.py +++ b/projects/hydrotech-beam/studies/01_doe_landscape/nx_interface.py @@ -145,7 +145,7 @@ class AtomizerNXSolver: def __init__( self, model_dir: str | Path = ".", - nx_version: str = "2412", + nx_version: str = "2512", timeout: int = 600, use_iteration_folders: bool = False, # Disabled: copied NX files break internal references ):