Skip to content

Enhanced 3D Model Metadata - Technical Design

Related Feature: [METADATA-001] Enhanced 3D Model Metadata Display
Version: 1.0
Last Updated: October 1, 2025

Architecture Overview

graph TB
    A[3D Files] --> B[Enhanced Parser]
    B --> C[Metadata Extraction]
    C --> D[Database Storage]
    D --> E[API Layer]
    E --> F[Frontend Display]

    subgraph "Parser Layer"
        B --> B1[BambuParser+]
        B --> B2[3MFAnalyzer]
        B --> B3[STLAnalyzer]
    end

    subgraph "Analysis Layer"
        C --> C1[Physical Analysis]
        C --> C2[Cost Calculation]
        C --> C3[Quality Assessment]
        C --> C4[Compatibility Check]
    end

    subgraph "Storage Layer"
        D --> D1[Files Table]
        D --> D2[Metadata Table]
        D --> D3[Cache Layer]
    end

Implementation Details

1. Parser Enhancements

Extended BambuParser

# src/services/bambu_parser.py

class EnhancedBambuParser(BambuParser):
    """Enhanced parser with comprehensive metadata extraction."""

    # Extended metadata patterns for G-code parsing
    ADVANCED_METADATA_PATTERNS = {
        # Physical properties
        'model_width': re.compile(r'; model_width = ([\d.]+)', re.IGNORECASE),
        'model_depth': re.compile(r'; model_depth = ([\d.]+)', re.IGNORECASE),
        'model_height': re.compile(r'; model_height = ([\d.]+)', re.IGNORECASE),
        'model_volume': re.compile(r'; model_volume = ([\d.]+)', re.IGNORECASE),

        # Print settings
        'nozzle_diameter': re.compile(r'; nozzle_diameter = ([\d.]+)', re.IGNORECASE),
        'wall_loops': re.compile(r'; wall_loops = (\d+)', re.IGNORECASE),
        'top_shell_layers': re.compile(r'; top_shell_layers = (\d+)', re.IGNORECASE),
        'bottom_shell_layers': re.compile(r'; bottom_shell_layers = (\d+)', re.IGNORECASE),
        'infill_pattern': re.compile(r'; sparse_infill_pattern = (.+)', re.IGNORECASE),

        # Advanced settings
        'overhang_speed': re.compile(r'; overhang_speed = ([\d.]+)', re.IGNORECASE),
        'bridge_speed': re.compile(r'; bridge_speed = ([\d.]+)', re.IGNORECASE),
        'support_angle': re.compile(r'; support_threshold_angle = (\d+)', re.IGNORECASE),

        # Compatibility
        'compatible_printers': re.compile(r'; compatible_printers = (.+)', re.IGNORECASE),
        'bed_type': re.compile(r'; curr_bed_type = (.+)', re.IGNORECASE),
        'slicer_version': re.compile(r'; (.+Studio) ([\d.]+)', re.IGNORECASE),

        # Cost and material
        'filament_density': re.compile(r'; filament_density = ([\d.,]+)', re.IGNORECASE),
        'filament_diameter': re.compile(r'; filament_diameter = ([\d.,]+)', re.IGNORECASE),
        'total_weight': re.compile(r'; total filament weight \[g\] = ([\d.]+)', re.IGNORECASE),
        'total_length': re.compile(r'; total filament length \[mm\] = ([\d.,]+)', re.IGNORECASE),
    }

    def extract_comprehensive_metadata(self, content: str) -> Dict[str, Any]:
        """Extract all available metadata from G-code content."""
        metadata = self._extract_gcode_metadata(content)  # Base implementation

        # Extract advanced metadata
        for key, pattern in self.ADVANCED_METADATA_PATTERNS.items():
            match = pattern.search(content)
            if match:
                value = match.group(1).strip()
                metadata[key] = self._convert_value(key, value)

        # Calculate derived values
        metadata.update(self._calculate_derived_metrics(metadata))

        return metadata

    def _convert_value(self, key: str, value: str) -> Any:
        """Convert string values to appropriate types."""
        if key in ['model_width', 'model_depth', 'model_height', 'model_volume', 
                   'nozzle_diameter', 'overhang_speed', 'bridge_speed']:
            return float(value)
        elif key in ['wall_loops', 'top_shell_layers', 'bottom_shell_layers', 'support_angle']:
            return int(value)
        elif key in ['filament_density', 'filament_diameter', 'total_length']:
            # Handle comma-separated values for multi-material
            return [float(x.strip()) for x in value.split(',')]
        elif key == 'compatible_printers':
            # Parse printer list
            return [p.strip().strip('"') for p in value.split(',')]
        else:
            return value

    def _calculate_derived_metrics(self, metadata: Dict[str, Any]) -> Dict[str, Any]:
        """Calculate derived metrics from extracted data."""
        derived = {}

        # Calculate wall thickness
        if 'wall_loops' in metadata and 'nozzle_diameter' in metadata:
            derived['wall_thickness'] = metadata['wall_loops'] * metadata['nozzle_diameter']

        # Calculate total filament weight and length
        if 'filament_density' in metadata and 'total_length' in metadata:
            densities = metadata['filament_density']
            lengths = metadata['total_length']
            if isinstance(densities, list) and isinstance(lengths, list):
                # Multi-material calculation
                weights = []
                for i, (density, length) in enumerate(zip(densities, lengths)):
                    # Volume = π * r² * length (assuming 1.75mm diameter)
                    radius = 1.75 / 2  # mm
                    volume = 3.14159 * (radius ** 2) * length  # mm³
                    weight = (volume / 1000) * density  # convert to cm³ then to grams
                    weights.append(weight)
                derived['total_filament_weight'] = sum(weights)
                derived['filament_weights'] = weights

        # Calculate complexity score
        derived['complexity_score'] = self._calculate_complexity_score(metadata)

        return derived

    def _calculate_complexity_score(self, metadata: Dict[str, Any]) -> int:
        """Calculate print complexity score (1-10)."""
        score = 5  # Base score

        # Layer height factor
        if 'layer_height' in metadata:
            if metadata['layer_height'] <= 0.1:
                score += 2  # Very fine layers
            elif metadata['layer_height'] <= 0.15:
                score += 1  # Fine layers
            elif metadata['layer_height'] >= 0.3:
                score -= 1  # Coarse layers

        # Support factor
        if metadata.get('support_used', False):
            score += 1

        # Infill complexity
        if 'infill_pattern' in metadata:
            complex_patterns = ['gyroid', 'voronoi', 'lightning']
            if any(pattern in metadata['infill_pattern'].lower() for pattern in complex_patterns):
                score += 1

        # Multi-material complexity
        if isinstance(metadata.get('filament_type'), list):
            score += len(metadata['filament_type']) - 1

        return max(1, min(10, score))

