איך לעבור על גרף סצנה תלת‑ממד ב‑Python
גרף הסצנה ב-Aspose.3D FOSS הוא עץ של Node אובייקטים שמקורים ב scene.root_node. כל קובץ 3D, בין אם נטען מ-OBJ, glTF, STL, COLLADA, או 3MF, מייצר את אותה מבנה עץ. ידיעת הדרך לעבור בו מאפשרת לך לבדוק גאומטריה, לספור פוליגונים, לסנן אובייקטים לפי סוג, ולעבד חלקים ספציפיים של סצנה מורכבת.
מדריך שלב‑אחר‑שלב
שלב 1: התקנה וייבוא
התקן את Aspose.3D FOSS מ‑PyPI:
pip install aspose-3d-fossייבא את המחלקות שאתה צריך:
from aspose.threed import Scene
from aspose.threed.entities import Meshכל המחלקות הציבוריות ממוקמות תחת aspose.threed או תתי-חבילות שלה (aspose.threed.entities, aspose.threed.utilities).
שלב 2: טען סצנה מקובץ
השתמש ב‑static Scene.from_file() מתודה כדי לפתוח כל פורמט נתמך. הפורמט מזוהה אוטומטית מהרחבת הקובץ:
scene = Scene.from_file("model.gltf")ניתן גם לפתוח עם אפשרויות טעינה מפורשות:
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.enable_materials = True
scene = Scene()
scene.open("model.obj", options)לאחר הטעינה, scene.root_node הוא שורש העץ. כל הצמתים המיובאים הם ילדים או צאצאים של צומת זה.
שלב 3: כתוב פונקציית מעבר רקורסיבית
המעבר הפשוט ביותר מבקר בכל צומת בסדר עומק ראשון (depth‑first):
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def traverse(node, depth=0):
prefix = " " * depth
entity_name = type(node.entity).__name__ if node.entity else "-"
print(f"{prefix}[{entity_name}] {node.name}")
for child in node.child_nodes:
traverse(child, depth + 1)
scene = Scene.from_file("model.gltf")
traverse(scene.root_node)פלט לדוגמה:
[-] RootNode
[-] Armature
[Mesh] Body
[Mesh] Eyes
[-] Ground
[Mesh] Plane<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.child_nodes מחזיר ילדים לפי סדר ההוספה (הסדר שבו המייבא או המשתמש הוסיף אותם).
שלב 4: גישה למאפייני ישות בכל צומת
השתמש node.entity כדי לקבל את הישות הראשונה המחוברת לצומת, או node.entities כדי לעבור על כולן. בדוק את הסוג לפני גישה למאפיינים ספציפיים לפורמט:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def print_entity_details(node, depth=0):
indent = " " * depth
for entity in node.entities:
if isinstance(entity, Mesh):
mesh = entity
print(f"{indent}Mesh '{node.name}':")
print(f"{indent} vertices : {len(mesh.control_points)}")
print(f"{indent} polygons : {mesh.polygon_count}")
print(f"{indent} cast_shadows : {mesh.cast_shadows}")
print(f"{indent} receive_shadows: {mesh.receive_shadows}")
for child in node.child_nodes:
print_entity_details(child, depth + 1)
scene = Scene.from_file("model.gltf")
print_entity_details(scene.root_node)node.transform.translation, node.transform.rotation, ו node.transform.scaling נותנים את הטרנספורם במרחב המקומי. node.global_transform נותן את תוצאת המרחב העולמי המחושבת (עם global_transform.scale לסקלה במרחב-עולם).
שלב 5: סינון צמתים לפי סוג ישות
כדי לפעול רק על סוגי ישויות ספציפיים, הוסף isinstance בדיקה בתוך הטרוורסל:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def find_mesh_nodes(node, results=None):
if results is None:
results = []
for entity in node.entities:
if isinstance(entity, Mesh):
results.append(node)
break # One match per node is enough
for child in node.child_nodes:
find_mesh_nodes(child, results)
return results
scene = Scene.from_file("model.gltf")
mesh_nodes = find_mesh_nodes(scene.root_node)
print(f"Found {len(mesh_nodes)} mesh node(s)")
for n in mesh_nodes:
print(f" {n.name}")תבנית זו שימושית לפעולות בכמות גדולה: החלת טרנספורם על כל צומת רשת, החלפת חומרים, או ייצוא תתי-עצים.
שלב 6: איסוף כל הרשתות והדפסת ספירות הקודקודים
אגרגציה של סטטיסטיקות רשת במעבר יחיד:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def collect_meshes(node, results=None):
if results is None:
results = []
if isinstance(node.entity, Mesh):
results.append((node.name, node.entity))
for child in node.child_nodes:
collect_meshes(child, results)
return results
scene = Scene.from_file("model.gltf")
meshes = collect_meshes(scene.root_node)
print(f"Total meshes: {len(meshes)}")
total_verts = 0
total_polys = 0
for name, mesh in meshes:
verts = len(mesh.control_points)
polys = mesh.polygon_count
total_verts += verts
total_polys += polys
print(f" {name}: {verts} vertices, {polys} polygons")
print(f"Scene totals: {total_verts} vertices, {total_polys} polygons")בעיות נפוצות
| בעיה | רזולוציה |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | הגן עם if node.entity is not None או isinstance(node.entity, Mesh) לפני גישה למאפייני הרשת. צמתים ללא ישויות מחזירים None. |
| המעבר נעצר מוקדם | ודא שהרקורסיה מגיעה אל node.child_nodes. אם אתה מבצע איטרציה רק scene.root_node.child_nodes (לא רקורסיבית), אתה מפסיד את כל הצאצאים. |
| Mesh חסר בתוצאות שנאספו | בדוק שהפורמט של הקובץ שומר על ההיררכיה. OBJ משטח את כל הגאומטריה לרמת צומת יחידה. השתמש ב‑glTF או ב‑COLLADA עבור היררכיות עמוקות. |
node.entity מחזיר רק את הישות הראשונה | השתמש node.entities (הרשימה המלאה) כאשר צומת נושא ישויות מרובות. node.entity קיצור ל node.entities[0] כאשר entities אינו ריק. |
collect_meshes מחזיר 0 תוצאות עבור STL טעון | קבצי STL בדרך כלל מייצרים node שטוח יחיד עם mesh entity אחת ישירות מתחת root_node. בדוק root_node.child_nodes[0].entity ישירות. |
| שמות הצמתים הם מחרוזות ריקות | חלק מהפורמטים (binary STL, קבצי OBJ מסוימים) אינם שומרים שמות אובייקטים. הצמתים יהיו ריקים name מחרוזות; השתמש באינדקס לזיהוי במקום זאת. |
שאלות נפוצות
עד כמה עמוק יכול להיות גרף סצנה?
אין גבול קשיח. הגבול ברירת המחדל של הרקורסיה של Python (1000 מסגרות) חל על פונקציות מעבר רקורסיביות. עבור היררכיות עמוקות מאוד, המר את הרקורסיה למחסנית מפורשת:
from collections import deque
from aspose.threed import Scene
scene = Scene.from_file("deep.gltf")
queue = deque([(scene.root_node, 0)])
while queue:
node, depth = queue.popleft()
print(" " * depth + node.name)
for child in node.child_nodes:
queue.append((child, depth + 1))האם ניתן לשנות את העץ בזמן המעבר עליו?
אל תוסיף או תסיר צמתים מ- child_nodes בזמן האיטרציה עליו. אסוף את הצמתים לשינוי במעבר הראשון, ואז יישם את השינויים במעבר השני.
איך אני מוצא צומת ספציפי לפי שם?
השתמש node.get_child(name) כדי למצוא צאצא ישיר לפי שם. לחיפוש עמוק, עבור על העץ עם מסנן שם:
def find_by_name(root, name):
if root.name == name:
return root
for child in root.child_nodes:
result = find_by_name(child, name)
if result:
return result
return None
target = find_by_name(scene.root_node, "Wheel_FL")האם node.entity תמיד מחזיר Mesh?
לא. צומת יכול להכיל כל סוג ישות: Mesh, Camera, Light, או ישויות מותאמות. תמיד לבדוק עם isinstance(node.entity, Mesh) לפני שימוש בתכונות ספציפיות ל‑Mesh.
איך אני מקבל את מיקום הצומת במרחב העולמי?
קרא node.global_transform.translation. זהו המיקום המחושב במרחב העולם, בהתחשב בכל ההמרות של האבות. הוא קריאה בלבד; שנה node.transform.translation כדי למקם מחדש את הצומת.
האם אני יכול לספור את סך הפוליגונים בסצנה ללא כתיבת מעבר?
לא ישירות דרך ה‑API; אין scene.total_polygon_count נכס. השתמש collect_meshes ולסכם mesh.polygon_count בכל התוצאות, כפי שמוצג בצעד 6.