85 lines
13 KiB
Python
85 lines
13 KiB
Python
"""
|
|
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 |