New 3MF Analyzer

# src/services/threemf_analyzer.py

import json
import zipfile
from typing import Dict, Any, List, Tuple
from pathlib import Path

class ThreeMFAnalyzer:
    """Comprehensive analyzer for 3MF files."""

    def __init__(self):
        self.supported_extensions = ['.3mf']

    async def analyze_file(self, file_path: Path) -> Dict[str, Any]:
        """Analyze 3MF file and extract comprehensive metadata."""
        metadata = {
            'physical_properties': {},
            'print_settings': {},
            'material_info': {},
            'compatibility': {},
            'cost_analysis': {},
            'quality_metrics': {}
        }

        try:
            with zipfile.ZipFile(file_path, 'r') as zip_file:
                # Analyze different components
                metadata['physical_properties'] = await self._analyze_model_geometry(zip_file)
                metadata['print_settings'] = await self._analyze_print_settings(zip_file)
                metadata['material_info'] = await self._analyze_material_usage(zip_file)
                metadata['compatibility'] = await self._analyze_compatibility(zip_file)
                metadata['cost_analysis'] = await self._calculate_costs(metadata)
                metadata['quality_metrics'] = await self._assess_quality(metadata)

        except Exception as e:
            logger.error("Failed to analyze 3MF file", file_path=str(file_path), error=str(e))

        return metadata

    async def _analyze_model_geometry(self, zip_file: zipfile.ZipFile) -> Dict[str, Any]:
        """Extract physical properties from 3MF model files."""
        geometry = {}

        try:
            # Parse plate JSON for object layout
            with zip_file.open('Metadata/plate_1.json') as f:
                plate_data = json.loads(f.read().decode('utf-8'))

            # Extract bounding box information
            if 'bbox_all' in plate_data:
                bbox = plate_data['bbox_all']
                geometry['dimensions'] = {
                    'width': bbox[2] - bbox[0],
                    'depth': bbox[3] - bbox[1],
                    'height': 0  # Will be calculated from layer info
                }
                geometry['bounding_box'] = {
                    'min_x': bbox[0], 'min_y': bbox[1],
                    'max_x': bbox[2], 'max_y': bbox[3]
                }

            # Extract object count and details
            if 'bbox_objects' in plate_data:
                objects = plate_data['bbox_objects']
                geometry['object_count'] = len([obj for obj in objects if obj.get('name') != 'wipe_tower'])
                geometry['objects'] = []

                total_area = 0
                for obj in objects:
                    if obj.get('name') != 'wipe_tower':
                        obj_info = {
                            'name': obj.get('name', 'Unknown'),
                            'area': obj.get('area', 0),
                            'layer_height': obj.get('layer_height', 0.2)
                        }
                        geometry['objects'].append(obj_info)
                        total_area += obj.get('area', 0)

                geometry['total_print_area'] = total_area

        except Exception as e:
            logger.warning("Could not extract geometry data", error=str(e))

        return geometry

    async def _analyze_print_settings(self, zip_file: zipfile.ZipFile) -> Dict[str, Any]:
        """Extract print settings from configuration files."""
        settings = {}

        try:
            # Parse process settings
            with zip_file.open('Metadata/process_settings_1.config') as f:
                config_data = json.loads(f.read().decode('utf-8'))

            # Extract key print parameters
            settings.update({
                'layer_height': config_data.get('layer_height', [0.2])[0],
                'first_layer_height': config_data.get('first_layer_height', [0.2])[0],
                'wall_loops': config_data.get('wall_loops', [2])[0],
                'top_shell_layers': config_data.get('top_shell_layers', 3),
                'bottom_shell_layers': config_data.get('bottom_shell_layers', 3),
                'infill_density': float(config_data.get('sparse_infill_density', ['20%'])[0].rstrip('%')),
                'infill_pattern': config_data.get('sparse_infill_pattern', 'gyroid'),
                'support_used': config_data.get('enable_support', False),
                'nozzle_diameter': config_data.get('nozzle_diameter', [0.4])[0],
            })

            # Extract temperature settings
            settings.update({
                'nozzle_temperature': config_data.get('nozzle_temperature', [210])[0],
                'bed_temperature': config_data.get('bed_temperature', [60])[0],
                'chamber_temperature': config_data.get('chamber_temperature', [0])[0],
            })

            # Extract speed settings
            settings.update({
                'print_speed': config_data.get('outer_wall_speed', [50])[0],
                'infill_speed': config_data.get('sparse_infill_speed', [100])[0],
                'travel_speed': config_data.get('travel_speed', [150])[0],
            })

        except Exception as e:
            logger.warning("Could not extract print settings", error=str(e))

        return settings

    async def _analyze_material_usage(self, zip_file: zipfile.ZipFile) -> Dict[str, Any]:
        """Extract material and filament information."""
        material_info = {}

        try:
            # Parse slice info for material data
            with zip_file.open('Metadata/slice_info.config') as f:
                slice_content = f.read().decode('utf-8')

            # Extract weight and prediction from XML
            import xml.etree.ElementTree as ET
            root = ET.fromstring(slice_content)

            plate = root.find('plate')
            if plate is not None:
                material_info['estimated_weight'] = float(plate.find("metadata[@key='weight']").get('value', 0))
                material_info['estimated_time'] = int(plate.find("metadata[@key='prediction']").get('value', 0))
                material_info['support_used'] = plate.find("metadata[@key='support_used']").get('value', 'false') == 'true'

                # Extract filament mapping
                filament_maps = plate.find("metadata[@key='filament_maps']")
                if filament_maps is not None:
                    maps = filament_maps.get('value', '').split()
                    material_info['filament_slots'] = [int(slot) for slot in maps if slot.isdigit()]

            # Parse plate JSON for color information
            with zip_file.open('Metadata/plate_1.json') as f:
                plate_data = json.loads(f.read().decode('utf-8'))

            material_info['filament_colors'] = plate_data.get('filament_colors', [])
            material_info['filament_ids'] = plate_data.get('filament_ids', [])

        except Exception as e:
            logger.warning("Could not extract material usage", error=str(e))

        return material_info

    async def _calculate_costs(self, metadata: Dict[str, Any]) -> Dict[str, Any]:
        """Calculate comprehensive cost breakdown."""
        costs = {
            'material_cost': 0.0,
            'energy_cost': 0.0,
            'total_cost': 0.0,
            'cost_per_gram': 0.0,
            'breakdown': {}
        }

        try:
            # Material cost calculation
            weight = metadata.get('material_info', {}).get('estimated_weight', 0)
            if weight > 0:
                # Use configurable material costs (EUR per kg)
                material_cost_per_kg = 25.0  # Default PLA cost
                costs['material_cost'] = (weight / 1000) * material_cost_per_kg
                costs['cost_per_gram'] = material_cost_per_kg / 1000

            # Energy cost calculation
            print_time = metadata.get('material_info', {}).get('estimated_time', 0)
            if print_time > 0:
                # Estimate power consumption (Watts)
                base_power = 50  # Base printer consumption
                heated_bed_power = 120 if metadata.get('print_settings', {}).get('bed_temperature', 0) > 30 else 0
                hotend_power = 40

                total_power = base_power + heated_bed_power + hotend_power
                energy_kwh = (total_power * print_time) / (1000 * 3600)  # Convert to kWh
                energy_cost_per_kwh = 0.30  # EUR per kWh
                costs['energy_cost'] = energy_kwh * energy_cost_per_kwh

            costs['total_cost'] = costs['material_cost'] + costs['energy_cost']

            # Detailed breakdown
            costs['breakdown'] = {
                'filament': costs['material_cost'],
                'electricity': costs['energy_cost'],
                'wear_and_tear': costs['total_cost'] * 0.05,  # 5% for printer wear
                'labor': 0.0  # Can be configured for service providers
            }

        except Exception as e:
            logger.warning("Could not calculate costs", error=str(e))

        return costs

