Skip to main content
Glama

3D Asset Processing MCP

by GeoLibra
optimizer-simple.ts10.6 kB
import { NodeIO, Document } from '@gltf-transform/core'; import { dedup, prune, weld, reorder, quantize, } from '@gltf-transform/functions'; import { OptimizationPreset, OptimizationResult, ProcessResult, } from '../types'; import { globalCache } from '../utils/cache'; import { globalFileHandler } from '../utils/file-handler'; import logger from '../utils/logger'; export class ModelOptimizer { private io: NodeIO; constructor() { this.io = new NodeIO(); } /** * Optimize model */ // 兼容测试签名:(inputPath, presetOrOptions, outputPath?) async optimize( filePath: string, presetOrOptions: string | OptimizationPreset | any, _outputPath?: string ): Promise<ProcessResult> { const startTime = Date.now(); try { // Get preset configuration const preset = typeof presetOrOptions === 'string' || (presetOrOptions as any)?.name ? presetOrOptions : { name: 'custom' } as any; const optimizationPreset = typeof preset === 'string' ? this.getPreset(preset) : (preset as OptimizationPreset); if (!optimizationPreset) { throw new Error(`Unknown preset: ${preset}`); } // Check cache const cacheKey = globalCache.generateKey('optimization', { filePath, preset: optimizationPreset.name, }); const cached = globalCache.get<OptimizationResult>(cacheKey); if (cached) { logger.info(`Optimization cache hit for: ${filePath}`); return { success: true, data: cached, metrics: { processingTime: Date.now() - startTime, memoryUsage: process.memoryUsage().heapUsed, }, }; } logger.info( `Starting optimization with preset '${optimizationPreset.name}' for: ${filePath}` ); // Read original document const document = await this.io.read(filePath); const originalStats = this.getDocumentStats(document); // Perform optimization const optimizedDocument = await this.applyOptimizations( document, optimizationPreset ); // Generate output file const outputPath = globalFileHandler.createTempPath('.glb'); await this.io.write(outputPath, optimizedDocument); // Calculate optimization results const optimizedStats = this.getDocumentStats(optimizedDocument); const result = await this.generateOptimizationResult( filePath, outputPath, originalStats, optimizedStats, optimizationPreset, startTime ); // Cache result globalCache.set(cacheKey, result, 1800); // 30分钟缓存 const processingTime = Date.now() - startTime; logger.info( `Optimization completed in ${processingTime}ms for: ${filePath}` ); return { success: true, data: result, metrics: { processingTime, memoryUsage: process.memoryUsage().heapUsed, }, }; } catch (error) { logger.error(`Optimization failed for ${filePath}:`, error); return { success: false, error: error instanceof Error ? error.message : String(error), metrics: { processingTime: Date.now() - startTime, memoryUsage: process.memoryUsage().heapUsed, }, }; } } /** * Apply optimization operations */ private async applyOptimizations( document: Document, preset: OptimizationPreset ): Promise<Document> { const operations = []; // Geometry optimization if (preset.geometry) { operations.push(...preset.geometry); } // Apply all operations if (operations.length > 0) { await document.transform(...operations); } return document; } /** * Get preset configuration */ private getPreset(presetName: string): OptimizationPreset | null { const presets: Record<string, OptimizationPreset> = { 'web-high': { name: 'web-high', description: 'High quality web optimization', geometry: [ dedup(), prune({ keepAttributes: true }), weld({ tolerance: 0.0001 }), reorder({ encoder: 'meshopt' }), quantize({ quantizePosition: 14, quantizeTexcoord: 12, quantizeNormal: 10, quantizeColor: 8, }), ], textures: [], }, 'web-lite': { name: 'web-lite', description: 'Lightweight web optimization', geometry: [ dedup(), prune({ keepAttributes: false }), weld({ tolerance: 0.001 }), quantize({ quantizePosition: 12, quantizeTexcoord: 10, quantizeNormal: 8, }), ], textures: [], }, mobile: { name: 'mobile', description: 'Mobile-optimized preset', geometry: [ dedup(), prune({ keepAttributes: true }), quantize({ quantizePosition: 12, quantizeTexcoord: 10, quantizeNormal: 8, }), ], textures: [], }, 'editor-safe': { name: 'editor-safe', description: 'Editor-safe optimization preserving all data', geometry: [ dedup(), quantize({ quantizePosition: 16, quantizeTexcoord: 16, quantizeNormal: 16, }), ], textures: [], }, }; return presets[presetName] || null; } /** * Get document statistics */ private getDocumentStats(document: Document) { const root = document.getRoot(); const meshes = root.listMeshes(); const materials = root.listMaterials(); const textures = root.listTextures(); let vertexCount = 0; let triangleCount = 0; let textureSize = 0; // Calculate geometry statistics for (const mesh of meshes) { for (const primitive of mesh.listPrimitives()) { const position = primitive.getAttribute('POSITION'); if (position) { vertexCount += position.getCount(); } const indices = primitive.getIndices(); if (indices) { triangleCount += indices.getCount() / 3; } else if (position) { triangleCount += position.getCount() / 3; } } } // Calculate texture size for (const texture of textures) { const image = texture.getImage(); if (image) { textureSize += image.byteLength; } } return { meshCount: meshes.length, materialCount: materials.length, textureCount: textures.length, vertexCount, triangleCount: Math.floor(triangleCount), textureSize, }; } /** * Generate optimization result report */ private async generateOptimizationResult( originalPath: string, optimizedPath: string, originalStats: any, optimizedStats: any, preset: OptimizationPreset, startTime: number ): Promise<OptimizationResult> { // Get file size const originalInfo = await globalFileHandler.getFileInfo(originalPath); const optimizedInfo = await globalFileHandler.getFileInfo(optimizedPath); const compressionRatio = originalInfo.size / optimizedInfo.size; const geometryReduction = originalStats.triangleCount > 0 ? 1 - optimizedStats.triangleCount / originalStats.triangleCount : 0; const textureReduction = originalStats.textureSize > 0 ? 1 - optimizedStats.textureSize / originalStats.textureSize : 0; // Generate report const report = this.generateReport(originalStats, optimizedStats, preset); const reportPath = globalFileHandler.createTempPath('.json'); const fs = require('fs-extra'); await fs.writeFile(reportPath, JSON.stringify(report, null, 2)); // Calculate quality score (simplified version) const visualScore = Math.max(0, 1 - Math.abs(geometryReduction - 0.3)); // Expect 30% geometry reduction const performanceScore = Math.min(1, compressionRatio / 2); // Expect 2x compression const compatibilityScore = 0.95; // Simplified to a fixed value const warnings: string[] = []; const errors: string[] = []; // Check potential issues if (compressionRatio < 1.1) { warnings.push('Low compression ratio achieved'); } if (geometryReduction > 0.8) { warnings.push('High geometry reduction may affect visual quality'); } return { artifacts: { optimized: optimizedPath, report: reportPath, }, metrics: { originalSize: originalInfo.size, optimizedSize: optimizedInfo.size, compressionRatio, geometryReduction, textureReduction, processingTime: Date.now() - startTime, }, quality: { visualScore, performanceScore, compatibilityScore, }, warnings, errors, }; } /** * Generate detailed report */ private generateReport( originalStats: any, optimizedStats: any, preset: OptimizationPreset ) { return { preset: preset.name, timestamp: new Date().toISOString(), original: originalStats, optimized: optimizedStats, changes: { meshCount: optimizedStats.meshCount - originalStats.meshCount, materialCount: optimizedStats.materialCount - originalStats.materialCount, textureCount: optimizedStats.textureCount - originalStats.textureCount, vertexCount: optimizedStats.vertexCount - originalStats.vertexCount, triangleCount: optimizedStats.triangleCount - originalStats.triangleCount, textureSize: optimizedStats.textureSize - originalStats.textureSize, }, reductions: { vertices: originalStats.vertexCount > 0 ? 1 - optimizedStats.vertexCount / originalStats.vertexCount : 0, triangles: originalStats.triangleCount > 0 ? 1 - optimizedStats.triangleCount / originalStats.triangleCount : 0, textures: originalStats.textureSize > 0 ? 1 - optimizedStats.textureSize / originalStats.textureSize : 0, }, }; } /** * Get list of available presets */ getAvailablePresets(): string[] { return ['web-high', 'web-lite', 'mobile', 'editor-safe']; } } // Global optimizer instance export const globalOptimizer = new ModelOptimizer();

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/GeoLibra/3d-asset-processing-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server