حالات الاستخدام الواقعية

حالات الاستخدام الواقعية

تغطي هذه الصفحة حالات الاستخدام العملية على مستوى الإنتاج لـ @aspose/3d في TypeScript و Node.js، من خدمات HTTP API إلى خطوط أنابيب التحقق CI.


حالة الاستخدام 1: خدمة HTTP لتحويل أصول 3D

أنشئ نقطة نهاية Express.js تقبل ملف 3D مرفوع وتعيد ملف GLB محول:

// 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'));

هذا النمط لا يتطلب ملفات مؤقتة ويعمل داخل حاويات Docker أو بيئات الخوادم بدون خادم.


حالة الاستخدام 2: خط أنابيب CI: التحقق من سلامة أصول 3D

أضف سكريبت Node.js إلى خط أنابيب CI الخاص بك يتحقق من أن جميع ملفات 3D الملتزم بها تُحمَّل بشكل صحيح وتحتوي على هندسة:

// 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.');

أضف إلى package.json السكربتات واستدعِها من خطوة CI الخاصة بك:

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

حالة الاستخدام 3: تحويل دفعي باستخدام خيوط العامل (Worker Threads)

حوّل دليل يحتوي على ملفات OBJ إلى GLB بشكل متوازي باستخدام خيوط العامل في 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();

شغّل باستخدام npx tsc && node dist/main.js. اضبط CONCURRENCY لتتناسب مع الأنوية المتاحة للمعالج CPU.


حالة الاستخدام 4: إعداد النماذج للطباعة ثلاثية الأبعاد

حوّل ملفات المصدر FBX إلى تنسيق 3MF لبرمجيات القطع (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)`);
}

لإخراج STL (أقصى توافق مع أداة التقطيع)، غيّر امتداد الإخراج إلى .stl. لاحظ أن STL يفقد بيانات المادة واللون.


حالة الاستخدام 5: استخراج إحصائيات المشهد لقاعدة بيانات الأصول

امسح مستودعًا من ملفات 3D وأصدر ملخصًا بصيغة JSON لاستيعابه في نظام إدارة الأصول:

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`);

انظر أيضًا

 العربية