2. Database Schema Updates

Migration Script

-- Migration: 006_enhanced_metadata.sql

-- Add new columns to files table
ALTER TABLE files ADD COLUMN model_width DECIMAL(8,3);
ALTER TABLE files ADD COLUMN model_depth DECIMAL(8,3); 
ALTER TABLE files ADD COLUMN model_height DECIMAL(8,3);
ALTER TABLE files ADD COLUMN model_volume DECIMAL(10,3);
ALTER TABLE files ADD COLUMN surface_area DECIMAL(10,3);
ALTER TABLE files ADD COLUMN object_count INTEGER DEFAULT 1;

-- Print settings
ALTER TABLE files ADD COLUMN nozzle_diameter DECIMAL(3,2);
ALTER TABLE files ADD COLUMN wall_count INTEGER;
ALTER TABLE files ADD COLUMN wall_thickness DECIMAL(4,2);
ALTER TABLE files ADD COLUMN infill_pattern VARCHAR(50);
ALTER TABLE files ADD COLUMN first_layer_height DECIMAL(4,3);

-- Material information
ALTER TABLE files ADD COLUMN total_filament_weight DECIMAL(8,3);
ALTER TABLE files ADD COLUMN filament_length DECIMAL(10,2);
ALTER TABLE files ADD COLUMN filament_colors TEXT; -- JSON array
ALTER TABLE files ADD COLUMN waste_weight DECIMAL(8,3);

