Các trường hợp sử dụng thực tế

Các trường hợp sử dụng thực tế

Trang này bao gồm các trường hợp sử dụng thực tế, cấp độ sản xuất cho @aspose/3d trong TypeScript và Node.js, từ dịch vụ API HTTP đến các pipeline xác thực CI.


Trường hợp sử dụng 1: Dịch vụ HTTP để chuyển đổi tài sản 3D

Xây dựng một endpoint Express.js nhận tệp 3D được tải lên và trả về GLB đã chuyển đổi:

// converter-service.ts
import express from 'express';
import multer from 'multer';
import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { GltfSaveOptions } from '@aspose/3d/formats/gltf';

const app = express();
const upload = multer({ storage: multer.memoryStorage() });

app.post('/convert/obj-to-glb', upload.single('file'), (req, res) => {
    if (!req.file) {
        return res.status(400).json({ error: 'No file uploaded' });
    }

    const scene = new Scene();
    const loadOpts = new ObjLoadOptions();
    loadOpts.enableMaterials = true;
    scene.openFromBuffer(req.file.buffer, loadOpts);

    const saveOpts = new GltfSaveOptions();
    saveOpts.binaryMode = true;

    const glbBuffer = scene.saveToBuffer('glb', saveOpts);

    res.setHeader('Content-Type', 'model/gltf-binary');
    res.setHeader('Content-Disposition', 'attachment; filename="output.glb"');
    res.send(glbBuffer);
});

app.listen(3000, () => console.log('Converter service listening on :3000'));

Mẫu này không yêu cầu tệp tạm thời và hoạt động trong các container Docker hoặc môi trường không máy chủ.


Trường hợp sử dụng 2: Pipeline CI: Xác thực tính toàn vẹn của tài sản 3D

Thêm một script Node.js vào pipeline CI của bạn để kiểm tra tất cả các tệp 3D đã commit tải đúng và chứa hình học:

// scripts/validate-assets.ts
import * as fs from 'fs';
import * as path from 'path';
import { Scene, Mesh } from '@aspose/3d';

const ASSET_DIR = './assets/3d';
const EXTENSIONS = ['.obj', '.fbx', '.glb', '.stl', '.dae'];

let failed = 0;

function countMeshes(node: any): number {
    let count = node.entity instanceof Mesh ? 1 : 0;
    for (const child of node.childNodes) count += countMeshes(child);
    return count;
}

for (const file of fs.readdirSync(ASSET_DIR)) {
    const ext = path.extname(file).toLowerCase();
    if (!EXTENSIONS.includes(ext)) continue;

    const filePath = path.join(ASSET_DIR, file);
    try {
        const scene = new Scene();
        scene.open(filePath);
        const meshCount = countMeshes(scene.rootNode);
        if (meshCount === 0) {
            console.error(`FAIL: ${file}: no meshes found`);
            failed++;
        } else {
            console.log(`OK: ${file}: ${meshCount} mesh(es)`);
        }
    } catch (err) {
        console.error(`ERROR: ${file}: ${(err as Error).message}`);
        failed++;
    }
}

if (failed > 0) {
    console.error(`\n${failed} asset(s) failed validation`);
    process.exit(1);
}
console.log('All assets validated successfully.');

Thêm vào package.json các script và gọi từ bước CI của bạn:

{
  "scripts": {
    "validate-assets": "npx ts-node scripts/validate-assets.ts"
  }
}

Trường hợp sử dụng 3: Chuyển đổi hàng loạt với Worker Threads

Chuyển đổi một thư mục các tệp OBJ sang GLB song song bằng các worker thread của Node.js:

// worker.ts
import { workerData, parentPort } from 'worker_threads';
import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { GltfSaveOptions } from '@aspose/3d/formats/gltf';

const { inputPath, outputPath } = workerData as { inputPath: string; outputPath: string };

const scene = new Scene();
const loadOpts = new ObjLoadOptions();
loadOpts.enableMaterials = true;
loadOpts.normalizeNormal = true;
scene.open(inputPath);

const saveOpts = new GltfSaveOptions();
saveOpts.binaryMode = true;
scene.save(outputPath, saveOpts);

parentPort?.postMessage({ ok: true, output: outputPath });
// main.ts
import { Worker } from 'worker_threads';
import * as fs from 'fs';
import * as path from 'path';

