feat: Major update - Physics docs, Zernike OPD, insights, NX journals, tools
Documentation: - Add docs/06_PHYSICS/ with Zernike fundamentals and OPD method docs - Add docs/guides/CMA-ES_EXPLAINED.md optimization guide - Update CLAUDE.md and ATOMIZER_CONTEXT.md with current architecture - Update OP_01_CREATE_STUDY protocol Planning: - Add DYNAMIC_RESPONSE plans for random vibration/PSD support - Add OPTIMIZATION_ENGINE_MIGRATION_PLAN for code reorganization Insights System: - Update design_space, modal_analysis, stress_field, thermal_field insights - Improve error handling and data validation NX Journals: - Add analyze_wfe_zernike.py for Zernike WFE analysis - Add capture_study_images.py for automated screenshots - Add extract_expressions.py and introspect_part.py utilities - Add user_generated_journals/journal_top_view_image_taking.py Tests & Tools: - Add comprehensive Zernike OPD test suite - Add audit_v10 tests for WFE validation - Add tools for Pareto graphs and mirror data extraction - Add migrate_studies_to_topics.py utility Knowledge Base: - Initialize LAC (Learning Atomizer Core) with failure/success patterns Dashboard: - Update Setup.tsx and launch_dashboard.py - Add restart-dev.bat helper script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
233
nx_journals/analyze_wfe_zernike.py
Normal file
233
nx_journals/analyze_wfe_zernike.py
Normal file
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Atomizer Zernike WFE Analyzer
|
||||
=============================
|
||||
|
||||
Analyze Zernike wavefront error from NX Nastran OP2 results.
|
||||
|
||||
IMPORTANT: This script requires numpy/scipy. Run from command line with
|
||||
the atomizer conda environment, NOT from within NX.
|
||||
|
||||
Usage:
|
||||
conda activate atomizer
|
||||
python analyze_wfe_zernike.py "path/to/solution.op2"
|
||||
|
||||
# Or without argument - searches current directory for OP2 files:
|
||||
python analyze_wfe_zernike.py
|
||||
|
||||
Output:
|
||||
- Zernike coefficients for each subcase
|
||||
- Relative WFE metrics (filtered RMS)
|
||||
- Manufacturing workload (J1-J3 filtered)
|
||||
- Weighted sum calculation
|
||||
|
||||
Author: Atomizer
|
||||
Created: 2025-12-18
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def log(msg):
|
||||
"""Print to console."""
|
||||
print(msg)
|
||||
|
||||
|
||||
def find_op2_file(working_dir=None):
|
||||
"""Find the most recent OP2 file in the working directory."""
|
||||
if working_dir is None:
|
||||
working_dir = Path.cwd()
|
||||
else:
|
||||
working_dir = Path(working_dir)
|
||||
|
||||
# Look for OP2 files
|
||||
op2_files = list(working_dir.glob("*solution*.op2")) + list(working_dir.glob("*.op2"))
|
||||
|
||||
if not op2_files:
|
||||
# Check subdirectories
|
||||
op2_files = list(working_dir.glob("**/*solution*.op2"))
|
||||
|
||||
if not op2_files:
|
||||
return None
|
||||
|
||||
# Return most recently modified
|
||||
return max(op2_files, key=lambda p: p.stat().st_mtime)
|
||||
|
||||
def analyze_zernike(op2_path):
|
||||
"""Run Zernike analysis on OP2 file."""
|
||||
|
||||
# Add Atomizer to path
|
||||
atomizer_root = Path(__file__).parent.parent
|
||||
if str(atomizer_root) not in sys.path:
|
||||
sys.path.insert(0, str(atomizer_root))
|
||||
|
||||
try:
|
||||
from optimization_engine.extractors import ZernikeExtractor
|
||||
except ImportError as e:
|
||||
log(f"ERROR: Could not import ZernikeExtractor: {e}")
|
||||
log(f"Make sure Atomizer is properly installed.")
|
||||
log(f"Atomizer root: {atomizer_root}")
|
||||
return None
|
||||
|
||||
log("=" * 70)
|
||||
log("ZERNIKE WAVEFRONT ERROR ANALYSIS")
|
||||
log("=" * 70)
|
||||
log(f"OP2 File: {op2_path.name}")
|
||||
log(f"Directory: {op2_path.parent}")
|
||||
log("")
|
||||
|
||||
# Create extractor
|
||||
try:
|
||||
extractor = ZernikeExtractor(
|
||||
op2_path,
|
||||
bdf_path=None,
|
||||
displacement_unit='mm',
|
||||
n_modes=50,
|
||||
filter_orders=4
|
||||
)
|
||||
except Exception as e:
|
||||
log(f"ERROR creating extractor: {e}")
|
||||
return None
|
||||
|
||||
# Get available subcases from the extractor's displacement data
|
||||
subcases = list(extractor.displacements.keys())
|
||||
log(f"Available subcases: {subcases}")
|
||||
log("")
|
||||
|
||||
# Standard subcase mapping for M1 mirror
|
||||
subcase_labels = {
|
||||
'1': '90 deg (Manufacturing/Polishing)',
|
||||
'2': '20 deg (Reference)',
|
||||
'3': '40 deg (Operational)',
|
||||
'4': '60 deg (Operational)'
|
||||
}
|
||||
|
||||
# Extract absolute Zernike for each subcase
|
||||
log("-" * 70)
|
||||
log("ABSOLUTE ZERNIKE ANALYSIS (per subcase)")
|
||||
log("-" * 70)
|
||||
|
||||
results = {}
|
||||
for sc in subcases:
|
||||
try:
|
||||
result = extractor.extract_subcase(sc)
|
||||
results[sc] = result
|
||||
label = subcase_labels.get(sc, f'Subcase {sc}')
|
||||
log(f"\n{label}:")
|
||||
log(f" Global RMS: {result['global_rms_nm']:.2f} nm")
|
||||
log(f" Filtered RMS: {result['filtered_rms_nm']:.2f} nm (J4+ only)")
|
||||
except Exception as e:
|
||||
log(f" ERROR extracting subcase {sc}: {e}")
|
||||
|
||||
# Relative analysis (using subcase 2 as reference)
|
||||
ref_subcase = '2'
|
||||
if ref_subcase in subcases:
|
||||
log("")
|
||||
log("-" * 70)
|
||||
log(f"RELATIVE ANALYSIS (vs {subcase_labels.get(ref_subcase, ref_subcase)})")
|
||||
log("-" * 70)
|
||||
|
||||
relative_results = {}
|
||||
for sc in subcases:
|
||||
if sc == ref_subcase:
|
||||
continue
|
||||
try:
|
||||
rel = extractor.extract_relative(sc, ref_subcase)
|
||||
relative_results[sc] = rel
|
||||
label = subcase_labels.get(sc, f'Subcase {sc}')
|
||||
log(f"\n{label} vs Reference:")
|
||||
log(f" Relative Filtered RMS: {rel['relative_filtered_rms_nm']:.2f} nm")
|
||||
if 'relative_rms_filter_j1to3' in rel:
|
||||
log(f" J1-J3 Filtered RMS: {rel['relative_rms_filter_j1to3']:.2f} nm")
|
||||
except Exception as e:
|
||||
log(f" ERROR: {e}")
|
||||
|
||||
# Calculate weighted sum (M1 mirror optimization objectives)
|
||||
log("")
|
||||
log("-" * 70)
|
||||
log("OPTIMIZATION OBJECTIVES")
|
||||
log("-" * 70)
|
||||
|
||||
obj_40_20 = relative_results.get('3', {}).get('relative_filtered_rms_nm', 0)
|
||||
obj_60_20 = relative_results.get('4', {}).get('relative_filtered_rms_nm', 0)
|
||||
obj_mfg = relative_results.get('1', {}).get('relative_rms_filter_j1to3', 0)
|
||||
|
||||
log(f"\n 40-20 Filtered RMS: {obj_40_20:.2f} nm")
|
||||
log(f" 60-20 Filtered RMS: {obj_60_20:.2f} nm")
|
||||
log(f" MFG 90 (J1-J3): {obj_mfg:.2f} nm")
|
||||
|
||||
# Weighted sums for different weight configurations
|
||||
log("")
|
||||
log("Weighted Sum Calculations:")
|
||||
|
||||
# V4 weights: 5*40 + 5*60 + 2*mfg + mass
|
||||
ws_v4 = 5*obj_40_20 + 5*obj_60_20 + 2*obj_mfg
|
||||
log(f" V4 weights (5/5/2): {ws_v4:.2f} (+ mass)")
|
||||
|
||||
# V5 weights: 5*40 + 5*60 + 3*mfg + mass
|
||||
ws_v5 = 5*obj_40_20 + 5*obj_60_20 + 3*obj_mfg
|
||||
log(f" V5 weights (5/5/3): {ws_v5:.2f} (+ mass)")
|
||||
|
||||
return {
|
||||
'absolute': results,
|
||||
'relative': relative_results,
|
||||
'objectives': {
|
||||
'40_20': obj_40_20,
|
||||
'60_20': obj_60_20,
|
||||
'mfg_90': obj_mfg,
|
||||
'ws_v4': ws_v4,
|
||||
'ws_v5': ws_v5
|
||||
}
|
||||
}
|
||||
|
||||
return {'absolute': results}
|
||||
|
||||
def main(args):
|
||||
"""Main entry point."""
|
||||
log("")
|
||||
log("=" * 70)
|
||||
log(" ATOMIZER ZERNIKE WFE ANALYZER")
|
||||
log("=" * 70)
|
||||
log("")
|
||||
|
||||
# Determine OP2 file
|
||||
op2_path = None
|
||||
|
||||
if args and len(args) > 0 and args[0]:
|
||||
# OP2 path provided as argument
|
||||
op2_path = Path(args[0])
|
||||
if not op2_path.exists():
|
||||
log(f"ERROR: OP2 file not found: {op2_path}")
|
||||
return
|
||||
else:
|
||||
# Try to find OP2 in current directory
|
||||
log("No OP2 file specified, searching...")
|
||||
op2_path = find_op2_file()
|
||||
|
||||
if op2_path is None:
|
||||
log("ERROR: No OP2 file found in current directory.")
|
||||
log("Usage: Run after solving, or provide OP2 path as argument.")
|
||||
return
|
||||
|
||||
log(f"Found: {op2_path}")
|
||||
|
||||
# Run analysis
|
||||
results = analyze_zernike(op2_path)
|
||||
|
||||
if results:
|
||||
log("")
|
||||
log("=" * 70)
|
||||
log("ANALYSIS COMPLETE")
|
||||
log("=" * 70)
|
||||
else:
|
||||
log("")
|
||||
log("Analysis failed. Check errors above.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Get arguments (works both in NX and command line)
|
||||
if len(sys.argv) > 1:
|
||||
main(sys.argv[1:])
|
||||
else:
|
||||
main([])
|
||||
184
nx_journals/capture_study_images.py
Normal file
184
nx_journals/capture_study_images.py
Normal file
@@ -0,0 +1,184 @@
|
||||
# NX Journal: Capture Study Images for Atomizer Documentation
|
||||
#
|
||||
# Purpose: Capture top view and isometric view images of a part for study documentation
|
||||
# Usage: run_journal.exe capture_study_images.py -args "part_file_path" "output_directory" ["prefix"]
|
||||
#
|
||||
# Arguments:
|
||||
# part_file_path: Full path to the .prt file to capture
|
||||
# output_directory: Directory where images will be saved
|
||||
# prefix (optional): Prefix for image filenames (default: part name)
|
||||
#
|
||||
# Output:
|
||||
# {prefix}_Top.png - Top view image
|
||||
# {prefix}_iso.png - Isometric view image
|
||||
#
|
||||
# Author: Atomizer
|
||||
# Created: 2025-12-18
|
||||
|
||||
import sys
|
||||
import os
|
||||
import math
|
||||
import NXOpen
|
||||
import NXOpen.Gateway
|
||||
|
||||
|
||||
def capture_images(part_path: str, output_dir: str, prefix: str = None):
|
||||
"""
|
||||
Capture top view and isometric view images of a part.
|
||||
|
||||
Args:
|
||||
part_path: Full path to the .prt file
|
||||
output_dir: Directory to save images
|
||||
prefix: Optional prefix for image filenames
|
||||
"""
|
||||
theSession = NXOpen.Session.GetSession()
|
||||
|
||||
# Open the part if not already open
|
||||
try:
|
||||
workPart, loadStatus = theSession.Parts.OpenDisplay(part_path, NXOpen.Part.LoadStatically)
|
||||
loadStatus.Dispose()
|
||||
except:
|
||||
workPart = theSession.Parts.Work
|
||||
|
||||
if workPart is None:
|
||||
print(f"ERROR: Could not open part: {part_path}")
|
||||
return False
|
||||
|
||||
# Determine prefix from part name if not provided
|
||||
if prefix is None:
|
||||
prefix = os.path.splitext(os.path.basename(part_path))[0]
|
||||
|
||||
# Ensure output directory exists
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
# Hide construction geometry for cleaner images
|
||||
_hide_construction_geometry(theSession, workPart)
|
||||
|
||||
# Capture top view
|
||||
top_image_path = os.path.join(output_dir, f"{prefix}_Top.png")
|
||||
_capture_top_view(theSession, workPart, top_image_path)
|
||||
print(f"Saved: {top_image_path}")
|
||||
|
||||
# Capture isometric view
|
||||
iso_image_path = os.path.join(output_dir, f"{prefix}_iso.png")
|
||||
_capture_isometric_view(theSession, workPart, iso_image_path)
|
||||
print(f"Saved: {iso_image_path}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _hide_construction_geometry(theSession, workPart):
|
||||
"""Hide datums, curves, and sketches for cleaner visualization."""
|
||||
markId = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Hide Construction")
|
||||
|
||||
# Hide datums
|
||||
theSession.DisplayManager.HideByType("SHOW_HIDE_TYPE_DATUMS",
|
||||
NXOpen.DisplayManager.ShowHideScope.AnyInAssembly)
|
||||
|
||||
# Hide curves
|
||||
theSession.DisplayManager.HideByType("SHOW_HIDE_TYPE_CURVES",
|
||||
NXOpen.DisplayManager.ShowHideScope.AnyInAssembly)
|
||||
|
||||
# Hide sketches
|
||||
theSession.DisplayManager.HideByType("SHOW_HIDE_TYPE_SKETCHES",
|
||||
NXOpen.DisplayManager.ShowHideScope.AnyInAssembly)
|
||||
|
||||
theSession.UpdateManager.DoUpdate(markId)
|
||||
workPart.ModelingViews.WorkView.FitAfterShowOrHide(NXOpen.View.ShowOrHideType.HideOnly)
|
||||
theSession.DeleteUndoMark(markId, None)
|
||||
|
||||
|
||||
def _capture_top_view(theSession, workPart, output_path):
|
||||
"""Capture top view (looking down Z-axis)."""
|
||||
# Set top view orientation (looking down -Z)
|
||||
matrix = NXOpen.Matrix3x3()
|
||||
matrix.Xx = 0.0
|
||||
matrix.Xy = -1.0
|
||||
matrix.Xz = 0.0
|
||||
matrix.Yx = -1.0
|
||||
matrix.Yy = 0.0
|
||||
matrix.Yz = 0.0
|
||||
matrix.Zx = 0.0
|
||||
matrix.Zy = 0.0
|
||||
matrix.Zz = -1.0
|
||||
workPart.ModelingViews.WorkView.Orient(matrix)
|
||||
|
||||
# Fit view
|
||||
workPart.ModelingViews.WorkView.Fit()
|
||||
|
||||
# Export image
|
||||
_export_image(workPart, output_path)
|
||||
|
||||
|
||||
def _capture_isometric_view(theSession, workPart, output_path):
|
||||
"""Capture isometric view (standard ISO angle showing backface)."""
|
||||
# Set isometric orientation showing backface structure
|
||||
rotMatrix = NXOpen.Matrix3x3()
|
||||
rotMatrix.Xx = -0.32736574141345925
|
||||
rotMatrix.Xy = -0.94489752125198745
|
||||
rotMatrix.Xz = -0.00058794613984273266
|
||||
rotMatrix.Yx = -0.71924452681462514
|
||||
rotMatrix.Yy = 0.24959027079525001
|
||||
rotMatrix.Yz = -0.64837643955618585
|
||||
rotMatrix.Zx = 0.61279603621108569
|
||||
rotMatrix.Zy = -0.21183335680718612
|
||||
rotMatrix.Zz = -0.76131967460967154
|
||||
|
||||
# Get current scale and set orientation
|
||||
translation = NXOpen.Point3d(0, 0, 0)
|
||||
workPart.ModelingViews.WorkView.SetRotationTranslationScale(rotMatrix, translation, 0.25)
|
||||
|
||||
# Fit view
|
||||
workPart.ModelingViews.WorkView.Fit()
|
||||
|
||||
# Export image
|
||||
_export_image(workPart, output_path)
|
||||
|
||||
|
||||
def _export_image(workPart, output_path, width=1200, height=1000):
|
||||
"""Export current view as PNG image."""
|
||||
imageExportBuilder = workPart.Views.CreateImageExportBuilder()
|
||||
|
||||
try:
|
||||
# Configure export settings
|
||||
imageExportBuilder.RegionMode = False # Use entire view
|
||||
imageExportBuilder.DeviceWidth = width
|
||||
imageExportBuilder.DeviceHeight = height
|
||||
imageExportBuilder.FileFormat = NXOpen.Gateway.ImageExportBuilder.FileFormats.Png
|
||||
imageExportBuilder.FileName = output_path
|
||||
imageExportBuilder.BackgroundOption = NXOpen.Gateway.ImageExportBuilder.BackgroundOptions.Original
|
||||
imageExportBuilder.EnhanceEdges = False
|
||||
|
||||
# Commit export
|
||||
imageExportBuilder.Commit()
|
||||
finally:
|
||||
imageExportBuilder.Destroy()
|
||||
|
||||
|
||||
def main(args):
|
||||
"""Main entry point for journal."""
|
||||
if len(args) < 2:
|
||||
print("Usage: capture_study_images.py -args \"part_path\" \"output_dir\" [\"prefix\"]")
|
||||
print(" part_path: Full path to .prt file")
|
||||
print(" output_dir: Directory for output images")
|
||||
print(" prefix: Optional filename prefix (default: part name)")
|
||||
return
|
||||
|
||||
part_path = args[0]
|
||||
output_dir = args[1]
|
||||
prefix = args[2] if len(args) > 2 else None
|
||||
|
||||
print(f"Capturing images for: {part_path}")
|
||||
print(f"Output directory: {output_dir}")
|
||||
|
||||
success = capture_images(part_path, output_dir, prefix)
|
||||
|
||||
if success:
|
||||
print("Image capture complete!")
|
||||
else:
|
||||
print("Image capture failed!")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
111
nx_journals/extract_expressions.py
Normal file
111
nx_journals/extract_expressions.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""
|
||||
NX Journal Script to Extract All Expressions from a Part
|
||||
|
||||
Usage:
|
||||
run_journal.exe extract_expressions.py <prt_file_path> [output_dir]
|
||||
|
||||
Output:
|
||||
_temp_expressions.json with all expressions from the part
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import NXOpen
|
||||
|
||||
|
||||
def main(args):
|
||||
if len(args) < 1:
|
||||
print("ERROR: No .prt file path provided")
|
||||
return False
|
||||
|
||||
prt_file_path = args[0]
|
||||
output_dir = args[1] if len(args) > 1 else os.path.dirname(prt_file_path)
|
||||
|
||||
print(f"[JOURNAL] Extracting expressions from: {os.path.basename(prt_file_path)}")
|
||||
|
||||
results = {
|
||||
'part_file': os.path.basename(prt_file_path),
|
||||
'part_path': prt_file_path,
|
||||
'expressions': [],
|
||||
'expression_count': 0,
|
||||
'user_expression_count': 0,
|
||||
'success': False,
|
||||
'error': None
|
||||
}
|
||||
|
||||
try:
|
||||
theSession = NXOpen.Session.GetSession()
|
||||
|
||||
# Set load options
|
||||
working_dir = os.path.dirname(prt_file_path)
|
||||
theSession.Parts.LoadOptions.ComponentLoadMethod = NXOpen.LoadOptions.LoadMethod.FromDirectory
|
||||
theSession.Parts.LoadOptions.SetSearchDirectories([working_dir], [True])
|
||||
|
||||
# Open the part file
|
||||
print(f"[JOURNAL] Opening part file...")
|
||||
basePart, partLoadStatus = theSession.Parts.OpenActiveDisplay(
|
||||
prt_file_path,
|
||||
NXOpen.DisplayPartOption.AllowAdditional
|
||||
)
|
||||
partLoadStatus.Dispose()
|
||||
|
||||
workPart = theSession.Parts.Work
|
||||
print(f"[JOURNAL] Loaded part: {workPart.Name}")
|
||||
|
||||
# Extract all expressions
|
||||
print(f"[JOURNAL] Extracting expressions...")
|
||||
for expr in workPart.Expressions:
|
||||
try:
|
||||
expr_data = {
|
||||
'name': expr.Name,
|
||||
'value': expr.Value,
|
||||
'rhs': expr.RightHandSide if hasattr(expr, 'RightHandSide') else None,
|
||||
'units': expr.Units.Name if expr.Units else None,
|
||||
'type': str(expr.Type) if hasattr(expr, 'Type') else 'Unknown',
|
||||
}
|
||||
|
||||
# Check if it's a user expression (not internal p0, p1, etc.)
|
||||
is_internal = expr.Name.startswith('p') and len(expr.Name) > 1 and expr.Name[1:].replace('.', '').replace('_', '').isdigit()
|
||||
expr_data['is_internal'] = is_internal
|
||||
|
||||
results['expressions'].append(expr_data)
|
||||
|
||||
if not is_internal:
|
||||
results['user_expression_count'] += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"[JOURNAL] Warning: Could not read expression: {e}")
|
||||
|
||||
results['expression_count'] = len(results['expressions'])
|
||||
results['success'] = True
|
||||
|
||||
print(f"[JOURNAL] Found {results['expression_count']} total expressions")
|
||||
print(f"[JOURNAL] Found {results['user_expression_count']} user expressions")
|
||||
|
||||
# Print user expressions
|
||||
print(f"\n[JOURNAL] USER EXPRESSIONS:")
|
||||
print(f"[JOURNAL] " + "=" * 50)
|
||||
for expr in results['expressions']:
|
||||
if not expr['is_internal']:
|
||||
units_str = f" [{expr['units']}]" if expr['units'] else ""
|
||||
print(f"[JOURNAL] {expr['name']}: {expr['value']}{units_str}")
|
||||
|
||||
except Exception as e:
|
||||
results['error'] = str(e)
|
||||
results['success'] = False
|
||||
print(f"[JOURNAL] ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# Write results
|
||||
output_file = os.path.join(output_dir, "_temp_expressions.json")
|
||||
with open(output_file, 'w') as f:
|
||||
json.dump(results, f, indent=2)
|
||||
print(f"\n[JOURNAL] Results written to: {output_file}")
|
||||
|
||||
return results['success']
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
96
nx_journals/extract_expressions_standalone.py
Normal file
96
nx_journals/extract_expressions_standalone.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Standalone expression extractor - opens part and extracts all expressions
|
||||
Run with: ugraf.exe -run extract_expressions_standalone.py
|
||||
"""
|
||||
import NXOpen
|
||||
import os
|
||||
import json
|
||||
|
||||
def main():
|
||||
session = NXOpen.Session.GetSession()
|
||||
|
||||
part_path = r"C:\Users\antoi\Atomizer\studies\m1_mirror_cost_reduction\1_setup\model\M1_Blank.prt"
|
||||
output_json = r"C:\Users\antoi\Atomizer\_expressions_output.json"
|
||||
output_txt = r"C:\Users\antoi\Atomizer\_expressions_output.txt"
|
||||
|
||||
results = {'expressions': [], 'success': False, 'part': part_path}
|
||||
output_lines = []
|
||||
|
||||
try:
|
||||
# Set load options
|
||||
working_dir = os.path.dirname(part_path)
|
||||
session.Parts.LoadOptions.ComponentLoadMethod = NXOpen.LoadOptions.LoadMethod.FromDirectory
|
||||
session.Parts.LoadOptions.SetSearchDirectories([working_dir], [True])
|
||||
|
||||
# Open the part
|
||||
output_lines.append(f"Opening: {part_path}")
|
||||
basePart, loadStatus = session.Parts.OpenActiveDisplay(
|
||||
part_path,
|
||||
NXOpen.DisplayPartOption.AllowAdditional
|
||||
)
|
||||
loadStatus.Dispose()
|
||||
|
||||
workPart = session.Parts.Work
|
||||
output_lines.append(f"Loaded: {workPart.Name}")
|
||||
output_lines.append("")
|
||||
output_lines.append("=" * 60)
|
||||
output_lines.append("EXPRESSIONS IN M1_Blank.prt")
|
||||
output_lines.append("=" * 60)
|
||||
|
||||
# Extract expressions
|
||||
for expr in workPart.Expressions:
|
||||
try:
|
||||
name = expr.Name
|
||||
|
||||
# Skip internal expressions (p0, p1, p123, etc.)
|
||||
if name.startswith('p') and len(name) > 1:
|
||||
rest = name[1:]
|
||||
# Check if rest is numeric (possibly with dots for decimals)
|
||||
if rest.replace('.', '').replace('_', '').isdigit():
|
||||
continue
|
||||
|
||||
value = expr.Value
|
||||
units = expr.Units.Name if expr.Units else ''
|
||||
rhs = expr.RightHandSide if hasattr(expr, 'RightHandSide') else ''
|
||||
|
||||
results['expressions'].append({
|
||||
'name': name,
|
||||
'value': value,
|
||||
'units': units,
|
||||
'formula': rhs
|
||||
})
|
||||
|
||||
units_str = f" [{units}]" if units else ""
|
||||
output_lines.append(f"{name}: {value}{units_str}")
|
||||
|
||||
except Exception as e:
|
||||
output_lines.append(f"Error reading expression: {e}")
|
||||
|
||||
results['success'] = True
|
||||
results['count'] = len(results['expressions'])
|
||||
output_lines.append("")
|
||||
output_lines.append(f"Total user expressions: {results['count']}")
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
results['error'] = str(e)
|
||||
results['traceback'] = traceback.format_exc()
|
||||
output_lines.append(f"ERROR: {e}")
|
||||
output_lines.append(traceback.format_exc())
|
||||
|
||||
# Write outputs
|
||||
with open(output_json, 'w') as f:
|
||||
json.dump(results, f, indent=2)
|
||||
|
||||
with open(output_txt, 'w') as f:
|
||||
f.write('\n'.join(output_lines))
|
||||
|
||||
# Exit NX
|
||||
try:
|
||||
session.Parts.Work.Close(NXOpen.BasePart.CloseWholeTree.FalseValue,
|
||||
NXOpen.BasePart.CloseModified.CloseModified, None)
|
||||
except:
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
620
nx_journals/introspect_part.py
Normal file
620
nx_journals/introspect_part.py
Normal file
@@ -0,0 +1,620 @@
|
||||
"""
|
||||
NX Journal: Comprehensive Part Introspection Tool
|
||||
===================================================
|
||||
|
||||
This journal performs deep introspection of an NX .prt file and extracts:
|
||||
- All expressions (user and internal, with values, units, formulas)
|
||||
- Mass properties (mass, volume, surface area, center of gravity)
|
||||
- Material properties (name, density, all material attributes)
|
||||
- Body information (solid bodies, sheet bodies, body attributes)
|
||||
- Part attributes (all user-defined attributes)
|
||||
- Groups (all groups and their members)
|
||||
- Features (all features in the part)
|
||||
- References (linked parts, assembly components)
|
||||
- Datum planes, coordinate systems
|
||||
- Units system
|
||||
|
||||
Usage:
|
||||
run_journal.exe introspect_part.py <prt_file_path> [output_dir]
|
||||
|
||||
Output:
|
||||
_temp_introspection.json - Comprehensive JSON with all extracted data
|
||||
|
||||
Author: Atomizer
|
||||
Created: 2025-12-19
|
||||
Version: 1.0
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import NXOpen
|
||||
import NXOpen.UF
|
||||
|
||||
|
||||
def get_expressions(part):
|
||||
"""Extract all expressions from the part."""
|
||||
expressions = {
|
||||
'user': [],
|
||||
'internal': [],
|
||||
'total_count': 0,
|
||||
'user_count': 0
|
||||
}
|
||||
|
||||
try:
|
||||
for expr in part.Expressions:
|
||||
try:
|
||||
expr_data = {
|
||||
'name': expr.Name,
|
||||
'value': expr.Value,
|
||||
'rhs': expr.RightHandSide if hasattr(expr, 'RightHandSide') else None,
|
||||
'units': expr.Units.Name if expr.Units else None,
|
||||
'type': str(expr.Type) if hasattr(expr, 'Type') else 'Unknown',
|
||||
}
|
||||
|
||||
# Determine if internal (p0, p1, p123, etc.)
|
||||
name = expr.Name
|
||||
is_internal = False
|
||||
if name.startswith('p') and len(name) > 1:
|
||||
rest = name[1:].replace('.', '').replace('_', '')
|
||||
if rest.isdigit():
|
||||
is_internal = True
|
||||
|
||||
if is_internal:
|
||||
expressions['internal'].append(expr_data)
|
||||
else:
|
||||
expressions['user'].append(expr_data)
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
expressions['total_count'] = len(expressions['user']) + len(expressions['internal'])
|
||||
expressions['user_count'] = len(expressions['user'])
|
||||
|
||||
except Exception as e:
|
||||
expressions['error'] = str(e)
|
||||
|
||||
return expressions
|
||||
|
||||
|
||||
def get_all_solid_bodies(part):
|
||||
"""Get all solid bodies from the part."""
|
||||
bodies = []
|
||||
try:
|
||||
for body in part.Bodies:
|
||||
if body.IsSolidBody:
|
||||
bodies.append(body)
|
||||
except Exception as e:
|
||||
pass
|
||||
return bodies
|
||||
|
||||
|
||||
def get_mass_properties(part, bodies):
|
||||
"""Extract mass properties using MeasureManager."""
|
||||
results = {
|
||||
'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': len(bodies),
|
||||
'success': False
|
||||
}
|
||||
|
||||
if not bodies:
|
||||
return results
|
||||
|
||||
try:
|
||||
measureManager = part.MeasureManager
|
||||
bodyArray = list(bodies)
|
||||
|
||||
# Build mass_units array
|
||||
uc = part.UnitCollection
|
||||
mass_units = [
|
||||
uc.GetBase("Area"),
|
||||
uc.GetBase("Volume"),
|
||||
uc.GetBase("Mass"),
|
||||
uc.GetBase("Length")
|
||||
]
|
||||
|
||||
measureBodies = measureManager.NewMassProperties(mass_units, 0.99, bodyArray)
|
||||
|
||||
if measureBodies:
|
||||
try:
|
||||
results['mass_kg'] = measureBodies.Mass
|
||||
results['mass_g'] = results['mass_kg'] * 1000.0
|
||||
except:
|
||||
pass
|
||||
|
||||
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
|
||||
|
||||
results['success'] = True
|
||||
|
||||
try:
|
||||
measureBodies.Dispose()
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
results['error'] = str(e)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def get_materials(part, bodies):
|
||||
"""Extract all materials from the part."""
|
||||
materials = {
|
||||
'assigned': [],
|
||||
'available': [],
|
||||
'library': []
|
||||
}
|
||||
|
||||
# Get materials assigned to bodies
|
||||
for body in bodies:
|
||||
try:
|
||||
phys_mat = body.GetPhysicalMaterial()
|
||||
if phys_mat:
|
||||
mat_info = {
|
||||
'name': phys_mat.Name,
|
||||
'body': body.Name if hasattr(body, 'Name') else 'Unknown',
|
||||
'properties': {}
|
||||
}
|
||||
|
||||
# Try to get common material properties
|
||||
prop_names = ['Density', 'YoungModulus', 'PoissonRatio',
|
||||
'ThermalExpansionCoefficient', 'ThermalConductivity',
|
||||
'SpecificHeat', 'YieldStrength', 'UltimateStrength']
|
||||
for prop_name in prop_names:
|
||||
try:
|
||||
val = phys_mat.GetPropertyValue(prop_name)
|
||||
if val is not None:
|
||||
mat_info['properties'][prop_name] = float(val)
|
||||
except:
|
||||
pass
|
||||
|
||||
materials['assigned'].append(mat_info)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Get all materials in part via PhysicalMaterialManager
|
||||
try:
|
||||
pmm = part.PhysicalMaterialManager
|
||||
if pmm:
|
||||
all_mats = pmm.GetAllPhysicalMaterials()
|
||||
for mat in all_mats:
|
||||
try:
|
||||
mat_info = {
|
||||
'name': mat.Name,
|
||||
'properties': {}
|
||||
}
|
||||
prop_names = ['Density', 'YoungModulus', 'PoissonRatio']
|
||||
for prop_name in prop_names:
|
||||
try:
|
||||
val = mat.GetPropertyValue(prop_name)
|
||||
if val is not None:
|
||||
mat_info['properties'][prop_name] = float(val)
|
||||
except:
|
||||
pass
|
||||
materials['available'].append(mat_info)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
materials['pmm_error'] = str(e)
|
||||
|
||||
return materials
|
||||
|
||||
|
||||
def get_body_info(part):
|
||||
"""Get detailed body information."""
|
||||
body_info = {
|
||||
'solid_bodies': [],
|
||||
'sheet_bodies': [],
|
||||
'counts': {
|
||||
'solid': 0,
|
||||
'sheet': 0,
|
||||
'total': 0
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
for body in part.Bodies:
|
||||
body_data = {
|
||||
'name': body.Name if hasattr(body, 'Name') else 'Unknown',
|
||||
'is_solid': body.IsSolidBody,
|
||||
'is_sheet': body.IsSheetBody if hasattr(body, 'IsSheetBody') else False,
|
||||
'attributes': []
|
||||
}
|
||||
|
||||
# Get body attributes
|
||||
try:
|
||||
attrs = body.GetUserAttributes()
|
||||
for attr in attrs:
|
||||
try:
|
||||
body_data['attributes'].append({
|
||||
'title': attr.Title,
|
||||
'type': str(attr.Type),
|
||||
'value': attr.StringValue if hasattr(attr, 'StringValue') else str(attr.Value)
|
||||
})
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
if body.IsSolidBody:
|
||||
body_info['solid_bodies'].append(body_data)
|
||||
body_info['counts']['solid'] += 1
|
||||
else:
|
||||
body_info['sheet_bodies'].append(body_data)
|
||||
body_info['counts']['sheet'] += 1
|
||||
|
||||
body_info['counts']['total'] = body_info['counts']['solid'] + body_info['counts']['sheet']
|
||||
|
||||
except Exception as e:
|
||||
body_info['error'] = str(e)
|
||||
|
||||
return body_info
|
||||
|
||||
|
||||
def get_part_attributes(part):
|
||||
"""Get all part-level attributes."""
|
||||
attributes = []
|
||||
|
||||
try:
|
||||
attrs = part.GetUserAttributes()
|
||||
for attr in attrs:
|
||||
try:
|
||||
attr_data = {
|
||||
'title': attr.Title,
|
||||
'type': str(attr.Type),
|
||||
}
|
||||
|
||||
# Get value based on type
|
||||
try:
|
||||
if hasattr(attr, 'StringValue'):
|
||||
attr_data['value'] = attr.StringValue
|
||||
elif hasattr(attr, 'Value'):
|
||||
attr_data['value'] = attr.Value
|
||||
elif hasattr(attr, 'IntegerValue'):
|
||||
attr_data['value'] = attr.IntegerValue
|
||||
elif hasattr(attr, 'RealValue'):
|
||||
attr_data['value'] = attr.RealValue
|
||||
except:
|
||||
attr_data['value'] = 'Unknown'
|
||||
|
||||
attributes.append(attr_data)
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
return attributes
|
||||
|
||||
|
||||
def get_groups(part):
|
||||
"""Get all groups in the part."""
|
||||
groups = []
|
||||
|
||||
try:
|
||||
# NX stores groups in a collection
|
||||
if hasattr(part, 'Groups'):
|
||||
for group in part.Groups:
|
||||
try:
|
||||
group_data = {
|
||||
'name': group.Name if hasattr(group, 'Name') else 'Unknown',
|
||||
'member_count': 0,
|
||||
'members': []
|
||||
}
|
||||
|
||||
# Try to get group members
|
||||
try:
|
||||
members = group.GetMembers()
|
||||
group_data['member_count'] = len(members) if members else 0
|
||||
for member in members[:10]: # Limit to first 10 for readability
|
||||
try:
|
||||
group_data['members'].append(str(type(member).__name__))
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
groups.append(group_data)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
return groups
|
||||
|
||||
|
||||
def get_features(part):
|
||||
"""Get summary of features in the part."""
|
||||
features = {
|
||||
'total_count': 0,
|
||||
'by_type': {},
|
||||
'first_10': []
|
||||
}
|
||||
|
||||
try:
|
||||
count = 0
|
||||
for feature in part.Features:
|
||||
try:
|
||||
feat_type = str(type(feature).__name__)
|
||||
|
||||
# Count by type
|
||||
if feat_type in features['by_type']:
|
||||
features['by_type'][feat_type] += 1
|
||||
else:
|
||||
features['by_type'][feat_type] = 1
|
||||
|
||||
# Store first 10 for reference
|
||||
if count < 10:
|
||||
features['first_10'].append({
|
||||
'name': feature.Name if hasattr(feature, 'Name') else 'Unknown',
|
||||
'type': feat_type
|
||||
})
|
||||
|
||||
count += 1
|
||||
except:
|
||||
pass
|
||||
|
||||
features['total_count'] = count
|
||||
|
||||
except Exception as e:
|
||||
features['error'] = str(e)
|
||||
|
||||
return features
|
||||
|
||||
|
||||
def get_datums(part):
|
||||
"""Get datum planes and coordinate systems."""
|
||||
datums = {
|
||||
'planes': [],
|
||||
'csys': [],
|
||||
'axes': []
|
||||
}
|
||||
|
||||
try:
|
||||
# Datum planes
|
||||
if hasattr(part, 'Datums'):
|
||||
for datum in part.Datums:
|
||||
try:
|
||||
datum_type = str(type(datum).__name__)
|
||||
datum_name = datum.Name if hasattr(datum, 'Name') else 'Unknown'
|
||||
|
||||
if 'Plane' in datum_type:
|
||||
datums['planes'].append(datum_name)
|
||||
elif 'Csys' in datum_type or 'Coordinate' in datum_type:
|
||||
datums['csys'].append(datum_name)
|
||||
elif 'Axis' in datum_type:
|
||||
datums['axes'].append(datum_name)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
datums['error'] = str(e)
|
||||
|
||||
return datums
|
||||
|
||||
|
||||
def get_units_info(part):
|
||||
"""Get unit system information."""
|
||||
units_info = {
|
||||
'base_units': {},
|
||||
'system': 'Unknown'
|
||||
}
|
||||
|
||||
try:
|
||||
uc = part.UnitCollection
|
||||
|
||||
# Get common base units
|
||||
unit_types = ['Length', 'Mass', 'Time', 'Temperature', 'Angle',
|
||||
'Area', 'Volume', 'Force', 'Pressure', 'Density']
|
||||
for unit_type in unit_types:
|
||||
try:
|
||||
base_unit = uc.GetBase(unit_type)
|
||||
if base_unit:
|
||||
units_info['base_units'][unit_type] = base_unit.Name
|
||||
except:
|
||||
pass
|
||||
|
||||
# Determine system from length unit
|
||||
if 'Length' in units_info['base_units']:
|
||||
length_unit = units_info['base_units']['Length'].lower()
|
||||
if 'mm' in length_unit or 'millimeter' in length_unit:
|
||||
units_info['system'] = 'Metric (mm)'
|
||||
elif 'meter' in length_unit and 'milli' not in length_unit:
|
||||
units_info['system'] = 'Metric (m)'
|
||||
elif 'inch' in length_unit or 'in' in length_unit:
|
||||
units_info['system'] = 'Imperial (inch)'
|
||||
|
||||
except Exception as e:
|
||||
units_info['error'] = str(e)
|
||||
|
||||
return units_info
|
||||
|
||||
|
||||
def get_linked_parts(theSession, working_dir):
|
||||
"""Get information about linked/associated parts."""
|
||||
linked_parts = {
|
||||
'loaded_parts': [],
|
||||
'fem_parts': [],
|
||||
'sim_parts': [],
|
||||
'idealized_parts': []
|
||||
}
|
||||
|
||||
try:
|
||||
for part in theSession.Parts:
|
||||
try:
|
||||
part_name = part.Name if hasattr(part, 'Name') else str(part)
|
||||
part_path = part.FullPath if hasattr(part, 'FullPath') else 'Unknown'
|
||||
|
||||
part_info = {
|
||||
'name': part_name,
|
||||
'path': part_path,
|
||||
'leaf_name': part.Leaf if hasattr(part, 'Leaf') else part_name
|
||||
}
|
||||
|
||||
name_lower = part_name.lower()
|
||||
if '_sim' in name_lower or name_lower.endswith('.sim'):
|
||||
linked_parts['sim_parts'].append(part_info)
|
||||
elif '_fem' in name_lower or name_lower.endswith('.fem'):
|
||||
if '_i.prt' in name_lower or '_i' in name_lower:
|
||||
linked_parts['idealized_parts'].append(part_info)
|
||||
else:
|
||||
linked_parts['fem_parts'].append(part_info)
|
||||
elif '_i.prt' in name_lower:
|
||||
linked_parts['idealized_parts'].append(part_info)
|
||||
else:
|
||||
linked_parts['loaded_parts'].append(part_info)
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
linked_parts['error'] = str(e)
|
||||
|
||||
return linked_parts
|
||||
|
||||
|
||||
def main(args):
|
||||
"""Main entry point for NX journal."""
|
||||
|
||||
if len(args) < 1:
|
||||
print("ERROR: No .prt file path provided")
|
||||
print("Usage: run_journal.exe introspect_part.py <prt_file> [output_dir]")
|
||||
return False
|
||||
|
||||
prt_file_path = args[0]
|
||||
output_dir = args[1] if len(args) > 1 else os.path.dirname(prt_file_path)
|
||||
prt_filename = os.path.basename(prt_file_path)
|
||||
|
||||
print(f"[INTROSPECT] " + "="*60)
|
||||
print(f"[INTROSPECT] NX COMPREHENSIVE PART INTROSPECTION")
|
||||
print(f"[INTROSPECT] " + "="*60)
|
||||
print(f"[INTROSPECT] Part: {prt_filename}")
|
||||
print(f"[INTROSPECT] Output: {output_dir}")
|
||||
|
||||
results = {
|
||||
'part_file': prt_filename,
|
||||
'part_path': prt_file_path,
|
||||
'success': False,
|
||||
'error': None,
|
||||
'expressions': {},
|
||||
'mass_properties': {},
|
||||
'materials': {},
|
||||
'bodies': {},
|
||||
'attributes': [],
|
||||
'groups': [],
|
||||
'features': {},
|
||||
'datums': {},
|
||||
'units': {},
|
||||
'linked_parts': {}
|
||||
}
|
||||
|
||||
try:
|
||||
theSession = NXOpen.Session.GetSession()
|
||||
|
||||
# Set load options
|
||||
working_dir = os.path.dirname(prt_file_path)
|
||||
theSession.Parts.LoadOptions.ComponentLoadMethod = NXOpen.LoadOptions.LoadMethod.FromDirectory
|
||||
theSession.Parts.LoadOptions.SetSearchDirectories([working_dir], [True])
|
||||
|
||||
# Open the part file
|
||||
print(f"[INTROSPECT] Opening part file...")
|
||||
basePart, partLoadStatus = theSession.Parts.OpenActiveDisplay(
|
||||
prt_file_path,
|
||||
NXOpen.DisplayPartOption.AllowAdditional
|
||||
)
|
||||
partLoadStatus.Dispose()
|
||||
|
||||
workPart = theSession.Parts.Work
|
||||
print(f"[INTROSPECT] Loaded: {workPart.Name}")
|
||||
|
||||
# Extract all data
|
||||
print(f"[INTROSPECT] Extracting expressions...")
|
||||
results['expressions'] = get_expressions(workPart)
|
||||
print(f"[INTROSPECT] Found {results['expressions']['user_count']} user expressions")
|
||||
|
||||
print(f"[INTROSPECT] Extracting body info...")
|
||||
results['bodies'] = get_body_info(workPart)
|
||||
print(f"[INTROSPECT] Found {results['bodies']['counts']['solid']} solid bodies")
|
||||
|
||||
print(f"[INTROSPECT] Extracting mass properties...")
|
||||
bodies = get_all_solid_bodies(workPart)
|
||||
results['mass_properties'] = get_mass_properties(workPart, bodies)
|
||||
print(f"[INTROSPECT] Mass: {results['mass_properties']['mass_kg']:.4f} kg")
|
||||
|
||||
print(f"[INTROSPECT] Extracting materials...")
|
||||
results['materials'] = get_materials(workPart, bodies)
|
||||
print(f"[INTROSPECT] Found {len(results['materials']['assigned'])} assigned materials")
|
||||
|
||||
print(f"[INTROSPECT] Extracting attributes...")
|
||||
results['attributes'] = get_part_attributes(workPart)
|
||||
print(f"[INTROSPECT] Found {len(results['attributes'])} part attributes")
|
||||
|
||||
print(f"[INTROSPECT] Extracting groups...")
|
||||
results['groups'] = get_groups(workPart)
|
||||
print(f"[INTROSPECT] Found {len(results['groups'])} groups")
|
||||
|
||||
print(f"[INTROSPECT] Extracting features...")
|
||||
results['features'] = get_features(workPart)
|
||||
print(f"[INTROSPECT] Found {results['features']['total_count']} features")
|
||||
|
||||
print(f"[INTROSPECT] Extracting datums...")
|
||||
results['datums'] = get_datums(workPart)
|
||||
print(f"[INTROSPECT] Found {len(results['datums']['planes'])} datum planes")
|
||||
|
||||
print(f"[INTROSPECT] Extracting units...")
|
||||
results['units'] = get_units_info(workPart)
|
||||
print(f"[INTROSPECT] System: {results['units']['system']}")
|
||||
|
||||
print(f"[INTROSPECT] Extracting linked parts...")
|
||||
results['linked_parts'] = get_linked_parts(theSession, working_dir)
|
||||
print(f"[INTROSPECT] Found {len(results['linked_parts']['loaded_parts'])} loaded parts")
|
||||
|
||||
results['success'] = True
|
||||
print(f"[INTROSPECT] ")
|
||||
print(f"[INTROSPECT] INTROSPECTION COMPLETE!")
|
||||
print(f"[INTROSPECT] " + "="*60)
|
||||
|
||||
# Summary
|
||||
print(f"[INTROSPECT] SUMMARY:")
|
||||
print(f"[INTROSPECT] Expressions: {results['expressions']['user_count']} user, {len(results['expressions']['internal'])} internal")
|
||||
print(f"[INTROSPECT] Mass: {results['mass_properties']['mass_kg']:.4f} kg ({results['mass_properties']['mass_g']:.2f} g)")
|
||||
print(f"[INTROSPECT] Bodies: {results['bodies']['counts']['solid']} solid, {results['bodies']['counts']['sheet']} sheet")
|
||||
print(f"[INTROSPECT] Features: {results['features']['total_count']}")
|
||||
print(f"[INTROSPECT] Materials: {len(results['materials']['assigned'])} assigned")
|
||||
|
||||
except Exception as e:
|
||||
results['error'] = str(e)
|
||||
results['success'] = False
|
||||
print(f"[INTROSPECT] FATAL ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# Write results
|
||||
output_file = os.path.join(output_dir, "_temp_introspection.json")
|
||||
with open(output_file, 'w') as f:
|
||||
json.dump(results, f, indent=2)
|
||||
print(f"[INTROSPECT] Results written to: {output_file}")
|
||||
|
||||
return results['success']
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
55
nx_journals/list_expressions_simple.py
Normal file
55
nx_journals/list_expressions_simple.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""Simple expression lister - writes to file regardless of print issues"""
|
||||
import NXOpen
|
||||
import os
|
||||
import json
|
||||
|
||||
session = NXOpen.Session.GetSession()
|
||||
output_lines = []
|
||||
results = {'expressions': [], 'success': False}
|
||||
|
||||
try:
|
||||
# Get all open parts and find M1_Blank
|
||||
for part in session.Parts:
|
||||
part_name = part.Name if hasattr(part, 'Name') else str(part)
|
||||
if 'M1_Blank' in part_name and '_fem' not in part_name.lower() and '_i' not in part_name.lower():
|
||||
output_lines.append(f"Found part: {part_name}")
|
||||
|
||||
for expr in part.Expressions:
|
||||
try:
|
||||
name = expr.Name
|
||||
# Skip internal expressions (p0, p1, etc.)
|
||||
if name.startswith('p') and len(name) > 1:
|
||||
rest = name[1:].replace('.', '').replace('_', '')
|
||||
if rest.isdigit():
|
||||
continue
|
||||
|
||||
value = expr.Value
|
||||
units = expr.Units.Name if expr.Units else ''
|
||||
rhs = expr.RightHandSide if hasattr(expr, 'RightHandSide') else ''
|
||||
|
||||
results['expressions'].append({
|
||||
'name': name,
|
||||
'value': value,
|
||||
'units': units,
|
||||
'rhs': rhs
|
||||
})
|
||||
output_lines.append(f"{name}: {value} {units}")
|
||||
except:
|
||||
pass
|
||||
|
||||
results['success'] = True
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
output_lines.append(f"Error: {str(e)}")
|
||||
results['error'] = str(e)
|
||||
|
||||
# Write to file
|
||||
output_path = r"C:\Users\antoi\Atomizer\_expressions_output.json"
|
||||
with open(output_path, 'w') as f:
|
||||
json.dump(results, f, indent=2)
|
||||
|
||||
# Also write text version
|
||||
text_path = r"C:\Users\antoi\Atomizer\_expressions_output.txt"
|
||||
with open(text_path, 'w') as f:
|
||||
f.write('\n'.join(output_lines))
|
||||
11
nx_journals/test_write.py
Normal file
11
nx_journals/test_write.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Simple test - just write a file"""
|
||||
with open(r"C:\Users\antoi\Atomizer\_test_output.txt", 'w') as f:
|
||||
f.write("Journal executed successfully!\n")
|
||||
|
||||
try:
|
||||
import NXOpen
|
||||
f.write("NXOpen imported OK\n")
|
||||
session = NXOpen.Session.GetSession()
|
||||
f.write(f"Session: {session}\n")
|
||||
except Exception as e:
|
||||
f.write(f"NXOpen error: {e}\n")
|
||||
@@ -0,0 +1,229 @@
|
||||
# Designcenter 2512
|
||||
# Journal created by antoi on Thu Dec 18 14:06:36 2025 Eastern Standard Time
|
||||
#
|
||||
import math
|
||||
import NXOpen
|
||||
import NXOpen.Gateway
|
||||
def main(args) :
|
||||
|
||||
theSession = NXOpen.Session.GetSession() #type: NXOpen.Session
|
||||
workPart = theSession.Parts.Work
|
||||
displayPart = theSession.Parts.Display
|
||||
# ----------------------------------------------
|
||||
# Menu: Edit->Show and Hide->Show and Hide...
|
||||
# ----------------------------------------------
|
||||
markId1 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start")
|
||||
|
||||
theSession.SetUndoMarkName(markId1, "Show and Hide Dialog")
|
||||
|
||||
markId2 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Hide Datums")
|
||||
|
||||
numberHidden1 = theSession.DisplayManager.HideByType("SHOW_HIDE_TYPE_DATUMS", NXOpen.DisplayManager.ShowHideScope.AnyInAssembly)
|
||||
|
||||
nErrs1 = theSession.UpdateManager.DoUpdate(markId2)
|
||||
|
||||
workPart.ModelingViews.WorkView.FitAfterShowOrHide(NXOpen.View.ShowOrHideType.HideOnly)
|
||||
|
||||
markId3 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Hide Curves")
|
||||
|
||||
numberHidden2 = theSession.DisplayManager.HideByType("SHOW_HIDE_TYPE_CURVES", NXOpen.DisplayManager.ShowHideScope.AnyInAssembly)
|
||||
|
||||
nErrs2 = theSession.UpdateManager.DoUpdate(markId3)
|
||||
|
||||
exists1 = theSession.DoesUndoMarkExist(markId3, "Hide Curves")
|
||||
|
||||
theSession.DeleteUndoMark(markId3, "Hide Curves")
|
||||
|
||||
workPart.ModelingViews.WorkView.FitAfterShowOrHide(NXOpen.View.ShowOrHideType.HideOnly)
|
||||
|
||||
markId4 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Hide Sketches")
|
||||
|
||||
numberHidden3 = theSession.DisplayManager.HideByType("SHOW_HIDE_TYPE_SKETCHES", NXOpen.DisplayManager.ShowHideScope.AnyInAssembly)
|
||||
|
||||
nErrs3 = theSession.UpdateManager.DoUpdate(markId4)
|
||||
|
||||
workPart.ModelingViews.WorkView.FitAfterShowOrHide(NXOpen.View.ShowOrHideType.HideOnly)
|
||||
|
||||
theSession.SetUndoMarkName(markId1, "Show and Hide")
|
||||
|
||||
theSession.DeleteUndoMark(markId1, None)
|
||||
|
||||
matrix1 = NXOpen.Matrix3x3()
|
||||
|
||||
matrix1.Xx = 0.0
|
||||
matrix1.Xy = -1.0
|
||||
matrix1.Xz = 0.0
|
||||
matrix1.Yx = -1.0
|
||||
matrix1.Yy = -0.0
|
||||
matrix1.Yz = -0.0
|
||||
matrix1.Zx = 0.0
|
||||
matrix1.Zy = 0.0
|
||||
matrix1.Zz = -1.0
|
||||
workPart.ModelingViews.WorkView.Orient(matrix1)
|
||||
|
||||
scaleAboutPoint1 = NXOpen.Point3d(-759.81281858578541, -319.30527689743337, 0.0)
|
||||
viewCenter1 = NXOpen.Point3d(759.81281858579484, 319.30527689744417, 0.0)
|
||||
workPart.ModelingViews.WorkView.ZoomAboutPoint(0.80000000000000004, scaleAboutPoint1, viewCenter1)
|
||||
|
||||
scaleAboutPoint2 = NXOpen.Point3d(-949.76602323223278, -399.13159612179305, 0.0)
|
||||
viewCenter2 = NXOpen.Point3d(949.76602323224245, 399.13159612180385, 0.0)
|
||||
workPart.ModelingViews.WorkView.ZoomAboutPoint(0.80000000000000004, scaleAboutPoint2, viewCenter2)
|
||||
|
||||
scaleAboutPoint3 = NXOpen.Point3d(-1394.8708922057567, -214.19365760462478, 0.0)
|
||||
viewCenter3 = NXOpen.Point3d(1394.870892205766, 214.19365760463569, 0.0)
|
||||
workPart.ModelingViews.WorkView.ZoomAboutPoint(1.25, scaleAboutPoint3, viewCenter3)
|
||||
|
||||
scaleAboutPoint4 = NXOpen.Point3d(-1115.8967137646043, -171.35492608369873, 0.0)
|
||||
viewCenter4 = NXOpen.Point3d(1115.8967137646139, 171.35492608370959, 0.0)
|
||||
workPart.ModelingViews.WorkView.ZoomAboutPoint(0.80000000000000004, scaleAboutPoint4, viewCenter4)
|
||||
|
||||
# ----------------------------------------------
|
||||
# Menu: File->Export->Image...
|
||||
# ----------------------------------------------
|
||||
markId5 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start")
|
||||
|
||||
imageExportBuilder1 = workPart.Views.CreateImageExportBuilder()
|
||||
|
||||
imageExportBuilder1.RegionMode = True
|
||||
|
||||
regiontopleftpoint1 = [None] * 2
|
||||
regiontopleftpoint1[0] = 95
|
||||
regiontopleftpoint1[1] = 83
|
||||
imageExportBuilder1.SetRegionTopLeftPoint(regiontopleftpoint1)
|
||||
|
||||
imageExportBuilder1.RegionWidth = 1157
|
||||
|
||||
imageExportBuilder1.RegionHeight = 1056
|
||||
|
||||
imageExportBuilder1.DeviceWidth = 2388
|
||||
|
||||
imageExportBuilder1.DeviceHeight = 1172
|
||||
|
||||
imageExportBuilder1.FileFormat = NXOpen.Gateway.ImageExportBuilder.FileFormats.Png
|
||||
|
||||
imageExportBuilder1.FileName = "C:\\Users\\antoi\\Atomizer\\studies\\M1_Mirror\\m1_mirror_cost_reduction_V4\\1_setup\\M1_Blank_Top.png"
|
||||
|
||||
imageExportBuilder1.BackgroundOption = NXOpen.Gateway.ImageExportBuilder.BackgroundOptions.Original
|
||||
|
||||
imageExportBuilder1.EnhanceEdges = False
|
||||
|
||||
nXObject1 = imageExportBuilder1.Commit()
|
||||
|
||||
theSession.DeleteUndoMark(markId5, "Export Image")
|
||||
|
||||
imageExportBuilder1.Destroy()
|
||||
|
||||
markId6 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start")
|
||||
|
||||
imageExportBuilder2 = workPart.Views.CreateImageExportBuilder()
|
||||
|
||||
imageExportBuilder2.Destroy()
|
||||
|
||||
theSession.UndoToMark(markId6, None)
|
||||
|
||||
theSession.DeleteUndoMark(markId6, None)
|
||||
|
||||
rotMatrix1 = NXOpen.Matrix3x3()
|
||||
|
||||
rotMatrix1.Xx = -0.34262722569067999
|
||||
rotMatrix1.Xy = -0.93944302509010613
|
||||
rotMatrix1.Xz = 0.0073066288434778118
|
||||
rotMatrix1.Yx = -0.67329035687890959
|
||||
rotMatrix1.Yy = 0.24011894541756998
|
||||
rotMatrix1.Yz = -0.69930178563008338
|
||||
rotMatrix1.Zx = 0.65519972493078527
|
||||
rotMatrix1.Zy = -0.2445193134725811
|
||||
rotMatrix1.Zz = -0.71478921773451431
|
||||
translation1 = NXOpen.Point3d(-691.94814615291523, -16.771832954225655, -903.92900031772103)
|
||||
workPart.ModelingViews.WorkView.SetRotationTranslationScale(rotMatrix1, translation1, 0.20258147300869808)
|
||||
|
||||
scaleAboutPoint5 = NXOpen.Point3d(-1091.8652302284754, -297.78142642594378, 0.0)
|
||||
viewCenter5 = NXOpen.Point3d(1091.8652302284847, 297.78142642595469, 0.0)
|
||||
workPart.ModelingViews.WorkView.ZoomAboutPoint(1.25, scaleAboutPoint5, viewCenter5)
|
||||
|
||||
scaleAboutPoint6 = NXOpen.Point3d(-873.49218418277917, -238.22514114075392, 0.0)
|
||||
viewCenter6 = NXOpen.Point3d(873.49218418278895, 238.2251411407648, 0.0)
|
||||
workPart.ModelingViews.WorkView.ZoomAboutPoint(1.25, scaleAboutPoint6, viewCenter6)
|
||||
|
||||
scaleAboutPoint7 = NXOpen.Point3d(-519.08004438038643, -302.5877231331695, 0.0)
|
||||
viewCenter7 = NXOpen.Point3d(519.08004438039586, 302.58772313318048, 0.0)
|
||||
workPart.ModelingViews.WorkView.ZoomAboutPoint(0.80000000000000004, scaleAboutPoint7, viewCenter7)
|
||||
|
||||
scaleAboutPoint8 = NXOpen.Point3d(-648.85005547548417, -378.23465391646323, 0.0)
|
||||
viewCenter8 = NXOpen.Point3d(648.85005547549372, 378.23465391647414, 0.0)
|
||||
workPart.ModelingViews.WorkView.ZoomAboutPoint(0.80000000000000004, scaleAboutPoint8, viewCenter8)
|
||||
|
||||
scaleAboutPoint9 = NXOpen.Point3d(-726.16874163520447, -271.6602486692816, 0.0)
|
||||
viewCenter9 = NXOpen.Point3d(726.16874163521379, 271.66024866929223, 0.0)
|
||||
workPart.ModelingViews.WorkView.ZoomAboutPoint(1.25, scaleAboutPoint9, viewCenter9)
|
||||
|
||||
rotMatrix2 = NXOpen.Matrix3x3()
|
||||
|
||||
rotMatrix2.Xx = -0.35281096074613638
|
||||
rotMatrix2.Xy = -0.93549939803135751
|
||||
rotMatrix2.Xz = 0.019112882052533756
|
||||
rotMatrix2.Yx = -0.67083068516183819
|
||||
rotMatrix2.Yy = 0.23864906945399289
|
||||
rotMatrix2.Yz = -0.70216295366107118
|
||||
rotMatrix2.Zx = 0.65231174895343103
|
||||
rotMatrix2.Zy = -0.26055229404422597
|
||||
rotMatrix2.Zz = -0.71175970962509794
|
||||
translation2 = NXOpen.Point3d(-445.60899304577225, -25.448049758528374, -903.92478002019129)
|
||||
workPart.ModelingViews.WorkView.SetRotationTranslationScale(rotMatrix2, translation2, 0.25322684126087264)
|
||||
|
||||
rotMatrix3 = NXOpen.Matrix3x3()
|
||||
|
||||
rotMatrix3.Xx = -0.32736574141345925
|
||||
rotMatrix3.Xy = -0.94489752125198745
|
||||
rotMatrix3.Xz = -0.00058794613984273266
|
||||
rotMatrix3.Yx = -0.71924452681462514
|
||||
rotMatrix3.Yy = 0.24959027079525001
|
||||
rotMatrix3.Yz = -0.64837643955618585
|
||||
rotMatrix3.Zx = 0.61279603621108569
|
||||
rotMatrix3.Zy = -0.21183335680718612
|
||||
rotMatrix3.Zz = -0.76131967460967154
|
||||
translation3 = NXOpen.Point3d(-445.6364375527848, -25.373121722553414, -903.99382020435428)
|
||||
workPart.ModelingViews.WorkView.SetRotationTranslationScale(rotMatrix3, translation3, 0.25322684126087264)
|
||||
|
||||
# ----------------------------------------------
|
||||
# Menu: File->Export->Image...
|
||||
# ----------------------------------------------
|
||||
markId7 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start")
|
||||
|
||||
imageExportBuilder3 = workPart.Views.CreateImageExportBuilder()
|
||||
|
||||
imageExportBuilder3.RegionMode = True
|
||||
|
||||
regiontopleftpoint2 = [None] * 2
|
||||
regiontopleftpoint2[0] = 129
|
||||
regiontopleftpoint2[1] = 96
|
||||
imageExportBuilder3.SetRegionTopLeftPoint(regiontopleftpoint2)
|
||||
|
||||
imageExportBuilder3.RegionWidth = 1343
|
||||
|
||||
imageExportBuilder3.RegionHeight = 1045
|
||||
|
||||
imageExportBuilder3.DeviceWidth = 2388
|
||||
|
||||
imageExportBuilder3.DeviceHeight = 1172
|
||||
|
||||
imageExportBuilder3.FileFormat = NXOpen.Gateway.ImageExportBuilder.FileFormats.Png
|
||||
|
||||
imageExportBuilder3.FileName = "C:\\Users\\antoi\\Atomizer\\studies\\M1_Mirror\\m1_mirror_cost_reduction_V4\\1_setup\\M1_Blank_iso.png"
|
||||
|
||||
imageExportBuilder3.BackgroundOption = NXOpen.Gateway.ImageExportBuilder.BackgroundOptions.Original
|
||||
|
||||
imageExportBuilder3.EnhanceEdges = False
|
||||
|
||||
nXObject2 = imageExportBuilder3.Commit()
|
||||
|
||||
theSession.DeleteUndoMark(markId7, "Export Image")
|
||||
|
||||
imageExportBuilder3.Destroy()
|
||||
|
||||
# ----------------------------------------------
|
||||
# Menu: Tools->Automation->Journal->Stop Recording
|
||||
# ----------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
Reference in New Issue
Block a user