-- Cost analysis
ALTER TABLE files ADD COLUMN material_cost DECIMAL(8,2);
ALTER TABLE files ADD COLUMN energy_cost DECIMAL(6,2);
ALTER TABLE files ADD COLUMN total_cost DECIMAL(8,2);

-- Quality metrics
ALTER TABLE files ADD COLUMN complexity_score INTEGER;
ALTER TABLE files ADD COLUMN success_probability DECIMAL(3,2);
ALTER TABLE files ADD COLUMN difficulty_level VARCHAR(20);
ALTER TABLE files ADD COLUMN overhang_percentage DECIMAL(5,2);

-- Compatibility
ALTER TABLE files ADD COLUMN compatible_printers TEXT; -- JSON array
ALTER TABLE files ADD COLUMN slicer_name VARCHAR(100);
ALTER TABLE files ADD COLUMN slicer_version VARCHAR(50);
ALTER TABLE files ADD COLUMN profile_name VARCHAR(100);

-- Create flexible metadata storage table
CREATE TABLE file_metadata (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    file_id VARCHAR(50) NOT NULL,
    category VARCHAR(50) NOT NULL,
    key VARCHAR(100) NOT NULL,
    value TEXT,
    data_type VARCHAR(20) DEFAULT 'string',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (file_id) REFERENCES files(id) ON DELETE CASCADE,
    UNIQUE(file_id, category, key)
);

