כיצד לעבור על גרף סצנה תלת‑ממדי ב‑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=“העתק קוד”
<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=“העתק קוד”
<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 |
| הטרוורס מפספס רשתות משולבות | לא מתבצע רקורסיה לכל הילדים | ודא שהקריאה הרקורסיבית מכסה כל אלמנט ב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.