كيفية استعراض رسم بياني لمشهد ثلاثي الأبعاد في TypeScript
مخطط المشهد في Aspose.3D FOSS للـ TypeScript هو شجرة من Node الكائنات الجذرية في scene.rootNode. الاستعراض متكرر: كل عقدة تعرض childNodes قابل للتكرار وخاصية اختيارية entity خاصية. يوضح هذا الدليل كيفية استعراض الشجرة بأكملها، وتحديد أنواع الكيانات، وجمع إحصائيات mesh.
المتطلبات المسبقة
- Node.js 18 أو أحدث
- TypeScript 5.0 أو أحدث
@aspose/3dمثبت
دليل خطوة بخطوة
الخطوة 1: التثبيت والاستيراد
تثبيت الحزمة:
npm install @aspose/3dاستيراد الفئات المستخدمة في هذا الدليل:
import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { Mesh } from '@aspose/3d/entities';Scene و Mesh هي الفئات الأساسية. ObjLoadOptions يُستخدم في مثال التحميل؛ استبدل فئة الخيارات المطابقة للأنساق الأخرى.
الخطوة 2: تحميل مشهد من ملف
إنشاء Scene واستدع scene.open() مع مسار ملف. اكتشاف الصيغة يتم تلقائيًا من أرقام السحر الثنائية، لذا لا تحتاج إلى تحديد الصيغة لملفات GLB أو STL أو 3MF:
import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
const scene = new Scene();
scene.open('model.obj', new ObjLoadOptions());
console.log(`Root node: "${scene.rootNode.name}"`);
console.log(`Top-level children: ${scene.rootNode.childNodes.length}`);يمكنك أيضًا التحميل من Buffer في الذاكرة باستخدام scene.openFromBuffer(buffer, options); مفيد في خطوط الأنابيب بدون خادم حيث لا يتوفر إدخال/إخراج القرص.
الخطوة 3: كتابة دالة استعراض متكررة
التكرار على childNodes هو النمط القياسي. الدالة تزور كل عقدة بعمق أولاً:
function traverse(node: any, depth = 0): void {
const indent = ' '.repeat(depth);
const entityType = node.entity ? node.entity.constructor.name : '-';
console.log(`${indent}[${entityType}] ${node.name}`);
for (const child of node.childNodes) {
traverse(child, depth + 1);
}
}
traverse(scene.rootNode);لمشهد يحتوي على شبكة واحدة باسم Cube,، سيظهر الناتج كالتالي:
[-] RootNode
[Mesh] Cube<button class=“hextra-code-copy-btn hx-group/copybtn hx-transition-all active:hx-opacity-50 hx-bg-primary-700/5 hx-border hx-border-black/5 hx-text-gray-600 hover:hx-text-gray-900 hx-rounded-md hx-p-1.5 dark:hx-bg-primary-300/10 dark:hx-border-white/10 dark:hx-text-gray-400 dark:hover:hx-text-gray-50” title=“Copy code”
<div class="copy-icon group-[.copied]/copybtn:hx-hidden hx-pointer-events-none hx-h-4 hx-w-4"></div>
<div class="success-icon hx-hidden group-[.copied]/copybtn:hx-block hx-pointer-events-none hx-h-4 hx-w-4"></div>
node.entity هو null لعقد المجموعات والعظام ومحددات المواقع. الـ constructor.name التحقق يعمل مع أي نوع من الكيانات: Mesh, Camera, Light, إلخ.
الخطوة 4: الوصول إلى نوع الكيان في كل عقدة
لإجراء فعل بناءً على نوع الكيان، استخدم instanceof تحقق بعد حماية الـ null:
import { Mesh } from '@aspose/3d/entities';
function visitWithTypeCheck(node: any, depth = 0): void {
const indent = ' '.repeat(depth);
if (node.entity instanceof Mesh) {
const mesh = node.entity as Mesh;
console.log(`${indent}MESH "${node.name}": ${mesh.controlPoints.length} vertices`);
} else if (node.entity) {
console.log(`${indent}${node.entity.constructor.name} "${node.name}"`);
} else {
console.log(`${indent}GROUP "${node.name}"`);
}
for (const child of node.childNodes) {
visitWithTypeCheck(child, depth + 1);
}
}
visitWithTypeCheck(scene.rootNode);instanceof Mesh هي الطريقة الأكثر أمانًا لتأكيد أن الكيان هو polygon mesh قبل الوصول إلى controlPoints, polygonCount, أو عناصر vertex.
الخطوة 5: تصفية العقد حسب نوع الكيان
لجمع العقد التي تحمل شبكة فقط دون طباعة الشجرة بالكامل، استخدم مُجمّعًا تكراريًا:
import { Mesh } from '@aspose/3d/entities';
function collectMeshes(
node: any,
results: Array<{ name: string; mesh: Mesh }> = []
): Array<{ name: string; mesh: Mesh }> {
if (node.entity instanceof Mesh) {
results.push({ name: node.name, mesh: node.entity as Mesh });
}
for (const child of node.childNodes) {
collectMeshes(child, results);
}
return results;
}
const meshNodes = collectMeshes(scene.rootNode);
console.log(`Found ${meshNodes.length} mesh node(s)`);تقبل الدالة معاملًا اختياريًا results مصفوفة حتى يتمكن المستدعون من تعبئتها مسبقًا لدمج النتائج عبر عدة subtrees.
الخطوة 6: جمع جميع الشبكات وطباعة عدد الرؤوس
وسّع المجمع لطباعة إحصائيات لكل شبكة:
import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { Mesh } from '@aspose/3d/entities';
function collectMeshes(node: any, results: Array<{name: string, mesh: Mesh}> = []) {
if (node.entity instanceof Mesh) {
results.push({ name: node.name, mesh: node.entity as Mesh });
}
for (const child of node.childNodes) {
collectMeshes(child, results);
}
return results;
}
const scene = new Scene();
scene.open('model.obj', new ObjLoadOptions());
const meshes = collectMeshes(scene.rootNode);
for (const { name, mesh } of meshes) {
console.log(`${name}: ${mesh.controlPoints.length} vertices, ${mesh.polygonCount} polygons`);
}مثال على الإخراج لمشهد يحتوي على شبكتين:
Cube: 8 vertices, 6 polygons
Sphere: 482 vertices, 480 polygons<button class=“hextra-code-copy-btn hx-group/copybtn hx-transition-all active:hx-opacity-50 hx-bg-primary-700/5 hx-border hx-border-black/5 hx-text-gray-600 hover:hx-text-gray-900 hx-rounded-md hx-p-1.5 dark:hx-bg-primary-300/10 dark:hx-border-white/10 dark:hx-text-gray-400 dark:hover:hx-text-gray-50” title=“Copy code”
<div class="copy-icon group-[.copied]/copybtn:hx-hidden hx-pointer-events-none hx-h-4 hx-w-4"></div>
<div class="success-icon hx-hidden group-[.copied]/copybtn:hx-block hx-pointer-events-none hx-h-4 hx-w-4"></div>
نصائح وأفضل الممارسات
- دائمًا افحص null
node.entityقبل الوصول إلى الخصائص الخاصة بالكيان. العديد من العقد هي عقد مجموعة صافية لا تحمل أي كيان. - استخدم
instanceofعبرconstructor.nameلإجراء فحوصات النوع في مسارات المنطق.instanceofآمن لإعادة الهيكلة؛ مقارنة السلاسل علىconstructor.nameيتعطل مع التصغير. - تجول عبر
for...ofعبرchildNodes: المتكرر يتعامل بأمان مع جميع أحجام المصفوفات. تجنب الفهرسة الرقمية لضمان التوافق المستقبلي. - تجنب تعديل الشجرة أثناء التجول:لا تقم بإضافة أو إزالة العقد داخل الاستدعاء المتكرر. اجمع النتائج أولاً، ثم عدل.
- مرّر مصفوفة النتائج كمعامل:هذا يتجنب تخصيص مصفوفة جديدة في كل استدعاء متكرر ويسهل دمج نتائج الفروع.
المشكلات الشائعة
| العَرَض | السبب | الإصلاح |
|---|---|---|
childNodes طوله صفر على rootNode | النموذج غير محمَّل | تأكد scene.open() اكتمل دون خطأ قبل التجوال |
node.entity instanceof Mesh غير صحيح أبداً | خطأ Mesh مسار الاستيراد | استيراد Mesh من @aspose/3d/entities, ليس من @aspose/3d جذر |
| Traversal يتخطى الشبكات المتداخلة | عدم التكرار في جميع الأطفال | تأكد من أن الاستدعاء المتكرر يغطي كل عنصر في node.childNodes |
mesh.controlPoints.length هي 0 | Mesh تم تحميله لكنه لا يحتوي على أي هندسة | تحقق من مصدر OBJ للمجموعات الفارغة؛ استخدم mesh.polygonCount كفحص ثانوي |
| تجاوز سعة المكدس في الهياكل العميقة | شجرة مشهد عميقة جدًا (مئات المستويات) | استبدل العودية بمكدس صريح باستخدام Array.push / Array.pop |
الأسئلة المتكررة
هل scene.rootNode يحمل نفسه كيانًا؟? لا. الـ root node هو container تم إنشاؤه تلقائيًا بواسطة الـ library. لا يحتوي على أي entity. الـ geometry والكائنات الأخرى في الـ scene objects توجد على الـ child nodes مستوى واحد أو أكثر أدناه. rootNode.
ما الفرق بين node.entity و node.entities? node.entity يحتوي على الكيان الأساسي الوحيد (الحالة الشائعة). قد تنتج بعض ملفات FBX و COLLADA القديمة عقدًا تحتوي على كيانات متعددة مرفقة؛ في هذه الحالة node.entities (جمع) يوفر القائمة الكاملة.
هل يمكنني التجول بترتيب العرض أولاً بدلاً من العمق أولاً؟? نعم. استخدم طابورًا بدلاً من استدعاء تكراري: push scene.rootNode إلى مصفوفة، ثم shift وعالج العقد بينما تدفع كل عقدة childNodes إلى ذيل queue.
هل scene.open() متزامن؟? نعم. scene.open() و scene.openFromBuffer() كلاهما يمنعان الخيط المستدعي حتى يتم تحليل الملف بالكامل. غلفهما في خيط عامل إذا كنت بحاجة إلى إبقاء حلقة الأحداث مستجيبة.
كيف أحصل على إحداثيات الفضاء العالمي من عقدة؟? قراءة node.globalTransform;؛ تُعيد قيمة للقراءة فقط GlobalTransform مع مصفوفة الفضاء العالمي، المكوّنة من جميع التحويلات السلفية. للرياضيات الصريحة للمصفوفات، استدعِ node.evaluateGlobalTransform(false).
ما هي أنواع الكيانات الممكنة بخلاف Mesh? Camera, Light,، وكيانات الهيكل العظمي/العظام المخصصة. تحقق من node.entity.constructor.name أو استخدم instanceof مع الفئة المحددة المستوردة من @aspose/3d.