1080 lines
43 KiB
Python
1080 lines
43 KiB
Python
"""
|
|
ANSYS Mechanical session manager for CAE Mesh Generator
|
|
"""
|
|
import os
|
|
import time
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Optional, Dict, Any, List, Callable
|
|
from datetime import datetime
|
|
|
|
from config import ANSYS_CONFIG, TEMP_DIR
|
|
from backend.pymechanical.named_selection_manager import NamedSelectionManager
|
|
from backend.pymechanical.mesh_controller import MeshController
|
|
from backend.pymechanical.mesh_generator import MeshGenerator
|
|
from backend.pymechanical.mesh_quality_checker import MeshQualityChecker
|
|
from backend.utils.resource_manager import resource_manager, file_manager
|
|
|
|
# Configure logging
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class ANSYSSessionManager:
|
|
"""
|
|
ANSYS Mechanical session manager
|
|
|
|
This class manages ANSYS Mechanical sessions for mesh generation.
|
|
It includes both real PyMechanical integration and simulation mode
|
|
for development/testing without ANSYS installation.
|
|
"""
|
|
|
|
def __init__(self, simulation_mode: bool = False):
|
|
"""
|
|
Initialize session manager
|
|
|
|
Args:
|
|
simulation_mode: If True, simulate ANSYS operations without real ANSYS
|
|
"""
|
|
self.simulation_mode = simulation_mode
|
|
self.session = None
|
|
self.mechanical = None
|
|
self.is_session_active = False
|
|
self.current_geometry = None
|
|
self.session_start_time = None
|
|
self.temp_files = []
|
|
self.named_selection_manager = None
|
|
self.mesh_controller = None
|
|
self.mesh_generator = None
|
|
self.mesh_quality_checker = None
|
|
|
|
# Create temp directory if it doesn't exist
|
|
os.makedirs(TEMP_DIR, exist_ok=True)
|
|
|
|
logger.info(f"ANSYS Session Manager initialized (simulation_mode={simulation_mode})")
|
|
|
|
def start_session(self, batch_mode: bool = True) -> bool:
|
|
"""
|
|
Start ANSYS Mechanical session
|
|
|
|
Args:
|
|
batch_mode: Whether to start in batch mode
|
|
|
|
Returns:
|
|
True if session started successfully, False otherwise
|
|
"""
|
|
try:
|
|
logger.info("Starting ANSYS Mechanical session...")
|
|
self.session_start_time = datetime.now()
|
|
|
|
if self.simulation_mode:
|
|
# Simulate session startup
|
|
logger.info("Simulation mode: Simulating ANSYS session startup")
|
|
time.sleep(1) # Simulate startup time
|
|
self.is_session_active = True
|
|
self.session = {"simulation": True, "batch_mode": batch_mode}
|
|
logger.info("✓ Simulated ANSYS session started successfully")
|
|
return True
|
|
|
|
else:
|
|
# Real PyMechanical integration
|
|
try:
|
|
import ansys.mechanical.core as pymechanical
|
|
import os
|
|
|
|
logger.info("Launching ANSYS Mechanical...")
|
|
|
|
# Set up ANSYS environment
|
|
ansys_root = ANSYS_CONFIG.get('ansys_root', r'C:\Program Files\ANSYS Inc\v241')
|
|
os.environ['AWP_ROOT241'] = ansys_root
|
|
os.environ['ANSYS_ROOT'] = ansys_root
|
|
|
|
# Configure launch parameters
|
|
launch_options = {
|
|
'batch': batch_mode,
|
|
'cleanup_on_exit': True,
|
|
}
|
|
|
|
# Add version-specific configuration if available
|
|
if 'ansys_version' in ANSYS_CONFIG:
|
|
launch_options['version'] = ANSYS_CONFIG['ansys_version']
|
|
|
|
# Start Mechanical session
|
|
self.mechanical = pymechanical.launch_mechanical(**launch_options)
|
|
|
|
# Verify session is working
|
|
if self.mechanical:
|
|
# Test basic functionality
|
|
try:
|
|
# Check if session is alive
|
|
if hasattr(self.mechanical, 'is_alive') and self.mechanical.is_alive:
|
|
logger.info(f"ANSYS Mechanical session established and alive")
|
|
else:
|
|
logger.info(f"ANSYS Mechanical session established")
|
|
|
|
# Try to get product info
|
|
try:
|
|
product_info = self.mechanical.get_product_info()
|
|
logger.info(f"ANSYS Product info: {product_info}")
|
|
except Exception as e:
|
|
logger.debug(f"Could not get product info: {str(e)}")
|
|
|
|
self.session = self.mechanical
|
|
self.is_session_active = True
|
|
|
|
# Initialize named selection manager
|
|
self.named_selection_manager = NamedSelectionManager(self.mechanical)
|
|
|
|
# Initialize mesh controller
|
|
self.mesh_controller = MeshController(self.mechanical)
|
|
|
|
# Initialize mesh generator
|
|
self.mesh_generator = MeshGenerator(self.mechanical)
|
|
|
|
# Initialize mesh quality checker
|
|
self.mesh_quality_checker = MeshQualityChecker(self.mechanical)
|
|
|
|
logger.info("✓ Real ANSYS Mechanical session started successfully")
|
|
return True
|
|
|
|
except Exception as session_error:
|
|
logger.error(f"Failed to verify Mechanical session: {str(session_error)}")
|
|
# Try to close the session if it was partially started
|
|
try:
|
|
self.mechanical.exit()
|
|
except:
|
|
pass
|
|
return False
|
|
else:
|
|
logger.error("Failed to launch ANSYS Mechanical")
|
|
return False
|
|
|
|
except ImportError as e:
|
|
logger.error(f"PyMechanical not available: {str(e)}")
|
|
logger.info("Falling back to simulation mode")
|
|
self.simulation_mode = True
|
|
return self.start_session(batch_mode)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to start ANSYS Mechanical session: {str(e)}")
|
|
logger.info("Falling back to simulation mode")
|
|
self.simulation_mode = True
|
|
return self.start_session(batch_mode)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Session startup error: {str(e)}")
|
|
return False
|
|
|
|
def import_geometry(self, file_path: str) -> bool:
|
|
"""
|
|
Import geometry from CAD file
|
|
|
|
Args:
|
|
file_path: Path to the CAD file
|
|
|
|
Returns:
|
|
True if import successful, False otherwise
|
|
"""
|
|
try:
|
|
if not self.is_session_active:
|
|
logger.error("No active ANSYS session")
|
|
return False
|
|
|
|
# In simulation mode, don't check for real file existence
|
|
if not self.simulation_mode and not os.path.exists(file_path):
|
|
logger.error(f"Geometry file not found: {file_path}")
|
|
return False
|
|
|
|
logger.info(f"Importing geometry from: {file_path}")
|
|
|
|
if self.simulation_mode:
|
|
# Simulate geometry import
|
|
logger.info("Simulation mode: Simulating geometry import")
|
|
time.sleep(2) # Simulate import time
|
|
|
|
# Simulate geometry validation
|
|
file_size = os.path.getsize(file_path)
|
|
if file_size == 0:
|
|
logger.error("Invalid geometry file (empty)")
|
|
return False
|
|
|
|
self.current_geometry = {
|
|
"file_path": file_path,
|
|
"file_size": file_size,
|
|
"imported_at": datetime.now(),
|
|
"simulation": True
|
|
}
|
|
|
|
logger.info("✓ Simulated geometry import completed successfully")
|
|
return True
|
|
|
|
else:
|
|
# Real PyMechanical geometry import
|
|
try:
|
|
logger.info("Importing geometry using PyMechanical...")
|
|
|
|
# Method 1: Try direct script execution
|
|
try:
|
|
# Create a script to import geometry using correct PyMechanical API
|
|
import_script = f'''
|
|
# Import geometry using PyMechanical API
|
|
geometry_import = Model.GeometryImportGroup.AddGeometryImport()
|
|
geometry_import.Import(r"{file_path}")
|
|
body_count = len(Model.Geometry.GetChildren(Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.Body, True))
|
|
print("Imported geometry with " + str(body_count) + " bodies")
|
|
'''
|
|
|
|
# Execute the script
|
|
result = self.mechanical.run_python_script(import_script)
|
|
logger.info(f"Import script result: {result}")
|
|
|
|
self.current_geometry = {
|
|
"file_path": file_path,
|
|
"imported_at": datetime.now(),
|
|
"import_method": "direct_script",
|
|
"import_result": result if result else "Import executed"
|
|
}
|
|
|
|
logger.info("✓ Real geometry import completed successfully")
|
|
return True
|
|
|
|
except Exception as script_error:
|
|
logger.warning(f"Direct script method failed: {str(script_error)}")
|
|
|
|
# Method 2: Try file upload approach
|
|
logger.info("Trying file upload approach...")
|
|
|
|
# Upload the file to the server first
|
|
uploaded_file = self.mechanical.upload(file_path)
|
|
logger.info(f"File uploaded to server: {uploaded_file}")
|
|
|
|
# Create import script using uploaded file with full path
|
|
project_dir = self.mechanical.run_python_script("ExtAPI.DataModel.Project.ProjectDirectory")
|
|
full_uploaded_path = f"{project_dir}/{uploaded_file}".replace("\\", "/")
|
|
|
|
upload_script = f'''
|
|
# Import uploaded geometry using PyMechanical API
|
|
geometry_import = Model.GeometryImportGroup.AddGeometryImport()
|
|
geometry_import.Import(r"{full_uploaded_path}")
|
|
body_count = len(Model.Geometry.GetChildren(Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.Body, True))
|
|
print("Imported uploaded geometry with " + str(body_count) + " bodies")
|
|
'''
|
|
|
|
upload_result = self.mechanical.run_python_script(upload_script)
|
|
logger.info(f"Upload import result: {upload_result}")
|
|
|
|
self.current_geometry = {
|
|
"file_path": file_path,
|
|
"uploaded_file": uploaded_file,
|
|
"imported_at": datetime.now(),
|
|
"import_method": "upload_script",
|
|
"import_result": upload_result if upload_result else "Upload import executed"
|
|
}
|
|
|
|
logger.info("✓ Real geometry import completed via upload method")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"PyMechanical geometry import error: {str(e)}")
|
|
logger.error("This could be due to:")
|
|
logger.error("- Invalid or corrupted STEP file")
|
|
logger.error("- Unsupported geometry format")
|
|
logger.error("- ANSYS Mechanical session issues")
|
|
logger.error("- PyMechanical API version mismatch")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Geometry import error: {str(e)}")
|
|
return False
|
|
|
|
def validate_geometry(self) -> Dict[str, Any]:
|
|
"""
|
|
Validate imported geometry
|
|
|
|
Returns:
|
|
Dictionary with validation results
|
|
"""
|
|
try:
|
|
if not self.current_geometry:
|
|
return {
|
|
"valid": False,
|
|
"error": "No geometry imported"
|
|
}
|
|
|
|
logger.info("Validating imported geometry...")
|
|
|
|
if self.simulation_mode:
|
|
# Simulate geometry validation
|
|
time.sleep(0.5)
|
|
|
|
validation_result = {
|
|
"valid": True,
|
|
"file_path": self.current_geometry["file_path"],
|
|
"file_size_mb": round(self.current_geometry["file_size"] / (1024 * 1024), 2),
|
|
"surface_count": 42, # Simulated values
|
|
"volume_count": 1,
|
|
"feature_count": 15,
|
|
"simulation": True
|
|
}
|
|
|
|
logger.info("✓ Simulated geometry validation completed")
|
|
return validation_result
|
|
|
|
else:
|
|
# Real geometry validation using PyMechanical
|
|
try:
|
|
# Get geometry information using PyMechanical script
|
|
validation_script = '''
|
|
# Get geometry validation information
|
|
body_count = len(Model.Geometry.GetChildren(Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.Body, True))
|
|
surface_count = body_count # Simplified - assume each body has surfaces
|
|
volume_count = body_count # Simplified - assume each body has volume
|
|
|
|
print("Bodies: " + str(body_count) + ", Surfaces: " + str(surface_count) + ", Volumes: " + str(volume_count))
|
|
'''
|
|
|
|
validation_result_str = self.mechanical.run_python_script(validation_script)
|
|
logger.info(f"Validation script result: {validation_result_str}")
|
|
|
|
# Parse the result or use default values
|
|
body_count = 1 # Default value
|
|
surface_count = 1
|
|
volume_count = 1
|
|
|
|
# Try to extract numbers from the result string
|
|
if validation_result_str:
|
|
import re
|
|
numbers = re.findall(r'\d+', str(validation_result_str))
|
|
if len(numbers) >= 3:
|
|
body_count = int(numbers[0])
|
|
surface_count = int(numbers[1])
|
|
volume_count = int(numbers[2])
|
|
|
|
validation_result = {
|
|
"valid": True,
|
|
"file_path": self.current_geometry["file_path"],
|
|
"body_count": body_count,
|
|
"surface_count": surface_count,
|
|
"volume_count": volume_count,
|
|
"imported_at": self.current_geometry["imported_at"].isoformat(),
|
|
"import_method": self.current_geometry.get("import_method", "unknown")
|
|
}
|
|
|
|
logger.info("✓ Real geometry validation completed")
|
|
return validation_result
|
|
|
|
except Exception as e:
|
|
logger.error(f"Geometry validation error: {str(e)}")
|
|
return {
|
|
"valid": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Validation error: {str(e)}")
|
|
return {
|
|
"valid": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
def create_named_selections(self) -> Dict[str, Any]:
|
|
"""
|
|
Create named selections for blade geometry
|
|
|
|
Returns:
|
|
Dictionary with creation results
|
|
"""
|
|
try:
|
|
if not self.is_session_active:
|
|
logger.error("No active ANSYS session")
|
|
return {
|
|
"success": False,
|
|
"error": "No active ANSYS session"
|
|
}
|
|
|
|
if not self.current_geometry:
|
|
logger.error("No geometry imported")
|
|
return {
|
|
"success": False,
|
|
"error": "No geometry imported"
|
|
}
|
|
|
|
logger.info("Creating named selections for blade geometry...")
|
|
|
|
if self.simulation_mode:
|
|
# Simulate named selection creation
|
|
logger.info("Simulation mode: Simulating named selection creation")
|
|
time.sleep(1)
|
|
|
|
simulated_result = {
|
|
"success": True,
|
|
"selections_created": ["leading_edge", "trailing_edge", "blade_root", "blade_surfaces"],
|
|
"selections_failed": [],
|
|
"total_selections": 4,
|
|
"created_at": datetime.now(),
|
|
"simulation": True
|
|
}
|
|
|
|
logger.info("✓ Simulated named selection creation completed")
|
|
return simulated_result
|
|
|
|
else:
|
|
# Real named selection creation using PyMechanical
|
|
if not self.named_selection_manager:
|
|
logger.error("Named selection manager not initialized")
|
|
return {
|
|
"success": False,
|
|
"error": "Named selection manager not initialized"
|
|
}
|
|
|
|
result = self.named_selection_manager.create_blade_named_selections()
|
|
|
|
if result["success"]:
|
|
logger.info(f"✓ Named selections created successfully: {result['total_selections']} selections")
|
|
else:
|
|
logger.error(f"Named selection creation failed: {result.get('error', 'Unknown error')}")
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"Named selection creation error: {str(e)}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"selections_created": [],
|
|
"selections_failed": [],
|
|
"total_selections": 0,
|
|
"created_at": datetime.now()
|
|
}
|
|
|
|
def get_named_selections_info(self) -> Dict[str, Any]:
|
|
"""
|
|
Get information about created named selections
|
|
|
|
Returns:
|
|
Dictionary with named selection information
|
|
"""
|
|
try:
|
|
if not self.is_session_active:
|
|
return {
|
|
"success": False,
|
|
"error": "No active ANSYS session"
|
|
}
|
|
|
|
if self.simulation_mode:
|
|
# Simulate getting named selection info
|
|
return {
|
|
"success": True,
|
|
"created_selections": {
|
|
"leading_edge": {"description": "Simulated leading edge", "method": "curvature_based"},
|
|
"trailing_edge": {"description": "Simulated trailing edge", "method": "angle_based"},
|
|
"blade_root": {"description": "Simulated blade root", "method": "location_based"},
|
|
"blade_surfaces": {"description": "Simulated blade surfaces", "method": "surface_based"}
|
|
},
|
|
"total_count": 4,
|
|
"simulation": True
|
|
}
|
|
|
|
else:
|
|
if not self.named_selection_manager:
|
|
return {
|
|
"success": False,
|
|
"error": "Named selection manager not initialized"
|
|
}
|
|
|
|
return self.named_selection_manager.get_named_selections_info()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting named selections info: {str(e)}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
def validate_named_selections(self) -> Dict[str, Any]:
|
|
"""
|
|
Validate created named selections
|
|
|
|
Returns:
|
|
Dictionary with validation results
|
|
"""
|
|
try:
|
|
if not self.is_session_active:
|
|
return {
|
|
"success": False,
|
|
"error": "No active ANSYS session"
|
|
}
|
|
|
|
if self.simulation_mode:
|
|
# Simulate validation
|
|
return {
|
|
"success": True,
|
|
"validation_result": "All 4 named selections validated successfully",
|
|
"validated_at": datetime.now(),
|
|
"simulation": True
|
|
}
|
|
|
|
else:
|
|
if not self.named_selection_manager:
|
|
return {
|
|
"success": False,
|
|
"error": "Named selection manager not initialized"
|
|
}
|
|
|
|
return self.named_selection_manager.validate_named_selections()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Named selection validation error: {str(e)}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
def apply_mesh_controls(self, named_selections: List[str] = None) -> Dict[str, Any]:
|
|
"""
|
|
Apply mesh controls for blade geometry
|
|
|
|
Args:
|
|
named_selections: List of named selections to use for controls
|
|
|
|
Returns:
|
|
Dictionary with mesh control application results
|
|
"""
|
|
try:
|
|
if not self.is_session_active:
|
|
logger.error("No active ANSYS session")
|
|
return {
|
|
"success": False,
|
|
"error": "No active ANSYS session"
|
|
}
|
|
|
|
if not self.current_geometry:
|
|
logger.error("No geometry imported")
|
|
return {
|
|
"success": False,
|
|
"error": "No geometry imported"
|
|
}
|
|
|
|
logger.info("Applying mesh controls for blade geometry...")
|
|
|
|
if self.simulation_mode:
|
|
# Simulate mesh control application
|
|
logger.info("Simulation mode: Simulating mesh control application")
|
|
time.sleep(2)
|
|
|
|
simulated_result = {
|
|
"success": True,
|
|
"controls_applied": ["parameter_calculation", "global_settings", "local_refinement", "inflation_layers"],
|
|
"controls_failed": [],
|
|
"mesh_parameters": {
|
|
"global_element_size": 2.0,
|
|
"curvature_angle": 25.0,
|
|
"inflation_layers": 5,
|
|
"growth_rate": 1.15
|
|
},
|
|
"applied_at": datetime.now(),
|
|
"simulation": True
|
|
}
|
|
|
|
logger.info("✓ Simulated mesh control application completed")
|
|
return simulated_result
|
|
|
|
else:
|
|
# Real mesh control application using PyMechanical
|
|
if not self.mesh_controller:
|
|
logger.error("Mesh controller not initialized")
|
|
return {
|
|
"success": False,
|
|
"error": "Mesh controller not initialized"
|
|
}
|
|
|
|
# Use provided named selections or get from named selection manager
|
|
if named_selections is None:
|
|
if self.named_selection_manager:
|
|
# Get created named selections
|
|
info_result = self.named_selection_manager.get_named_selections_info()
|
|
if info_result.get('success'):
|
|
named_selections = list(info_result.get('created_selections', {}).keys())
|
|
else:
|
|
named_selections = ['leading_edge', 'trailing_edge', 'blade_root', 'blade_surfaces']
|
|
else:
|
|
named_selections = ['leading_edge', 'trailing_edge', 'blade_root', 'blade_surfaces']
|
|
|
|
result = self.mesh_controller.apply_all_mesh_controls(named_selections)
|
|
|
|
if result["success"]:
|
|
logger.info(f"✓ Mesh controls applied successfully: {len(result['controls_applied'])} controls")
|
|
else:
|
|
logger.warning(f"⚠ Mesh controls partially applied: {result.get('controls_applied', [])} successful, {result.get('controls_failed', [])} failed")
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"Mesh control application error: {str(e)}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"controls_applied": [],
|
|
"controls_failed": ["all"],
|
|
"applied_at": datetime.now()
|
|
}
|
|
|
|
def get_mesh_control_summary(self) -> Dict[str, Any]:
|
|
"""
|
|
Get summary of applied mesh controls
|
|
|
|
Returns:
|
|
Dictionary with mesh control summary
|
|
"""
|
|
try:
|
|
if not self.is_session_active:
|
|
return {
|
|
"success": False,
|
|
"error": "No active ANSYS session"
|
|
}
|
|
|
|
if self.simulation_mode:
|
|
# Simulate mesh control summary
|
|
return {
|
|
"success": True,
|
|
"mesh_parameters": {
|
|
"global_element_size": 2.0,
|
|
"curvature_angle": 25.0,
|
|
"inflation_layers": 5,
|
|
"growth_rate": 1.15
|
|
},
|
|
"applied_controls": {
|
|
"global_settings": {"applied": True},
|
|
"local_refinement": {"applied": True, "regions": 3},
|
|
"inflation_layers": {"applied": True, "surfaces": 1}
|
|
},
|
|
"geometry_info": {
|
|
"min_feature_size": 5.0,
|
|
"max_dimension": 100.0
|
|
},
|
|
"simulation": True
|
|
}
|
|
|
|
else:
|
|
if not self.mesh_controller:
|
|
return {
|
|
"success": False,
|
|
"error": "Mesh controller not initialized"
|
|
}
|
|
|
|
return self.mesh_controller.get_mesh_control_summary()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get mesh control summary: {str(e)}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
def generate_mesh(self, progress_callback: Optional[Callable[[float, str], None]] = None) -> Dict[str, Any]:
|
|
"""
|
|
Generate mesh for the imported geometry
|
|
|
|
Args:
|
|
progress_callback: Optional callback for progress updates
|
|
|
|
Returns:
|
|
Dictionary with mesh generation results
|
|
"""
|
|
try:
|
|
if not self.is_session_active:
|
|
logger.error("No active ANSYS session")
|
|
return {
|
|
"success": False,
|
|
"error": "No active ANSYS session"
|
|
}
|
|
|
|
if not self.current_geometry:
|
|
logger.error("No geometry imported")
|
|
return {
|
|
"success": False,
|
|
"error": "No geometry imported"
|
|
}
|
|
|
|
logger.info("Starting mesh generation...")
|
|
|
|
if self.simulation_mode:
|
|
# Simulate mesh generation
|
|
logger.info("Simulation mode: Simulating mesh generation")
|
|
|
|
# Simulate progress updates
|
|
if progress_callback:
|
|
progress_steps = [
|
|
(10.0, "Preparing mesh generation..."),
|
|
(30.0, "Generating mesh..."),
|
|
(60.0, "Creating elements..."),
|
|
(80.0, "Optimizing mesh quality..."),
|
|
(100.0, "Mesh generation completed")
|
|
]
|
|
|
|
for progress, message in progress_steps:
|
|
progress_callback(progress, message)
|
|
time.sleep(0.5)
|
|
|
|
simulated_result = {
|
|
"success": True,
|
|
"status": "completed",
|
|
"element_count": 12500,
|
|
"node_count": 18750,
|
|
"generation_time": 15.5,
|
|
"progress_percentage": 100.0,
|
|
"started_at": datetime.now().isoformat(),
|
|
"completed_at": datetime.now().isoformat(),
|
|
"simulation": True
|
|
}
|
|
|
|
logger.info("✓ Simulated mesh generation completed")
|
|
return simulated_result
|
|
|
|
else:
|
|
# Real mesh generation using PyMechanical
|
|
if not self.mesh_generator:
|
|
logger.error("Mesh generator not initialized")
|
|
return {
|
|
"success": False,
|
|
"error": "Mesh generator not initialized"
|
|
}
|
|
|
|
# Set progress callback if provided
|
|
if progress_callback:
|
|
self.mesh_generator.set_progress_callback(progress_callback)
|
|
|
|
# Generate mesh
|
|
result = self.mesh_generator.generate_mesh()
|
|
|
|
# Convert result to dictionary
|
|
result_dict = {
|
|
"success": result.success,
|
|
"status": result.status.value,
|
|
"element_count": result.element_count,
|
|
"node_count": result.node_count,
|
|
"generation_time": result.generation_time,
|
|
"progress_percentage": result.progress_percentage,
|
|
"started_at": result.started_at.isoformat() if result.started_at else None,
|
|
"completed_at": result.completed_at.isoformat() if result.completed_at else None,
|
|
"error_message": result.error_message,
|
|
"warnings": result.warnings
|
|
}
|
|
|
|
if result.success:
|
|
logger.info(f"✓ Mesh generation completed: {result.element_count} elements, {result.node_count} nodes")
|
|
else:
|
|
logger.error(f"✗ Mesh generation failed: {result.error_message}")
|
|
|
|
return result_dict
|
|
|
|
except Exception as e:
|
|
logger.error(f"Mesh generation error: {str(e)}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"status": "failed",
|
|
"completed_at": datetime.now().isoformat()
|
|
}
|
|
|
|
def get_mesh_statistics(self) -> Dict[str, Any]:
|
|
"""
|
|
Get mesh statistics
|
|
|
|
Returns:
|
|
Dictionary with mesh statistics
|
|
"""
|
|
try:
|
|
if not self.is_session_active:
|
|
return {
|
|
"success": False,
|
|
"error": "No active ANSYS session"
|
|
}
|
|
|
|
if self.simulation_mode:
|
|
# Simulate mesh statistics
|
|
return {
|
|
"success": True,
|
|
"element_count": 12500,
|
|
"node_count": 18750,
|
|
"generation_time": 15.5,
|
|
"has_mesh": True,
|
|
"status": "completed",
|
|
"retrieved_at": datetime.now().isoformat(),
|
|
"simulation": True
|
|
}
|
|
|
|
else:
|
|
if not self.mesh_generator:
|
|
return {
|
|
"success": False,
|
|
"error": "Mesh generator not initialized"
|
|
}
|
|
|
|
statistics = self.mesh_generator.get_mesh_statistics()
|
|
statistics["success"] = True
|
|
return statistics
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get mesh statistics: {str(e)}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
def check_mesh_quality(self) -> Dict[str, Any]:
|
|
"""
|
|
Perform mesh quality check
|
|
|
|
Returns:
|
|
Dictionary with quality check results
|
|
"""
|
|
try:
|
|
if not self.is_session_active:
|
|
logger.error("No active ANSYS session")
|
|
return {
|
|
"success": False,
|
|
"error": "No active ANSYS session"
|
|
}
|
|
|
|
logger.info("Starting mesh quality check...")
|
|
|
|
if self.simulation_mode:
|
|
# Simulate quality check
|
|
logger.info("Simulation mode: Simulating mesh quality check")
|
|
time.sleep(1)
|
|
|
|
simulated_result = {
|
|
"success": True,
|
|
"overall_status": "PASSED",
|
|
"quality_score": 85.5,
|
|
"metrics": {
|
|
"min_element_quality": 0.25,
|
|
"max_aspect_ratio": 18.5,
|
|
"max_skewness": 0.75,
|
|
"min_orthogonal_quality": 0.18,
|
|
"average_element_quality": 0.65,
|
|
"failed_elements_count": 12,
|
|
"total_elements": 6500,
|
|
"failed_elements_percentage": 0.18
|
|
},
|
|
"thresholds": {
|
|
"min_element_quality": 0.2,
|
|
"max_aspect_ratio": 20,
|
|
"max_skewness": 0.8,
|
|
"min_orthogonal_quality": 0.15
|
|
},
|
|
"recommendations": [
|
|
"Mesh quality is acceptable for analysis",
|
|
"Consider local refinement for critical areas"
|
|
],
|
|
"warnings": [
|
|
"Element quality acceptable: 0.250",
|
|
"Aspect ratio acceptable: 18.50",
|
|
"Failed elements acceptable: 0.2%"
|
|
],
|
|
"critical_issues": [],
|
|
"check_time": datetime.now().isoformat(),
|
|
"simulation": True
|
|
}
|
|
|
|
logger.info("✓ Simulated mesh quality check completed")
|
|
return simulated_result
|
|
|
|
else:
|
|
# Real mesh quality check using PyMechanical
|
|
if not self.mesh_quality_checker:
|
|
logger.error("Mesh quality checker not initialized")
|
|
return {
|
|
"success": False,
|
|
"error": "Mesh quality checker not initialized"
|
|
}
|
|
|
|
quality_result = self.mesh_quality_checker.check_mesh_quality()
|
|
summary = self.mesh_quality_checker.get_quality_summary(quality_result)
|
|
|
|
result = {
|
|
"success": True,
|
|
**summary
|
|
}
|
|
|
|
if quality_result.passed:
|
|
logger.info(f"✓ Mesh quality check passed: score {summary.get('quality_score', 0):.1f}")
|
|
else:
|
|
logger.warning(f"⚠ Mesh quality check failed: {len(quality_result.critical_issues)} critical issues")
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"Mesh quality check error: {str(e)}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"overall_status": "ERROR",
|
|
"check_time": datetime.now().isoformat()
|
|
}
|
|
|
|
def validate_mesh_generation_setup(self) -> Dict[str, Any]:
|
|
"""
|
|
Validate that mesh generation setup is ready
|
|
|
|
Returns:
|
|
Dictionary with validation results
|
|
"""
|
|
try:
|
|
if not self.is_session_active:
|
|
return {
|
|
"success": False,
|
|
"error": "No active ANSYS session",
|
|
"ready_for_generation": False
|
|
}
|
|
|
|
if self.simulation_mode:
|
|
# Simulate validation
|
|
return {
|
|
"success": True,
|
|
"geometry_available": True,
|
|
"mesh_object_exists": True,
|
|
"global_settings_applied": True,
|
|
"local_controls_applied": True,
|
|
"ready_for_generation": True,
|
|
"simulation": True
|
|
}
|
|
|
|
else:
|
|
if not self.mesh_generator:
|
|
return {
|
|
"success": False,
|
|
"error": "Mesh generator not initialized",
|
|
"ready_for_generation": False
|
|
}
|
|
|
|
validation = self.mesh_generator.validate_mesh_generation_setup()
|
|
validation["success"] = True
|
|
return validation
|
|
|
|
except Exception as e:
|
|
logger.error(f"Setup validation failed: {str(e)}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"ready_for_generation": False
|
|
}
|
|
|
|
def close_session(self) -> bool:
|
|
"""
|
|
Close ANSYS Mechanical session
|
|
|
|
Returns:
|
|
True if session closed successfully, False otherwise
|
|
"""
|
|
try:
|
|
if not self.is_session_active:
|
|
logger.info("No active session to close")
|
|
return True
|
|
|
|
logger.info("Closing ANSYS Mechanical session...")
|
|
|
|
# Clean up temporary files first
|
|
self.cleanup_temp_files()
|
|
|
|
if self.simulation_mode:
|
|
# Simulate session cleanup
|
|
time.sleep(0.5)
|
|
self.session = None
|
|
self.is_session_active = False
|
|
self.current_geometry = None
|
|
|
|
logger.info("✓ Simulated ANSYS session closed successfully")
|
|
|
|
else:
|
|
# Real PyMechanical session cleanup
|
|
try:
|
|
# Register session with resource manager for cleanup
|
|
if self.mechanical:
|
|
resource_manager.register_ansys_session(self.mechanical)
|
|
self.mechanical.exit()
|
|
self.mechanical = None
|
|
|
|
self.session = None
|
|
self.is_session_active = False
|
|
self.current_geometry = None
|
|
|
|
logger.info("✓ Real ANSYS session closed successfully")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Session close error: {str(e)}")
|
|
return False
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Session close error: {str(e)}")
|
|
return False
|
|
|
|
def cleanup_temp_files(self) -> bool:
|
|
"""
|
|
Clean up temporary files created during session
|
|
|
|
Returns:
|
|
True if cleanup successful, False otherwise
|
|
"""
|
|
try:
|
|
logger.info("Cleaning up temporary files...")
|
|
|
|
# Register temp files with resource manager
|
|
for temp_file in self.temp_files:
|
|
resource_manager.register_temp_file(temp_file)
|
|
|
|
# Use resource manager for cleanup
|
|
cleanup_results = resource_manager.cleanup_temp_files()
|
|
|
|
# Clear local temp files list
|
|
self.temp_files.clear()
|
|
|
|
# Clean up temp directory if empty
|
|
try:
|
|
if os.path.exists(TEMP_DIR) and not os.listdir(TEMP_DIR):
|
|
os.rmdir(TEMP_DIR)
|
|
logger.debug(f"Removed empty temp directory: {TEMP_DIR}")
|
|
except Exception as e:
|
|
logger.debug(f"Could not remove temp directory: {str(e)}")
|
|
|
|
cleaned_count = cleanup_results.get('cleaned', 0)
|
|
failed_count = cleanup_results.get('failed', 0)
|
|
|
|
logger.info(f"✓ Cleanup completed: {cleaned_count} files cleaned, {failed_count} failed")
|
|
return failed_count == 0
|
|
|
|
except Exception as e:
|
|
logger.error(f"Cleanup error: {str(e)}")
|
|
return False
|
|
|
|
def get_session_info(self) -> Dict[str, Any]:
|
|
"""
|
|
Get current session information
|
|
|
|
Returns:
|
|
Dictionary with session information
|
|
"""
|
|
session_time = None
|
|
if self.session_start_time:
|
|
session_time = (datetime.now() - self.session_start_time).total_seconds()
|
|
|
|
return {
|
|
"is_active": self.is_session_active,
|
|
"simulation_mode": self.simulation_mode,
|
|
"session_time": session_time,
|
|
"has_geometry": self.current_geometry is not None,
|
|
"geometry_info": self.current_geometry if self.current_geometry else None,
|
|
"temp_files_count": len(self.temp_files)
|
|
}
|
|
|
|
def __enter__(self):
|
|
"""Context manager entry"""
|
|
self.start_session()
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
"""Context manager exit"""
|
|
self.close_session()
|
|
self.cleanup_temp_files() |