""" Simple Mesh Visualizer for CAE Mesh Generator This module provides basic mesh visualization export functionality using real ANSYS Mechanical PyMechanical API to generate mesh images for display purposes. """ import logging import time import os from typing import Dict, Any, Optional from datetime import datetime from dataclasses import dataclass from pathlib import Path logger = logging.getLogger(__name__) @dataclass class VisualizationResult: """Result of mesh visualization export""" success: bool = False image_path: str = None image_size: tuple = None file_size: int = 0 export_time: float = 0.0 error_message: str = None warnings: list = None class SimpleMeshVisualizer: """ Simple mesh visualizer using real ANSYS Mechanical integration This class provides basic mesh visualization export functionality for displaying generated meshes to users. """ def __init__(self, mechanical_session, output_dir: str = "static/visualizations"): """ Initialize simple mesh visualizer Args: mechanical_session: Active PyMechanical session output_dir: Directory to save visualization images """ if mechanical_session is None: raise ValueError("Mechanical session is required for mesh visualization") self.mechanical = mechanical_session self.output_dir = Path(output_dir) # Create output directory if it doesn't exist self.output_dir.mkdir(parents=True, exist_ok=True) logger.info(f"Simple Mesh Visualizer initialized with output directory: {self.output_dir}") def export_mesh_image(self, filename: str = None, width: int = 1280, height: int = 720) -> VisualizationResult: """ Export mesh visualization as an image Args: filename: Output filename (optional, auto-generated if None) width: Image width in pixels height: Image height in pixels Returns: VisualizationResult with export information """ result = VisualizationResult() start_time = time.time() try: logger.info(f"Starting mesh visualization export ({width}x{height})...") # Generate filename if not provided if filename is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"mesh_visualization_{timestamp}.png" # Ensure filename has .png extension if not filename.lower().endswith('.png'): filename += '.png' output_path = self.output_dir / filename # Create PyMechanical script for mesh visualization visualization_script = f'''\n# Export mesh visualization\ntry:\n # Get the mesh object\n mesh = Model.Mesh\n \n # Check if mesh exists\n if mesh is None:\n print("VISUALIZATION_ERROR:No mesh available for visualization")\n else:\n # Set up graphics for export\n try:\n # Get graphics object\n graphics = ExtAPI.Graphics\n \n # Set camera to isometric view\n graphics.Camera.SetFit()\n \n # Set image size\n graphics.ImageOptions.Width = {width}\n graphics.ImageOptions.Height = {height}\n \n # Set background to white\n graphics.Appearance.BackgroundColor = 255, 255, 255\n \n # Show mesh\n mesh.GenerateMesh()\n \n # Export image\n output_file = r"{str(output_path).replace(chr(92), chr(92) + chr(92))}"\n graphics.ExportImage(output_file)\n \n print("VISUALIZATION_SUCCESS:" + output_file)\n \n except Exception as graphics_error:\n # Fallback: try alternative visualization method\n try:\n # Alternative method using Tree operations\n tree = ExtAPI.DataModel.Tree\n mesh_object = tree.Find("Mesh")[0] if tree.Find("Mesh") else None\n \n if mesh_object:\n mesh_object.Activate()\n \n # Try to export using different method\n output_file = r"{str(output_path).replace(chr(92), chr(92) + chr(92))}"\n \n # Simple export attempt\n ExtAPI.Graphics.ExportImage(output_file)\n print("VISUALIZATION_SUCCESS:" + output_file)\n else:\n print("VISUALIZATION_ERROR:Could not find mesh object")\n \n except Exception as fallback_error:\n print("VISUALIZATION_ERROR:" + str(fallback_error))\n \nexcept Exception as e:\n print("VISUALIZATION_ERROR:" + str(e))\n'''\n \n # Execute the visualization script\n script_result = self.mechanical.run_python_script(visualization_script)\n logger.debug(f"Visualization script result: {script_result}")\n \n # Parse the result\n if script_result:\n lines = str(script_result).split('\\n')\n for line in lines:\n if line.startswith('VISUALIZATION_SUCCESS:'):\n exported_path = line.split(':', 1)[1].strip()\n \n # Verify file was created\n if output_path.exists():\n result.success = True\n result.image_path = str(output_path)\n result.file_size = output_path.stat().st_size\n result.image_size = (width, height)\n \n logger.info(f"✓ Mesh visualization exported successfully: {output_path}")\n else:\n result.error_message = "Image file was not created despite success message"\n break\n elif line.startswith('VISUALIZATION_ERROR:'):\n result.error_message = line.split(':', 1)[1].strip()\n break\n \n # If no success/error message found, check if file exists\n if not result.success and not result.error_message:\n if output_path.exists():\n result.success = True\n result.image_path = str(output_path)\n result.file_size = output_path.stat().st_size\n result.image_size = (width, height)\n result.warnings = ["Visualization completed but with unclear status"]\n logger.warning("Visualization file exists but status unclear")\n else:\n result.error_message = "Visualization script completed but no image was generated"\n \n result.export_time = time.time() - start_time\n return result\n \n except Exception as e:\n logger.error(f"Mesh visualization export failed: {str(e)}")\n result.success = False\n result.error_message = str(e)\n result.export_time = time.time() - start_time\n return result\n \n def export_simple_mesh_preview(self) -> VisualizationResult:\n """\n Export a simple mesh preview with default settings\n \n Returns:\n VisualizationResult with export information\n """\n try:\n return self.export_mesh_image(\n filename="mesh_preview.png",\n width=800,\n height=600\n )\n except Exception as e:\n logger.error(f"Simple mesh preview export failed: {str(e)}")\n result = VisualizationResult()\n result.success = False\n result.error_message = str(e)\n return result\n \n def get_visualization_info(self) -> Dict[str, Any]:\n """\n Get information about visualization capabilities\n \n Returns:\n Dictionary with visualization information\n """\n try:\n return {\n 'visualizer_type': 'SimpleMeshVisualizer',\n 'output_directory': str(self.output_dir),\n 'supported_formats': ['PNG'],\n 'default_settings': {\n 'width': 800,\n 'height': 600,\n 'background': 'white',\n 'view': 'isometric'\n },\n 'mechanical_session_active': self.mechanical is not None,\n 'capabilities': {\n 'basic_mesh_export': True,\n 'custom_dimensions': True,\n 'multiple_views': False,\n 'quality_mapping': False,\n 'animation': False\n }\n }\n except Exception as e:\n logger.error(f"Failed to get visualization info: {str(e)}")\n return {\n 'error': str(e),\n 'visualizer_type': 'SimpleMeshVisualizer',\n 'mechanical_session_active': False\n }\n \n def cleanup_old_images(self, max_age_hours: int = 24) -> Dict[str, Any]:\n """\n Clean up old visualization images\n \n Args:\n max_age_hours: Maximum age of images to keep (in hours)\n \n Returns:\n Dictionary with cleanup results\n """\n try:\n cleanup_result = {\n 'files_removed': 0,\n 'space_freed': 0,\n 'errors': []\n }\n \n if not self.output_dir.exists():\n return cleanup_result\n \n current_time = time.time()\n max_age_seconds = max_age_hours * 3600\n \n for image_file in self.output_dir.glob('*.png'):\n try:\n file_age = current_time - image_file.stat().st_mtime\n \n if file_age > max_age_seconds:\n file_size = image_file.stat().st_size\n image_file.unlink()\n \n cleanup_result['files_removed'] += 1\n cleanup_result['space_freed'] += file_size\n \n logger.debug(f"Removed old visualization: {image_file}")\n \n except Exception as file_error:\n cleanup_result['errors'].append(f"Failed to remove {image_file}: {str(file_error)}")\n \n if cleanup_result['files_removed'] > 0:\n logger.info(f"Cleaned up {cleanup_result['files_removed']} old visualization files, freed {cleanup_result['space_freed']} bytes")\n \n return cleanup_result\n \n except Exception as e:\n logger.error(f"Cleanup failed: {str(e)}")\n return {\n 'files_removed': 0,\n 'space_freed': 0,\n 'errors': [str(e)]\n }\n \n def get_available_images(self) -> Dict[str, Any]:\n """\n Get list of available visualization images\n \n Returns:\n Dictionary with available images information\n """\n try:\n images = []\n \n if self.output_dir.exists():\n for image_file in self.output_dir.glob('*.png'):\n try:\n stat = image_file.stat()\n images.append({\n 'filename': image_file.name,\n 'path': str(image_file),\n 'size': stat.st_size,\n 'created': datetime.fromtimestamp(stat.st_ctime).isoformat(),\n 'modified': datetime.fromtimestamp(stat.st_mtime).isoformat()\n })\n except Exception as file_error:\n logger.warning(f"Could not get info for {image_file}: {str(file_error)}")\n \n return {\n 'success': True,\n 'images': sorted(images, key=lambda x: x['modified'], reverse=True),\n 'total_images': len(images),\n 'total_size': sum(img['size'] for img in images),\n 'output_directory': str(self.output_dir)\n }\n \n except Exception as e:\n logger.error(f"Failed to get available images: {str(e)}")\n return {\n 'success': False,\n 'error': str(e),\n 'images': [],\n 'total_images': 0\n }\n