""" 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