-- Create indexes for performance
CREATE INDEX idx_file_metadata_file_id ON file_metadata(file_id);
CREATE INDEX idx_file_metadata_category ON file_metadata(category);
CREATE INDEX idx_files_complexity ON files(complexity_score);
CREATE INDEX idx_files_dimensions ON files(model_width, model_depth, model_height);
CREATE INDEX idx_files_cost ON files(total_cost);

3. API Enhancements

Enhanced Response Models

# src/models/file.py

from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from decimal import Decimal

class PhysicalProperties(BaseModel):
    """Physical properties of the 3D model."""
    width: Optional[float] = Field(None, description="Model width in mm")
    depth: Optional[float] = Field(None, description="Model depth in mm") 
    height: Optional[float] = Field(None, description="Model height in mm")
    volume: Optional[float] = Field(None, description="Model volume in cm³")
    surface_area: Optional[float] = Field(None, description="Surface area in cm²")
    object_count: int = Field(1, description="Number of objects in the model")
    bounding_box: Optional[Dict[str, float]] = Field(None, description="3D bounding box coordinates")

class PrintSettings(BaseModel):
    """Print configuration settings."""
    layer_height: Optional[float] = Field(None, description="Layer height in mm")
    first_layer_height: Optional[float] = Field(None, description="First layer height in mm")
    nozzle_diameter: Optional[float] = Field(None, description="Nozzle diameter in mm")
    wall_count: Optional[int] = Field(None, description="Number of perimeter walls")
    wall_thickness: Optional[float] = Field(None, description="Total wall thickness in mm")
    infill_density: Optional[float] = Field(None, description="Infill density percentage")
    infill_pattern: Optional[str] = Field(None, description="Infill pattern type")
    support_used: Optional[bool] = Field(None, description="Whether supports are required")
    nozzle_temperature: Optional[int] = Field(None, description="Nozzle temperature in °C")
    bed_temperature: Optional[int] = Field(None, description="Bed temperature in °C")

