582 lines
23 KiB
Python
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 |