400 lines
12 KiB
Python
400 lines
12 KiB
Python
|
|
"""
|
||
|
|
NX Open Hooks - Usage Examples
|
||
|
|
==============================
|
||
|
|
|
||
|
|
This file contains practical examples of using the NX Open hooks
|
||
|
|
for common optimization tasks.
|
||
|
|
|
||
|
|
Run examples:
|
||
|
|
python -m optimization_engine.hooks.examples
|
||
|
|
|
||
|
|
Or import specific examples:
|
||
|
|
from optimization_engine.hooks.examples import design_exploration_example
|
||
|
|
"""
|
||
|
|
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
# Add project root to path
|
||
|
|
project_root = Path(__file__).parent.parent.parent
|
||
|
|
sys.path.insert(0, str(project_root))
|
||
|
|
|
||
|
|
from optimization_engine.hooks.nx_cad import (
|
||
|
|
part_manager,
|
||
|
|
expression_manager,
|
||
|
|
geometry_query,
|
||
|
|
feature_manager,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Example 1: Basic Expression Query
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
def basic_expression_query(part_path: str):
|
||
|
|
"""
|
||
|
|
Example: Query all expressions from an NX part.
|
||
|
|
|
||
|
|
This is useful for discovering available design parameters
|
||
|
|
before setting up an optimization study.
|
||
|
|
"""
|
||
|
|
print("\n" + "=" * 60)
|
||
|
|
print("Example 1: Basic Expression Query")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
result = expression_manager.get_expressions(part_path)
|
||
|
|
|
||
|
|
if not result["success"]:
|
||
|
|
print(f"ERROR: {result['error']}")
|
||
|
|
return None
|
||
|
|
|
||
|
|
data = result["data"]
|
||
|
|
print(f"\nFound {data['count']} expressions:\n")
|
||
|
|
|
||
|
|
# Print in a nice table format
|
||
|
|
print(f"{'Name':<25} {'Value':>12} {'Units':<15} {'RHS'}")
|
||
|
|
print("-" * 70)
|
||
|
|
|
||
|
|
for name, expr in data["expressions"].items():
|
||
|
|
units = expr.get("units") or ""
|
||
|
|
rhs = expr.get("rhs", "")
|
||
|
|
# Truncate RHS if it's a formula reference
|
||
|
|
if len(rhs) > 20:
|
||
|
|
rhs = rhs[:17] + "..."
|
||
|
|
print(f"{name:<25} {expr['value']:>12.4f} {units:<15} {rhs}")
|
||
|
|
|
||
|
|
return data["expressions"]
|
||
|
|
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Example 2: Mass Properties Extraction
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
def mass_properties_example(part_path: str):
|
||
|
|
"""
|
||
|
|
Example: Extract mass properties from an NX part.
|
||
|
|
|
||
|
|
This is useful for mass optimization objectives.
|
||
|
|
"""
|
||
|
|
print("\n" + "=" * 60)
|
||
|
|
print("Example 2: Mass Properties Extraction")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
result = geometry_query.get_mass_properties(part_path)
|
||
|
|
|
||
|
|
if not result["success"]:
|
||
|
|
print(f"ERROR: {result['error']}")
|
||
|
|
return None
|
||
|
|
|
||
|
|
data = result["data"]
|
||
|
|
|
||
|
|
print(f"\nMass Properties:")
|
||
|
|
print("-" * 40)
|
||
|
|
print(f" Mass: {data['mass']:.6f} {data['mass_unit']}")
|
||
|
|
print(f" Volume: {data['volume']:.2f} {data['volume_unit']}")
|
||
|
|
print(f" Surface Area: {data['surface_area']:.2f} {data['area_unit']}")
|
||
|
|
print(f" Material: {data['material'] or 'Not assigned'}")
|
||
|
|
|
||
|
|
centroid = data["centroid"]
|
||
|
|
print(f"\nCentroid (mm):")
|
||
|
|
print(f" X: {centroid['x']:.4f}")
|
||
|
|
print(f" Y: {centroid['y']:.4f}")
|
||
|
|
print(f" Z: {centroid['z']:.4f}")
|
||
|
|
|
||
|
|
if data.get("principal_moments"):
|
||
|
|
pm = data["principal_moments"]
|
||
|
|
print(f"\nPrincipal Moments of Inertia ({pm['unit']}):")
|
||
|
|
print(f" Ixx: {pm['Ixx']:.4f}")
|
||
|
|
print(f" Iyy: {pm['Iyy']:.4f}")
|
||
|
|
print(f" Izz: {pm['Izz']:.4f}")
|
||
|
|
|
||
|
|
return data
|
||
|
|
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Example 3: Design Parameter Update
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
def design_update_example(part_path: str, dry_run: bool = True):
|
||
|
|
"""
|
||
|
|
Example: Update design parameters in an NX part.
|
||
|
|
|
||
|
|
This demonstrates the workflow for parametric optimization:
|
||
|
|
1. Read current values
|
||
|
|
2. Compute new values
|
||
|
|
3. Update the model
|
||
|
|
|
||
|
|
Args:
|
||
|
|
part_path: Path to the NX part
|
||
|
|
dry_run: If True, only shows what would be changed (default)
|
||
|
|
"""
|
||
|
|
print("\n" + "=" * 60)
|
||
|
|
print("Example 3: Design Parameter Update")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
# Step 1: Get current expressions
|
||
|
|
result = expression_manager.get_expressions(part_path)
|
||
|
|
if not result["success"]:
|
||
|
|
print(f"ERROR: {result['error']}")
|
||
|
|
return None
|
||
|
|
|
||
|
|
expressions = result["data"]["expressions"]
|
||
|
|
|
||
|
|
# Step 2: Find numeric expressions (potential design variables)
|
||
|
|
design_vars = {}
|
||
|
|
for name, expr in expressions.items():
|
||
|
|
# Skip linked expressions (RHS contains another expression name)
|
||
|
|
if expr.get("rhs") and not expr["rhs"].replace(".", "").replace("-", "").isdigit():
|
||
|
|
continue
|
||
|
|
# Only include length/angle expressions
|
||
|
|
if expr.get("units") in ["MilliMeter", "Degrees", None]:
|
||
|
|
design_vars[name] = expr["value"]
|
||
|
|
|
||
|
|
print(f"\nIdentified {len(design_vars)} potential design variables:")
|
||
|
|
for name, value in design_vars.items():
|
||
|
|
print(f" {name}: {value}")
|
||
|
|
|
||
|
|
if dry_run:
|
||
|
|
print("\n[DRY RUN] Would update expressions (no changes made)")
|
||
|
|
|
||
|
|
# Example: increase all dimensions by 10%
|
||
|
|
new_values = {name: value * 1.1 for name, value in design_vars.items()}
|
||
|
|
|
||
|
|
print("\nProposed changes:")
|
||
|
|
for name, new_val in new_values.items():
|
||
|
|
old_val = design_vars[name]
|
||
|
|
print(f" {name}: {old_val:.4f} -> {new_val:.4f} (+10%)")
|
||
|
|
|
||
|
|
return new_values
|
||
|
|
|
||
|
|
else:
|
||
|
|
# Actually update the model
|
||
|
|
new_values = {name: value * 1.1 for name, value in design_vars.items()}
|
||
|
|
|
||
|
|
print("\nUpdating expressions...")
|
||
|
|
result = expression_manager.set_expressions(part_path, new_values)
|
||
|
|
|
||
|
|
if result["success"]:
|
||
|
|
print(f"SUCCESS: Updated {result['data']['update_count']} expressions")
|
||
|
|
if result["data"].get("errors"):
|
||
|
|
print(f"Warnings: {result['data']['errors']}")
|
||
|
|
else:
|
||
|
|
print(f"ERROR: {result['error']}")
|
||
|
|
|
||
|
|
return result
|
||
|
|
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Example 4: Feature Exploration
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
def feature_exploration_example(part_path: str):
|
||
|
|
"""
|
||
|
|
Example: Explore and manipulate features.
|
||
|
|
|
||
|
|
This is useful for topological optimization where features
|
||
|
|
can be suppressed/unsuppressed to explore design space.
|
||
|
|
"""
|
||
|
|
print("\n" + "=" * 60)
|
||
|
|
print("Example 4: Feature Exploration")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
result = feature_manager.get_features(part_path)
|
||
|
|
|
||
|
|
if not result["success"]:
|
||
|
|
print(f"ERROR: {result['error']}")
|
||
|
|
return None
|
||
|
|
|
||
|
|
data = result["data"]
|
||
|
|
|
||
|
|
print(f"\nFound {data['count']} features ({data['suppressed_count']} suppressed):\n")
|
||
|
|
|
||
|
|
print(f"{'Name':<30} {'Type':<20} {'Status'}")
|
||
|
|
print("-" * 60)
|
||
|
|
|
||
|
|
for feat in data["features"]:
|
||
|
|
status = "SUPPRESSED" if feat["is_suppressed"] else "Active"
|
||
|
|
print(f"{feat['name']:<30} {feat['type']:<20} {status}")
|
||
|
|
|
||
|
|
# Group by type
|
||
|
|
print("\n\nFeatures by type:")
|
||
|
|
print("-" * 40)
|
||
|
|
type_counts = {}
|
||
|
|
for feat in data["features"]:
|
||
|
|
feat_type = feat["type"]
|
||
|
|
type_counts[feat_type] = type_counts.get(feat_type, 0) + 1
|
||
|
|
|
||
|
|
for feat_type, count in sorted(type_counts.items(), key=lambda x: -x[1]):
|
||
|
|
print(f" {feat_type}: {count}")
|
||
|
|
|
||
|
|
return data
|
||
|
|
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Example 5: Optimization Objective Evaluation
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
def evaluate_design_point(part_path: str, parameters: dict) -> dict:
|
||
|
|
"""
|
||
|
|
Example: Complete design evaluation workflow.
|
||
|
|
|
||
|
|
This demonstrates how hooks integrate into an optimization loop:
|
||
|
|
1. Update parameters
|
||
|
|
2. Extract objectives (mass, volume)
|
||
|
|
3. Return metrics
|
||
|
|
|
||
|
|
Args:
|
||
|
|
part_path: Path to the NX part
|
||
|
|
parameters: Dict of parameter_name -> new_value
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dict with mass_kg, volume_mm3, surface_area_mm2
|
||
|
|
"""
|
||
|
|
print("\n" + "=" * 60)
|
||
|
|
print("Example 5: Optimization Objective Evaluation")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
print(f"\nParameters to set:")
|
||
|
|
for name, value in parameters.items():
|
||
|
|
print(f" {name} = {value}")
|
||
|
|
|
||
|
|
# Step 1: Update parameters
|
||
|
|
print("\n[1/2] Updating design parameters...")
|
||
|
|
result = expression_manager.set_expressions(part_path, parameters)
|
||
|
|
|
||
|
|
if not result["success"]:
|
||
|
|
raise RuntimeError(f"Failed to set expressions: {result['error']}")
|
||
|
|
|
||
|
|
print(f" Updated {result['data']['update_count']} expressions")
|
||
|
|
|
||
|
|
# Step 2: Extract objectives
|
||
|
|
print("\n[2/2] Extracting mass properties...")
|
||
|
|
result = geometry_query.get_mass_properties(part_path)
|
||
|
|
|
||
|
|
if not result["success"]:
|
||
|
|
raise RuntimeError(f"Failed to get mass properties: {result['error']}")
|
||
|
|
|
||
|
|
data = result["data"]
|
||
|
|
|
||
|
|
# Return metrics
|
||
|
|
metrics = {
|
||
|
|
"mass_kg": data["mass"],
|
||
|
|
"volume_mm3": data["volume"],
|
||
|
|
"surface_area_mm2": data["surface_area"],
|
||
|
|
"material": data.get("material"),
|
||
|
|
}
|
||
|
|
|
||
|
|
print(f"\nObjective metrics:")
|
||
|
|
print(f" Mass: {metrics['mass_kg']:.6f} kg")
|
||
|
|
print(f" Volume: {metrics['volume_mm3']:.2f} mm^3")
|
||
|
|
print(f" Surface Area: {metrics['surface_area_mm2']:.2f} mm^2")
|
||
|
|
|
||
|
|
return metrics
|
||
|
|
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Example 6: Batch Processing Multiple Parts
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
def batch_mass_extraction(part_paths: list) -> list:
|
||
|
|
"""
|
||
|
|
Example: Extract mass from multiple parts.
|
||
|
|
|
||
|
|
Useful for comparing variants or processing a design library.
|
||
|
|
"""
|
||
|
|
print("\n" + "=" * 60)
|
||
|
|
print("Example 6: Batch Processing Multiple Parts")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
results = []
|
||
|
|
|
||
|
|
for i, part_path in enumerate(part_paths, 1):
|
||
|
|
print(f"\n[{i}/{len(part_paths)}] Processing: {Path(part_path).name}")
|
||
|
|
|
||
|
|
result = geometry_query.get_mass_properties(part_path)
|
||
|
|
|
||
|
|
if result["success"]:
|
||
|
|
data = result["data"]
|
||
|
|
results.append({
|
||
|
|
"part": Path(part_path).name,
|
||
|
|
"mass_kg": data["mass"],
|
||
|
|
"volume_mm3": data["volume"],
|
||
|
|
"material": data.get("material"),
|
||
|
|
"success": True,
|
||
|
|
})
|
||
|
|
print(f" Mass: {data['mass']:.4f} kg, Material: {data.get('material')}")
|
||
|
|
else:
|
||
|
|
results.append({
|
||
|
|
"part": Path(part_path).name,
|
||
|
|
"error": result["error"],
|
||
|
|
"success": False,
|
||
|
|
})
|
||
|
|
print(f" ERROR: {result['error']}")
|
||
|
|
|
||
|
|
# Summary
|
||
|
|
print("\n" + "-" * 60)
|
||
|
|
print("Summary:")
|
||
|
|
successful = [r for r in results if r["success"]]
|
||
|
|
print(f" Processed: {len(successful)}/{len(part_paths)} parts")
|
||
|
|
|
||
|
|
if successful:
|
||
|
|
total_mass = sum(r["mass_kg"] for r in successful)
|
||
|
|
print(f" Total mass: {total_mass:.4f} kg")
|
||
|
|
|
||
|
|
return results
|
||
|
|
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# Main - Run All Examples
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
def main():
|
||
|
|
"""Run all examples with a test part."""
|
||
|
|
|
||
|
|
# Default test part
|
||
|
|
default_part = project_root / "studies/bracket_stiffness_optimization_V3/1_setup/model/Bracket.prt"
|
||
|
|
|
||
|
|
if len(sys.argv) > 1:
|
||
|
|
part_path = sys.argv[1]
|
||
|
|
else:
|
||
|
|
part_path = str(default_part)
|
||
|
|
|
||
|
|
print("\n" + "=" * 60)
|
||
|
|
print("NX OPEN HOOKS - EXAMPLES")
|
||
|
|
print("=" * 60)
|
||
|
|
print(f"\nUsing part: {Path(part_path).name}")
|
||
|
|
|
||
|
|
if not os.path.exists(part_path):
|
||
|
|
print(f"\nERROR: Part file not found: {part_path}")
|
||
|
|
print("\nUsage: python -m optimization_engine.hooks.examples [part_path]")
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
# Run examples
|
||
|
|
try:
|
||
|
|
# Example 1: Query expressions
|
||
|
|
basic_expression_query(part_path)
|
||
|
|
|
||
|
|
# Example 2: Get mass properties
|
||
|
|
mass_properties_example(part_path)
|
||
|
|
|
||
|
|
# Example 3: Design update (dry run)
|
||
|
|
design_update_example(part_path, dry_run=True)
|
||
|
|
|
||
|
|
# Example 4: Feature exploration
|
||
|
|
feature_exploration_example(part_path)
|
||
|
|
|
||
|
|
print("\n" + "=" * 60)
|
||
|
|
print("ALL EXAMPLES COMPLETED SUCCESSFULLY!")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
print(f"\nEXAMPLE FAILED: {e}")
|
||
|
|
import traceback
|
||
|
|
traceback.print_exc()
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|