class MaterialRequirements(BaseModel):
    """Material usage and requirements."""
    total_weight: Optional[float] = Field(None, description="Total filament weight in grams")
    filament_length: Optional[float] = Field(None, description="Total filament length in meters")
    filament_colors: Optional[List[str]] = Field(None, description="Filament color codes")
    material_types: Optional[List[str]] = Field(None, description="Material types (PLA, PETG, etc.)")
    waste_weight: Optional[float] = Field(None, description="Estimated waste material in grams")
    multi_material: bool = Field(False, description="Whether multi-material printing is used")

class CostBreakdown(BaseModel):
    """Detailed cost analysis."""
    material_cost: Optional[Decimal] = Field(None, description="Material cost in EUR")
    energy_cost: Optional[Decimal] = Field(None, description="Energy cost in EUR")
    total_cost: Optional[Decimal] = Field(None, description="Total estimated cost in EUR")
    cost_per_gram: Optional[Decimal] = Field(None, description="Cost per gram in EUR")
    breakdown: Optional[Dict[str, Decimal]] = Field(None, description="Detailed cost components")

class QualityMetrics(BaseModel):
    """Print quality and difficulty assessment."""
    complexity_score: Optional[int] = Field(None, description="Complexity score 1-10", ge=1, le=10)
    difficulty_level: Optional[str] = Field(None, description="Beginner, Intermediate, Advanced")
    success_probability: Optional[float] = Field(None, description="Estimated success rate 0-100", ge=0, le=100)
    overhang_percentage: Optional[float] = Field(None, description="Percentage of overhanging surfaces")
    recommended_settings: Optional[Dict[str, Any]] = Field(None, description="Optimization suggestions")

class CompatibilityInfo(BaseModel):
    """Printer and software compatibility."""
    compatible_printers: Optional[List[str]] = Field(None, description="List of compatible printer models")
    slicer_name: Optional[str] = Field(None, description="Slicer software name")
    slicer_version: Optional[str] = Field(None, description="Slicer software version")
    profile_name: Optional[str] = Field(None, description="Print profile name")
    bed_type: Optional[str] = Field(None, description="Required bed surface type")
    required_features: Optional[List[str]] = Field(None, description="Required printer features")

class EnhancedFileMetadata(BaseModel):
    """Comprehensive file metadata."""
    physical_properties: Optional[PhysicalProperties] = None
    print_settings: Optional[PrintSettings] = None
    material_requirements: Optional[MaterialRequirements] = None
    cost_breakdown: Optional[CostBreakdown] = None
    quality_metrics: Optional[QualityMetrics] = None
    compatibility_info: Optional[CompatibilityInfo] = None

class EnhancedFileResponse(File):
    """File response with enhanced metadata."""
    enhanced_metadata: Optional[EnhancedFileMetadata] = None
    last_analyzed: Optional[datetime] = Field(None, description="When metadata was last extracted")

New API Endpoints

# src/api/routes/files.py

@router.get("/files/{file_id}/metadata/enhanced", response_model=EnhancedFileMetadata)
async def get_enhanced_metadata(
    file_id: str,
    force_refresh: bool = Query(False, description="Force re-analysis of file"),
    db: Session = Depends(get_db)
) -> EnhancedFileMetadata:
    """Get comprehensive metadata for a file."""

    file_record = db.query(FileModel).filter(FileModel.id == file_id).first()
    if not file_record:
        raise HTTPException(status_code=404, detail="File not found")

    # Check if we need to analyze the file
    if force_refresh or not file_record.last_analyzed or _needs_reanalysis(file_record):
        await _analyze_file_metadata(file_record, db)

    return _build_enhanced_metadata_response(file_record)

@router.get("/files/{file_id}/analysis", response_model=FileAnalysisResponse)
async def analyze_file(
    file_id: str,
    include_recommendations: bool = Query(True),
    db: Session = Depends(get_db)
) -> FileAnalysisResponse:
    """Get detailed file analysis with optimization recommendations."""

    metadata = await get_enhanced_metadata(file_id, db=db)

    analysis = {
        'printability_score': _calculate_printability_score(metadata),
        'optimization_suggestions': [],
        'risk_factors': [],
        'estimated_success_rate': metadata.quality_metrics.success_probability if metadata.quality_metrics else None
    }

    if include_recommendations:
        analysis['optimization_suggestions'] = _generate_optimization_suggestions(metadata)
        analysis['risk_factors'] = _identify_risk_factors(metadata)

    return FileAnalysisResponse(**analysis)

