كيفية استعراض رسم بياني ثلاثي الأبعاد للمشهد باستخدام TypeScript
مخطط المشهد في Aspose.3D FOSS لـ TypeScript هو شجرة من كائنات Node جذورها scene.rootNode. التجوال متكرر: كل عقدة تكشف عن قابل للتكرار childNodes وخصائص اختيارية entity. يوضح هذا الدليل كيفية استعراض الشجرة بالكامل، وتحديد أنواع الكيانات، وجمع إحصائيات الشبكة.
المتطلبات المسبقة
- 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 هو الطريقة الأكثر أمانًا لتأكيد أن الكيان هو شبكة مضلعات قبل الوصول إلى controlPoints، polygonCount، أو عناصر القمم.
الخطوة 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 اختيارية بحيث يمكن للمتصلين تعبئتها مسبقًا لدمج النتائج عبر عدة شجرات فرعية.
الخطوة 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>
نصائح وأفضل الممارسات
- تحقق دائمًا من عدم وجود قيمة فارغة
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 |
| التجوال يتخطى الشبكات المتداخلة | عدم الاستدعاء المتكرر لجميع الأطفال | تأكد من أن الاستدعاء المتكرر يغطي كل عنصر في node.childNodes |
mesh.controlPoints.length يساوي 0 | تم تحميل الشبكة ولكنها لا تحتوي على هندسة | تحقق من مصدر OBJ للمجموعات الفارغة؛ استخدم mesh.polygonCount كفحص ثانوي |
| تجاوز سعة المكدس في الهياكل العميقة | شجرة مشهد عميقة جدًا (مئات المستويات) | استبدل الاستدعاء المتكرر بمكدس صريح باستخدام Array.push / Array.pop |
الأسئلة المتكررة
هل يحمل scene.rootNode نفسه كيانًا؟
لا. العقدة الجذرية هي حاوية تم إنشاؤها تلقائيًا بواسطة المكتبة. لا تحتوي على كيان. الهندسة الخاصة بك وغيرها من كائنات المشهد تعيش على عقد فرعية بمستوى أو أكثر أسفل rootNode.
ما هو الفرق بين node.entity و node.entities؟node.entity يحتوي على الكيان الأساسي الوحيد (الحالة الشائعة). قد تُنتج بعض ملفات FBX و COLLADA القديمة عقدًا بها كيانات مرفقة متعددة؛ في هذه الحالة node.entities (جمع) يوفر القائمة الكاملة.
هل يمكنني التجوال بترتيب عرض أولاً بدلاً من عمق أولاً؟
نعم. استخدم طابورًا بدلاً من الاستدعاء المتكرر: ادفع scene.rootNode إلى مصفوفة، ثم قم بالإزاحة ومعالجة العقد بينما تدفع childNodes لكل عقدة إلى ذيل الطابور.
هل scene.open() متزامن؟
نعم. scene.open() وscene.openFromBuffer() كلاهما يحجزان الخيط المستدعي حتى يتم تحليل الملف بالكامل. قم بلفهما في خيط عامل إذا كنت بحاجة إلى إبقاء حلقة الأحداث مستجيبة.
كيف أحصل على إحداثيات الفضاء العالمي من عقدة؟
اقرأ node.globalTransform؛ فإنه يُعيد GlobalTransform للقراءة فقط مع مصفوفة الفضاء العالمي، المكوّنة من جميع التحويلات السلفية. للرياضيات الصريحة للمصفوفات، استدعِ node.evaluateGlobalTransform(false).
ما هي أنواع الكيانات الممكنة بجانب Mesh؟Camera، Light، وكيانات الهيكل العظمي/العظام المخصصة. تحقق من node.entity.constructor.name أو استخدم instanceof مع الفئة المحددة المستوردة من @aspose/3d.