const INPUT_DIR = './input';
const OUTPUT_DIR = './output';
const CONCURRENCY = 4;

fs.mkdirSync(OUTPUT_DIR, { recursive: true });

const files = fs.readdirSync(INPUT_DIR).filter(f => f.endsWith('.obj'));
let active = 0;
let index = 0;

function dispatch(): void {
    while (active < CONCURRENCY && index < files.length) {
        const file = files[index++];
        const inputPath = path.join(INPUT_DIR, file);
        const outputPath = path.join(OUTPUT_DIR, file.replace('.obj', '.glb'));

        active++;
        const worker = new Worker(path.resolve('./dist/worker.js'), {
            workerData: { inputPath, outputPath }
        });

        worker.on('message', msg => {
            console.log(`Converted: ${msg.output}`);
            active--;
            dispatch();
        });

        worker.on('error', err => {
            console.error(`Failed: ${inputPath}: ${err.message}`);
            active--;
            dispatch();
        });
    }
}

dispatch();

Chạy với npx tsc && node dist/main.js. Điều chỉnh CONCURRENCY để phù hợp với các lõi CPU có sẵn.


Trường hợp sử dụng 4: Chuẩn bị mô hình cho in 3D

Chuyển đổi các tệp nguồn FBX sang định dạng 3MF cho phần mềm slicer:

import * as fs from 'fs';
import * as path from 'path';
import { Scene } from '@aspose/3d';

const SOURCE_DIR = './models/fbx';
const PRINT_DIR = './models/print';

fs.mkdirSync(PRINT_DIR, { recursive: true });

for (const file of fs.readdirSync(SOURCE_DIR)) {
    if (!file.endsWith('.fbx')) continue;

    const inputPath = path.join(SOURCE_DIR, file);
    const outputPath = path.join(PRINT_DIR, file.replace('.fbx', '.3mf'));

    const scene = new Scene();
    scene.open(inputPath);
    scene.save(outputPath);

    const { size } = fs.statSync(outputPath);
    console.log(`${file}${path.basename(outputPath)} (${Math.round(size / 1024)} KB)`);
}

Đối với đầu ra STL (tương thích tối đa với slicer), thay đổi phần mở rộng đầu ra thành .stl. Lưu ý rằng STL mất dữ liệu vật liệu và màu sắc.


Trường hợp sử dụng 5: Trích xuất thống kê cảnh cho cơ sở dữ liệu tài sản

Quét một kho lưu trữ các tệp 3D và tạo ra bản tóm tắt JSON để nhập vào hệ thống quản lý tài sản:

import * as fs from 'fs';
import * as path from 'path';
import { Scene, Mesh, Node } from '@aspose/3d';

interface AssetStats {
    file: string;
    format: string;
    meshCount: number;
    totalVertices: number;
    totalPolygons: number;
    animationClips: number;
    sizeBytes: number;
}

function collectStats(node: Node, stats: AssetStats): void {
    if (node.entity instanceof Mesh) {
        const mesh = node.entity as Mesh;
        stats.meshCount++;
        stats.totalVertices += mesh.controlPoints.length;
        stats.totalPolygons += mesh.polygonCount;
    }
    for (const child of node.childNodes) {
        collectStats(child, stats);
    }
}

const SCAN_DIR = './assets';
const results: AssetStats[] = [];
const EXTENSIONS = ['.obj', '.fbx', '.glb', '.gltf', '.stl', '.dae', '.3mf'];

for (const file of fs.readdirSync(SCAN_DIR)) {
    const ext = path.extname(file).toLowerCase();
    if (!EXTENSIONS.includes(ext)) continue;

    const filePath = path.join(SCAN_DIR, file);
    const stats: AssetStats = {
        file, format: ext.slice(1).toUpperCase(),
        meshCount: 0, totalVertices: 0, totalPolygons: 0,
        animationClips: 0, sizeBytes: fs.statSync(filePath).size
    };

    try {
        const scene = new Scene();
        scene.open(filePath);
        collectStats(scene.rootNode, stats);
        stats.animationClips = scene.animationClips.length;
    } catch {
        // skip unreadable files
    }

    results.push(stats);
}

fs.writeFileSync('asset-stats.json', JSON.stringify(results, null, 2));
console.log(`Scanned ${results.length} assets → asset-stats.json`);

Xem thêm

 Tiếng Việt