@router.get("/files/{file_id}/compatibility/{printer_id}")
async def check_printer_compatibility(
    file_id: str,
    printer_id: str,
    db: Session = Depends(get_db)
) -> CompatibilityCheckResponse:
    """Check if file is compatible with specific printer."""

    file_metadata = await get_enhanced_metadata(file_id, db=db)
    printer_info = await get_printer_info(printer_id, db=db)

    compatibility = _check_compatibility(file_metadata, printer_info)

    return CompatibilityCheckResponse(
        compatible=compatibility['compatible'],
        issues=compatibility['issues'],
        warnings=compatibility['warnings'],
        recommendations=compatibility['recommendations']
    )

4. Frontend Implementation

Component Structure

// frontend/js/components/FileMetadata/

interface EnhancedMetadata {
  physical_properties?: PhysicalProperties;
  print_settings?: PrintSettings;
  material_requirements?: MaterialRequirements;
  cost_breakdown?: CostBreakdown;
  quality_metrics?: QualityMetrics;
  compatibility_info?: CompatibilityInfo;
}

class EnhancedFileMetadata {
  private fileId: string;
  private metadata: EnhancedMetadata | null = null;

  constructor(fileId: string) {
    this.fileId = fileId;
  }

  async loadMetadata(forceRefresh: boolean = false): Promise<void> {
    try {
      const response = await fetch(`/api/v1/files/${this.fileId}/metadata/enhanced?force_refresh=${forceRefresh}`);
      this.metadata = await response.json();
    } catch (error) {
      console.error('Failed to load enhanced metadata:', error);
      throw error;
    }
  }

  render(): string {
    if (!this.metadata) {
      return this.renderLoadingState();
    }

    return `
      <div class="enhanced-file-metadata">
        ${this.renderSummaryCards()}
        ${this.renderDetailedSections()}
        ${this.renderAnalysisSection()}
      </div>
    `;
  }

  private renderSummaryCards(): string {
    const { physical_properties, cost_breakdown, quality_metrics } = this.metadata!;

    return `
      <div class="metadata-summary-cards">
        <div class="summary-card dimensions">
          <div class="card-icon">📐</div>
          <div class="card-content">
            <div class="card-title">Dimensions</div>
            <div class="card-value">
              ${physical_properties?.width?.toFixed(1) || '?'} × 
              ${physical_properties?.depth?.toFixed(1) || '?'} × 
              ${physical_properties?.height?.toFixed(1) || '?'} mm
            </div>
          </div>
        </div>

        <div class="summary-card cost">
          <div class="card-icon">💰</div>
          <div class="card-content">
            <div class="card-title">Total Cost</div>
            <div class="card-value">€${cost_breakdown?.total_cost?.toFixed(2) || '-.--'}</div>
          </div>
        </div>

        <div class="summary-card quality">
          <div class="card-icon">⭐</div>
          <div class="card-content">
            <div class="card-title">Quality Score</div>
            <div class="card-value">${quality_metrics?.complexity_score || '-'}/10</div>
          </div>
        </div>

        <div class="summary-card objects">
          <div class="card-icon">🧩</div>
          <div class="card-content">
            <div class="card-title">Objects</div>
            <div class="card-value">${physical_properties?.object_count || 1}</div>
          </div>
        </div>
      </div>
    `;
  }

  private renderDetailedSections(): string {
    return `
      <div class="metadata-detailed-sections">
        <div class="sections-grid">
          ${this.renderPhysicalProperties()}
          ${this.renderPrintSettings()}
          ${this.renderMaterialRequirements()}
          ${this.renderCompatibilityInfo()}
        </div>
      </div>
    `;
  }

