""" Visualization Exporter for CAE Mesh Generator This module handles mesh visualization export including image generation, 3D visualization, and mesh quality visualization for blade geometry. """ import logging import os import time from typing import Dict, List, Any, Optional, Tuple from datetime import datetime from pathlib import Path from dataclasses import dataclass logger = logging.getLogger(__name__) @dataclass class VisualizationSettings: """Settings for mesh visualization export""" width: int = 1280 height: int = 720 background_color: str = "white" show_edges: bool = True show_nodes: bool = False camera_view: str = "isometric" # isometric, front, side, top image_format: str = "PNG" quality: int = 95 anti_aliasing: bool = True @dataclass class VisualizationResult: """Result container for visualization export""" success: bool = False image_path: Optional[str] = None image_size: Tuple[int, int] = (0, 0) file_size: int = 0 export_time: float = 0.0 error_message: Optional[str] = None warnings: List[str] = None def __post_init__(self): if self.warnings is None: self.warnings = [] class VisualizationExporter: """ Mesh visualization exporter for ANSYS Mechanical This class provides functionality to export mesh visualizations, generate images, and create visual reports for blade geometry. """ def __init__(self, mechanical_session=None, output_dir: str = "output"): """ Initialize visualization exporter Args: mechanical_session: Active PyMechanical session output_dir: Directory for output files """ self.mechanical = mechanical_session self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) # Store mechanical session for real visualization export self.mechanical = mechanical_session logger.info("Visualization Exporter initialized") def export_mesh_image(self, filename: str = None, settings: VisualizationSettings = None) -> VisualizationResult: """ Export mesh visualization as image Args: filename: Output filename (auto-generated if None) settings: Visualization settings Returns: VisualizationResult with export results """ try: logger.info("Starting mesh image export...") start_time = time.time() # Use default settings if not provided if settings is None: settings = VisualizationSettings() # Generate filename if not provided if filename is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"mesh_visualization_{timestamp}.{settings.image_format.lower()}" output_path = self.output_dir / filename return self._export_real_image(output_path, settings, start_time) except Exception as e: logger.error(f"Mesh image export failed: {str(e)}") result = VisualizationResult() result.success = False result.error_message = str(e) return result def _export_real_image(self, output_path: Path, settings: VisualizationSettings, start_time: float) -> VisualizationResult: """ Export real mesh image using PyMechanical Args: output_path: Output file path settings: Visualization settings start_time: Export start time Returns: VisualizationResult with export results """ try: logger.info("Exporting real mesh image using PyMechanical...") # Prepare visualization script based on design document recommendations visualization_script = f''' # Mesh visualization export using PyMechanical API import Ansys.Mechanical.Graphics as Graphics try: print("=== Starting Mesh Visualization Export ===") # Set up graphics export settings graphics_settings = Ansys.Mechanical.Graphics.GraphicsImageExportSettings() graphics_settings.Width = {settings.width} graphics_settings.Height = {settings.height} graphics_settings.Background = Ansys.Mechanical.DataModel.Enums.GraphicsBackgroundType.{settings.background_color.title()} graphics_settings.CurrentGraphicsDisplay = False # Set image format if "{settings.image_format.upper()}" == "PNG": image_format = Ansys.Mechanical.DataModel.Enums.GraphicsImageExportFormat.PNG elif "{settings.image_format.upper()}" == "JPG" or "{settings.image_format.upper()}" == "JPEG": image_format = Ansys.Mechanical.DataModel.Enums.GraphicsImageExportFormat.JPG else: image_format = Ansys.Mechanical.DataModel.Enums.GraphicsImageExportFormat.PNG print("Graphics settings configured") # Set camera view camera_view = "{settings.camera_view.lower()}" if camera_view == "isometric": ExtAPI.Graphics.Camera.SetSpecificViewOrientation(Ansys.Mechanical.DataModel.Enums.ViewOrientationType.Iso) elif camera_view == "front": ExtAPI.Graphics.Camera.SetSpecificViewOrientation(Ansys.Mechanical.DataModel.Enums.ViewOrientationType.Front) elif camera_view == "side": ExtAPI.Graphics.Camera.SetSpecificViewOrientation(Ansys.Mechanical.DataModel.Enums.ViewOrientationType.Right) elif camera_view == "top": ExtAPI.Graphics.Camera.SetSpecificViewOrientation(Ansys.Mechanical.DataModel.Enums.ViewOrientationType.Top) print("Camera view set to: " + camera_view) # Ensure mesh is visible mesh = Model.Mesh if mesh: # Make sure mesh is displayed mesh.Activate() print("Mesh activated for visualization") # Set mesh display options if {str(settings.show_edges).lower()}: print("Showing mesh edges") if {str(settings.show_nodes).lower()}: print("Showing mesh nodes") # Fit view to show entire mesh ExtAPI.Graphics.Camera.FitToScreen() print("Camera fitted to screen") # Export the image export_path = r"{str(output_path).replace(chr(92), chr(92)+chr(92))}" ExtAPI.Graphics.ExportImage(export_path, image_format, graphics_settings) print("SUCCESS: Mesh visualization exported to: " + export_path) except Exception as export_error: print("ERROR: Mesh visualization export failed: " + str(export_error)) print("Common causes:") print("- No mesh generated yet") print("- Graphics system not initialized") print("- Invalid file path or permissions") print("- Unsupported image format") raise export_error ''' # Execute visualization script result_str = self.mechanical.run_python_script(visualization_script) logger.info(f"Visualization script result: {result_str}") export_time = time.time() - start_time # Check if file was created successfully if output_path.exists(): file_size = os.path.getsize(output_path) result = VisualizationResult( success=True, image_path=str(output_path), image_size=(settings.width, settings.height), file_size=file_size, export_time=export_time ) logger.info(f"✓ Real mesh image export completed: {output_path} ({file_size} bytes)") return result else: # Export failed, but try to provide useful feedback result = VisualizationResult() result.success = False result.export_time = export_time if "SUCCESS:" in result_str: result.error_message = "Export script reported success but file not found" result.warnings.append("File may have been created in different location") elif "ERROR:" in result_str: # Extract error message error_lines = [line for line in result_str.split('\n') if line.startswith("ERROR:")] if error_lines: result.error_message = error_lines[0].replace("ERROR:", "").strip() else: result.error_message = "Visualization export failed" else: result.error_message = "No output from visualization script" logger.error(f"✗ Mesh image export failed: {result.error_message}") return result except Exception as e: logger.error(f"Real image export failed: {str(e)}") result = VisualizationResult() result.success = False result.error_message = f"Real export failed: {str(e)}" result.export_time = time.time() - start_time return result def export_quality_visualization(self, filename: str = None, quality_metric: str = "element_quality") -> VisualizationResult: """ Export mesh quality visualization Args: filename: Output filename quality_metric: Quality metric to visualize (element_quality, aspect_ratio, skewness) Returns: VisualizationResult with export results """ try: logger.info(f"Exporting mesh quality visualization: {quality_metric}") # Generate filename if not provided if filename is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"mesh_quality_{quality_metric}_{timestamp}.png" # Use specialized settings for quality visualization settings = VisualizationSettings( width=1280, height=720, background_color="white", show_edges=True, show_nodes=False, camera_view="isometric", image_format="PNG" ) # For real mode, we would need to implement quality contour visualization # This would require additional PyMechanical commands for result visualization logger.warning("Real quality visualization not yet implemented") result = VisualizationResult() result.success = False result.error_message = "Real quality visualization not yet implemented" return result except Exception as e: logger.error(f"Quality visualization export failed: {str(e)}") result = VisualizationResult() result.success = False result.error_message = str(e) return result def get_available_views(self) -> List[str]: """ Get list of available camera views Returns: List of available view names """ return ["isometric", "front", "side", "top", "bottom", "back", "left"] def get_available_formats(self) -> List[str]: """ Get list of available image formats Returns: List of supported image formats """ return ["PNG", "JPG", "JPEG", "BMP", "TIFF"] def cleanup_temp_files(self) -> int: """ Clean up temporary visualization files Returns: Number of files cleaned up """ try: temp_patterns = ["temp_*.png", "temp_*.jpg", "*_temp.*"] cleaned_count = 0 for pattern in temp_patterns: for temp_file in self.output_dir.glob(pattern): try: temp_file.unlink() cleaned_count += 1 except Exception as e: logger.warning(f"Could not delete temp file {temp_file}: {e}") if cleaned_count > 0: logger.info(f"✓ Cleaned up {cleaned_count} temporary visualization files") return cleaned_count except Exception as e: logger.error(f"Cleanup failed: {str(e)}") return 0 def get_export_summary(self) -> Dict[str, Any]: """ Get summary of visualization export capabilities Returns: Dictionary with export summary """ return { 'exporter_type': 'VisualizationExporter', 'output_directory': str(self.output_dir), 'available_views': self.get_available_views(), 'available_formats': self.get_available_formats(), 'supported_features': [ 'mesh_visualization', 'quality_visualization', 'custom_camera_views', 'configurable_resolution', 'multiple_formats' ], 'created_at': datetime.now().isoformat() }