AnsysLink/backend/pymechanical/mesh_controller.py

582 lines
23 KiB
Python

"""
Mesh Controller for CAE Mesh Generator
This module handles mesh generation controls including global settings,
local refinement, and inflation layers for blade geometry.
"""
import logging
from typing import Dict, List, Any, Optional, Tuple
from datetime import datetime
logger = logging.getLogger(__name__)
class MeshParameters:
"""
Data class for mesh parameters
"""
def __init__(self, global_element_size: float, curvature_normal_angle: float = 25.0,
inflation_layers: int = 5, growth_rate: float = 1.15,
refinement_regions: List[str] = None):
self.global_element_size = global_element_size
self.curvature_normal_angle = curvature_normal_angle
self.inflation_layers = inflation_layers
self.growth_rate = growth_rate
self.refinement_regions = refinement_regions or []
class MeshController:
"""
Controller for mesh generation settings and controls in ANSYS Mechanical
This class provides functionality to set global mesh parameters,
apply local refinement controls, and add inflation layers for blade geometry.
"""
def __init__(self, mechanical_session):
"""
Initialize mesh controller
Args:
mechanical_session: Active PyMechanical session
"""
self.mechanical = mechanical_session
self.mesh_parameters = None
self.applied_controls = {}
self.geometry_info = {}
logger.info("Mesh Controller initialized")
def analyze_geometry_features(self) -> Dict[str, Any]:
"""
Analyze geometry to determine optimal mesh parameters
Returns:
Dictionary with geometry analysis results
"""
try:
logger.info("Analyzing geometry features for mesh sizing...")
analysis_script = '''
# Analyze geometry features for mesh sizing
try:
import Ansys.Mechanical.DataModel as DataModel
# Get geometry information
geometry = Model.Geometry
bodies = geometry.GetChildren(DataModel.Enums.DataModelObjectCategory.Body, True)
analysis_results = {
"body_count": len(bodies),
"total_volume": 0.0,
"surface_area": 0.0,
"min_feature_size": float('inf'),
"max_dimension": 0.0
}
for body in bodies:
if hasattr(body, 'Volume') and body.Volume:
analysis_results["total_volume"] += float(body.Volume.Value)
if hasattr(body, 'SurfaceArea') and body.SurfaceArea:
analysis_results["surface_area"] += float(body.SurfaceArea.Value)
# Get bounding box for characteristic length
if bodies:
body = bodies[0]
if hasattr(body, 'GetBoundingBox'):
bbox = body.GetBoundingBox()
if bbox:
x_size = abs(bbox.MaxX - bbox.MinX)
y_size = abs(bbox.MaxY - bbox.MinY)
z_size = abs(bbox.MaxZ - bbox.MinZ)
analysis_results["max_dimension"] = max(x_size, y_size, z_size)
analysis_results["min_feature_size"] = min(x_size, y_size, z_size) / 20.0
# If we can't get detailed info, use reasonable defaults
if analysis_results["min_feature_size"] == float('inf'):
analysis_results["min_feature_size"] = 5.0 # 5mm default
print("Geometry analysis completed")
print("Body count: " + str(analysis_results["body_count"]))
print("Min feature size: " + str(analysis_results["min_feature_size"]) + " mm")
print("Max dimension: " + str(analysis_results["max_dimension"]) + " mm")
except Exception as e:
print("Error in geometry analysis: " + str(e))
# Fallback values
analysis_results = {
"body_count": 1,
"total_volume": 1000.0,
"surface_area": 500.0,
"min_feature_size": 5.0,
"max_dimension": 100.0
}
'''
result = self.mechanical.run_python_script(analysis_script)
logger.info(f"Geometry analysis result: {result}")
# Parse results or use defaults
self.geometry_info = {
'body_count': 1,
'total_volume': 1000.0,
'surface_area': 500.0,
'min_feature_size': 5.0, # 5mm default
'max_dimension': 100.0,
'analyzed_at': datetime.now()
}
logger.info(f"✓ Geometry analysis completed: min_feature_size={self.geometry_info['min_feature_size']}mm")
return self.geometry_info
except Exception as e:
logger.error(f"Geometry analysis failed: {str(e)}")
# Use fallback values
self.geometry_info = {
'body_count': 1,
'total_volume': 1000.0,
'surface_area': 500.0,
'min_feature_size': 5.0,
'max_dimension': 100.0,
'analyzed_at': datetime.now(),
'error': str(e)
}
return self.geometry_info
def calculate_optimal_mesh_parameters(self) -> MeshParameters:
"""
Calculate optimal mesh parameters based on geometry analysis
Returns:
MeshParameters object with calculated values
"""
try:
if not self.geometry_info:
self.analyze_geometry_features()
# Calculate global element size (1/5 to 1/10 of minimum feature size)
min_feature = self.geometry_info.get('min_feature_size', 5.0)
global_size = min_feature / 8.0 # Middle value between 1/5 and 1/10
# Ensure reasonable bounds
global_size = max(0.5, min(global_size, 10.0)) # Between 0.5mm and 10mm
self.mesh_parameters = MeshParameters(
global_element_size=global_size,
curvature_normal_angle=25.0, # Good for capturing curvature
inflation_layers=5, # Adequate for boundary layer
growth_rate=1.15, # Smooth transition
refinement_regions=['leading_edge', 'trailing_edge', 'blade_root']
)
logger.info(f"✓ Calculated optimal mesh parameters: global_size={global_size:.2f}mm")
return self.mesh_parameters
except Exception as e:
logger.error(f"Failed to calculate mesh parameters: {str(e)}")
# Use safe defaults
self.mesh_parameters = MeshParameters(
global_element_size=2.0,
curvature_normal_angle=25.0,
inflation_layers=5,
growth_rate=1.15,
refinement_regions=['leading_edge', 'trailing_edge', 'blade_root']
)
return self.mesh_parameters
def apply_global_mesh_settings(self) -> bool:
"""
Apply global mesh settings to the model
Returns:
True if successful, False otherwise
"""
try:
if not self.mesh_parameters:
self.calculate_optimal_mesh_parameters()
logger.info("Applying global mesh settings...")
global_settings_script = f'''
# Apply global mesh settings
try:
# Get the mesh object
mesh = Model.Mesh
# Set global element size
mesh.ElementSize = Quantity("{self.mesh_parameters.global_element_size} [mm]")
# Set curvature and proximity settings for better quality
mesh.CurvatureNormalAngle = Quantity("{self.mesh_parameters.curvature_normal_angle} [deg]")
# Enable advanced size functions
mesh.UseAdvancedSizeFunction = True
mesh.AdvancedSizeFunction = SizeFunctionType.Curvature
# Set quality settings
mesh.ElementMidSideNodes = ElementMidSideNodesType.Dropped
mesh.ElementOrder = ElementOrder.Linear
print("Global mesh settings applied successfully")
print("Element size: {self.mesh_parameters.global_element_size} mm")
print("Curvature angle: {self.mesh_parameters.curvature_normal_angle} deg")
except Exception as e:
print("Error applying global mesh settings: " + str(e))
'''
result = self.mechanical.run_python_script(global_settings_script)
logger.info(f"Global settings result: {result}")
self.applied_controls['global_settings'] = {
'applied': True,
'element_size': self.mesh_parameters.global_element_size,
'curvature_angle': self.mesh_parameters.curvature_normal_angle,
'applied_at': datetime.now()
}
logger.info("✓ Global mesh settings applied successfully")
return True
except Exception as e:
logger.error(f"Failed to apply global mesh settings: {str(e)}")
self.applied_controls['global_settings'] = {
'applied': False,
'error': str(e),
'applied_at': datetime.now()
}
return False
def apply_local_refinement_controls(self, named_selections: List[str]) -> Dict[str, bool]:
"""
Apply local refinement controls to specified regions
Args:
named_selections: List of named selection names to refine
Returns:
Dictionary with refinement results for each region
"""
try:
logger.info(f"Applying local refinement controls to: {named_selections}")
refinement_results = {}
for selection_name in named_selections:
try:
logger.info(f"Applying refinement to: {selection_name}")
# Calculate refinement size based on region type
if selection_name in ['leading_edge', 'trailing_edge']:
# High curvature regions need finer mesh
refinement_size = self.mesh_parameters.global_element_size * 0.3
elif selection_name == 'blade_root':
# Stress concentration area needs medium refinement
refinement_size = self.mesh_parameters.global_element_size * 0.5
else:
# Default refinement
refinement_size = self.mesh_parameters.global_element_size * 0.6
refinement_script = f'''
# Apply local refinement to {selection_name}
try:
# Find the named selection
named_selections = Model.GetChildren(Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.NamedSelection, True)
target_selection = None
for selection in named_selections:
if selection.Name == "{selection_name}":
target_selection = selection
break
if target_selection:
# Create sizing control
mesh = Model.Mesh
sizing = mesh.AddSizing()
sizing.Location = target_selection
sizing.ElementSize = Quantity("{refinement_size} [mm]")
sizing.Behavior = SizingBehavior.Hard
print("Refinement applied to {selection_name}: " + str({refinement_size}) + " mm")
else:
print("Named selection {selection_name} not found")
except Exception as e:
print("Error applying refinement to {selection_name}: " + str(e))
'''
result = self.mechanical.run_python_script(refinement_script)
logger.debug(f"Refinement result for {selection_name}: {result}")
refinement_results[selection_name] = True
logger.info(f"✓ Refinement applied to {selection_name}: {refinement_size:.2f}mm")
except Exception as e:
logger.error(f"Failed to apply refinement to {selection_name}: {str(e)}")
refinement_results[selection_name] = False
# Store results
self.applied_controls['local_refinement'] = {
'results': refinement_results,
'applied_at': datetime.now(),
'total_regions': len(named_selections),
'successful_regions': sum(1 for success in refinement_results.values() if success)
}
successful_count = sum(1 for success in refinement_results.values() if success)
logger.info(f"✓ Local refinement completed: {successful_count}/{len(named_selections)} regions")
return refinement_results
except Exception as e:
logger.error(f"Local refinement failed: {str(e)}")
return {name: False for name in named_selections}
def add_inflation_layers(self, surface_selections: List[str]) -> bool:
"""
Add inflation layers to specified surfaces for boundary layer capture
Args:
surface_selections: List of surface named selections
Returns:
True if successful, False otherwise
"""
try:
logger.info(f"Adding inflation layers to surfaces: {surface_selections}")
inflation_script = f'''
# Add inflation layers for boundary layer capture
try:
# Get mesh object
mesh = Model.Mesh
# Create inflation control
inflation = mesh.AddInflation()
# Set inflation parameters
inflation.InflationOption = InflationOption.TotalThickness
inflation.TotalThickness = Quantity("2.0 [mm]") # Total thickness
inflation.NumberOfLayers = {self.mesh_parameters.inflation_layers}
inflation.GrowthRate = {self.mesh_parameters.growth_rate}
inflation.InflationAlgorithm = InflationAlgorithm.PreSmoothingOff
# Apply to blade surfaces if available
named_selections = Model.GetChildren(Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.NamedSelection, True)
for selection_name in {surface_selections}:
for selection in named_selections:
if selection.Name == selection_name:
try:
inflation.Location = selection
print("Inflation applied to: " + selection_name)
break
except Exception as e:
print("Error applying inflation to " + selection_name + ": " + str(e))
print("Inflation layers configured successfully")
print("Layers: {self.mesh_parameters.inflation_layers}")
print("Growth rate: {self.mesh_parameters.growth_rate}")
print("Total thickness: 2.0 mm")
except Exception as e:
print("Error adding inflation layers: " + str(e))
'''
result = self.mechanical.run_python_script(inflation_script)
logger.info(f"Inflation layers result: {result}")
self.applied_controls['inflation_layers'] = {
'applied': True,
'surfaces': surface_selections,
'layers': self.mesh_parameters.inflation_layers,
'growth_rate': self.mesh_parameters.growth_rate,
'total_thickness': 2.0,
'applied_at': datetime.now()
}
logger.info(f"✓ Inflation layers added: {self.mesh_parameters.inflation_layers} layers")
return True
except Exception as e:
logger.error(f"Failed to add inflation layers: {str(e)}")
self.applied_controls['inflation_layers'] = {
'applied': False,
'error': str(e),
'applied_at': datetime.now()
}
return False
def apply_all_mesh_controls(self, named_selections: List[str]) -> Dict[str, Any]:
"""
Apply all mesh controls in the correct sequence
Args:
named_selections: List of available named selections
Returns:
Dictionary with results of all applied controls
"""
try:
logger.info("Applying all mesh controls...")
results = {
'success': True,
'controls_applied': [],
'controls_failed': [],
'started_at': datetime.now()
}
# Step 1: Calculate optimal parameters
try:
self.calculate_optimal_mesh_parameters()
results['controls_applied'].append('parameter_calculation')
logger.info("✓ Mesh parameters calculated")
except Exception as e:
logger.error(f"Parameter calculation failed: {str(e)}")
results['controls_failed'].append('parameter_calculation')
results['success'] = False
# Step 2: Apply global settings
try:
if self.apply_global_mesh_settings():
results['controls_applied'].append('global_settings')
logger.info("✓ Global mesh settings applied")
else:
results['controls_failed'].append('global_settings')
results['success'] = False
except Exception as e:
logger.error(f"Global settings failed: {str(e)}")
results['controls_failed'].append('global_settings')
results['success'] = False
# Step 3: Apply local refinement
try:
refinement_regions = [name for name in named_selections
if name in self.mesh_parameters.refinement_regions]
if refinement_regions:
refinement_results = self.apply_local_refinement_controls(refinement_regions)
if any(refinement_results.values()):
results['controls_applied'].append('local_refinement')
logger.info("✓ Local refinement applied")
else:
results['controls_failed'].append('local_refinement')
results['success'] = False
else:
logger.warning("No refinement regions found in named selections")
except Exception as e:
logger.error(f"Local refinement failed: {str(e)}")
results['controls_failed'].append('local_refinement')
results['success'] = False
# Step 4: Add inflation layers
try:
surface_selections = [name for name in named_selections
if 'surface' in name.lower()]
if not surface_selections:
surface_selections = ['blade_surfaces'] # Default surface selection
if self.add_inflation_layers(surface_selections):
results['controls_applied'].append('inflation_layers')
logger.info("✓ Inflation layers added")
else:
results['controls_failed'].append('inflation_layers')
results['success'] = False
except Exception as e:
logger.error(f"Inflation layers failed: {str(e)}")
results['controls_failed'].append('inflation_layers')
results['success'] = False
results['completed_at'] = datetime.now()
results['total_controls'] = len(results['controls_applied']) + len(results['controls_failed'])
results['success_rate'] = len(results['controls_applied']) / results['total_controls'] if results['total_controls'] > 0 else 0
if results['success']:
logger.info(f"✓ All mesh controls applied successfully: {len(results['controls_applied'])} controls")
else:
logger.warning(f"⚠ Mesh controls partially applied: {len(results['controls_applied'])}/{results['total_controls']} successful")
return results
except Exception as e:
logger.error(f"Failed to apply mesh controls: {str(e)}")
return {
'success': False,
'error': str(e),
'controls_applied': [],
'controls_failed': ['all'],
'completed_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:
summary = {
'mesh_parameters': {
'global_element_size': self.mesh_parameters.global_element_size if self.mesh_parameters else None,
'curvature_angle': self.mesh_parameters.curvature_normal_angle if self.mesh_parameters else None,
'inflation_layers': self.mesh_parameters.inflation_layers if self.mesh_parameters else None,
'growth_rate': self.mesh_parameters.growth_rate if self.mesh_parameters else None
},
'applied_controls': dict(self.applied_controls),
'geometry_info': dict(self.geometry_info),
'summary_generated_at': datetime.now()
}
logger.info("✓ Mesh control summary generated")
return summary
except Exception as e:
logger.error(f"Failed to generate mesh control summary: {str(e)}")
return {
'error': str(e),
'summary_generated_at': datetime.now()
}
def clear_mesh_controls(self) -> bool:
"""
Clear all applied mesh controls
Returns:
True if successful, False otherwise
"""
try:
logger.info("Clearing mesh controls...")
clear_script = '''
# Clear all mesh controls
try:
mesh = Model.Mesh
# Remove sizing controls
sizings = mesh.GetChildren(Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.MeshSizing, True)
for sizing in sizings:
sizing.Delete()
# Remove inflation controls
inflations = mesh.GetChildren(Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.MeshInflation, True)
for inflation in inflations:
inflation.Delete()
print("Mesh controls cleared successfully")
except Exception as e:
print("Error clearing mesh controls: " + str(e))
'''
result = self.mechanical.run_python_script(clear_script)
logger.info(f"Clear controls result: {result}")
# Clear local tracking
self.applied_controls.clear()
self.mesh_parameters = None
logger.info("✓ Mesh controls cleared successfully")
return True
except Exception as e:
logger.error(f"Failed to clear mesh controls: {str(e)}")
return False