  private renderPhysicalProperties(): string {
    const props = this.metadata!.physical_properties;
    if (!props) return '';

    return `
      <div class="metadata-section physical-properties">
        <h4><span class="section-icon">📋</span> Model Information</h4>
        <div class="property-grid">
          ${props.width ? `<div class="property-item"><span class="label">Width:</span> <span class="value">${props.width.toFixed(1)} mm</span></div>` : ''}
          ${props.depth ? `<div class="property-item"><span class="label">Depth:</span> <span class="value">${props.depth.toFixed(1)} mm</span></div>` : ''}
          ${props.height ? `<div class="property-item"><span class="label">Height:</span> <span class="value">${props.height.toFixed(1)} mm</span></div>` : ''}
          ${props.volume ? `<div class="property-item"><span class="label">Volume:</span> <span class="value">${props.volume.toFixed(1)} cm³</span></div>` : ''}
          ${props.surface_area ? `<div class="property-item"><span class="label">Surface Area:</span> <span class="value">${props.surface_area.toFixed(1)} cm²</span></div>` : ''}
          <div class="property-item"><span class="label">Objects:</span> <span class="value">${props.object_count}</span></div>
        </div>
      </div>
    `;
  }

  // Similar methods for other sections...
}

CSS Styling

/* frontend/css/enhanced-metadata.css */

.enhanced-file-metadata {
  padding: 1rem;
  background: var(--bg-secondary);
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.metadata-summary-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1rem;
  margin-bottom: 2rem;
}

.summary-card {
  display: flex;
  align-items: center;
  padding: 1rem;
  background: var(--bg-primary);
  border-radius: 6px;
  border: 1px solid var(--border-color);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.summary-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.card-icon {
  font-size: 2rem;
  margin-right: 1rem;
  opacity: 0.8;
}

.card-content {
  flex: 1;
}

.card-title {
  font-size: 0.875rem;
  color: var(--text-secondary);
  margin-bottom: 0.25rem;
}

.card-value {
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--text-primary);
}

.sections-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1.5rem;
}

.metadata-section {
  background: var(--bg-primary);
  border-radius: 6px;
  border: 1px solid var(--border-color);
  padding: 1.5rem;
}

.metadata-section h4 {
  display: flex;
  align-items: center;
  margin: 0 0 1rem 0;
  font-size: 1.125rem;
  color: var(--text-primary);
}

.section-icon {
  margin-right: 0.5rem;
  font-size: 1.25rem;
}

.property-grid {
  display: grid;
  gap: 0.75rem;
}

.property-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border-color-light);
}

.property-item:last-child {
  border-bottom: none;
}

.property-item .label {
  color: var(--text-secondary);
  font-weight: 500;
}

.property-item .value {
  color: var(--text-primary);
  font-weight: 600;
}

/* Responsive design */
@media (max-width: 768px) {
  .metadata-summary-cards {
    grid-template-columns: repeat(2, 1fr);
  }

  .sections-grid {
    grid-template-columns: 1fr;
  }

  .summary-card {
    padding: 0.75rem;
  }

  .card-icon {
    font-size: 1.5rem;
    margin-right: 0.75rem;
  }
}

@media (max-width: 480px) {
  .metadata-summary-cards {
    grid-template-columns: 1fr;
  }
}

Performance Considerations

Caching Strategy

  • File-level caching: Cache parsed metadata until file changes
  • Database indexing: Index frequently queried metadata fields
  • API response caching: Cache API responses with appropriate TTL
  • Background processing: Parse metadata asynchronously for large files

Optimization Techniques

  • Lazy loading: Load detailed metadata only when requested
  • Progressive enhancement: Show basic info first, then enhanced details
  • Batch processing: Process multiple files in parallel
  • Memory management: Stream large files instead of loading fully into memory

This technical design provides a comprehensive foundation for implementing the enhanced 3D model metadata feature, with detailed code examples and implementation guidance for each component of the system.