איך לעבור על גרף סצנה תלת‑ממד ב‑Python

איך לעבור על גרף סצנה תלת‑ממד ב‑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.

 עברית