כיצד לטעון מודלים תלת‑ממדיים בפייתון

כיצד לטעון מודלים תלת‑ממדיים בפייתון

Aspose.3D FOSS for Python מספק API פשוט לפתיחת קבצי 3D ללא תלותיות מקומיות. לאחר טעינת קובץ לאובייקט Scene, ניתן לעבור על היררכיית הצמתים ולקרוא נתוני גאומטריה גולמיים עבור כל רשת בסצנה.

מדריך שלב אחר שלב

שלב 1: התקנת החבילה

התקן את Aspose.3D FOSS מ‑PyPI. אין צורך בספריות מערכת נוספות.

pip install aspose-3d-foss

גרסאות Python נתמכות: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12.


שלב 2: ייבא את Scene Class

המחלקה Scene היא המכולה ברמת העל עבור כל נתוני ה‑3D. ייבא אותה יחד עם כל מחלקות אפשרויות הטעינה שאתה צריך.

from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions

כל המחלקות הציבוריות ממוקמות תחת aspose.threed או תחת חבילות המשנה שלה (aspose.threed.entities, aspose.threed.formats, aspose.threed.utilities).


שלב 3: טען קובץ

השתמש במתודה הסטטית Scene.from_file() כדי לפתוח כל פורמט נתמך. הספרייה מזהה את הפורמט אוטומטית מהרחבת הקובץ.

##Automatic format detection
scene = Scene.from_file("model.obj")

לחלופין, צור מופע של Scene וקרא לopen(); שימושי כאשר אתה רוצה להעביר אפשרויות טעינה או לטפל בשגיאות במפורש:

scene = Scene()
scene.open("model.obj")

שתי השיטות תומכות בקבצי OBJ, STL (בינארי ו‑ASCII), glTF 2.0 / GLB, COLLADA (DAE) ו‑3MF.


שלב 4: מעבר על צמתי הסצנה

סצנה טעונה היא עץ של אובייקטים Node שמקורה ב-scene.root_node. חזור רקורסיבית כדי למצוא את כל הצמתים:

from aspose.threed import Scene, Node

scene = Scene.from_file("model.obj")

def walk(node: Node, depth: int = 0) -> None:
    indent = "  " * depth
    print(f"{indent}Node: {node.name!r}")
    for child in node.child_nodes:
        walk(child, depth + 1)

walk(scene.root_node)

כל Node יכול לשאת אפס או יותר של עצמים Entity (רשתות, מצלמות, אורות). בדוק את node.entities כדי לראות מה מחובר.


שלב 5: גישה לנתוני קודקוד ופוליגון

המר את ישות הצומת ל‑Mesh וקרא את נקודות הבקרה (מיקומי הקודקודים) והפוליגונים (רשימות אינדקסי הפנים):

from aspose.threed import Scene
from aspose.threed.entities import Mesh

scene = Scene.from_file("model.obj")

for node in scene.root_node.child_nodes:
    for entity in node.entities:
        if isinstance(entity, Mesh):
            mesh: Mesh = entity
            print(f"Mesh '{node.name}': "
                  f"{len(mesh.control_points)} vertices, "
                  f"{len(mesh.polygons)} polygons")

            # First vertex position
            if mesh.control_points:
                v = mesh.control_points[0]
                print(f"  First vertex: ({v.x:.4f}, {v.y:.4f}, {v.z:.4f})")

            # First polygon face (list of control-point indices)
            if mesh.polygons:
                print(f"  First polygon: {mesh.polygons[0]}")

mesh.control_points היא רשימה של Vector4; x, y, z נושאים את המיקום ו-w הוא הקואורדינטה ההומוגנית (בדרך כלל 1.0).

mesh.polygons הוא רשימה של רשימות של מספרים שלמים, כאשר כל רשימה פנימית היא הקבוצה הממוינת של אינדקסי נקודות הבקרה עבור פנים אחת.


שלב 6: החל אפשרויות טעינה ספציפיות לפורמט

לשליטה מדויקת יותר על האופן שבו קובץ OBJ מתפרש, העבירו מופע של ObjLoadOptions ל-scene.open():

from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions

options = ObjLoadOptions()
options.flip_coordinate_system = True   # Convert right-hand Y-up to Z-up
options.scale = 0.01                    # Convert centimetres to metres
options.enable_materials = True         # Load .mtl material file
options.normalize_normal = True         # Normalize all normals to unit length

scene = Scene()
scene.open("model.obj", options)

עבור קבצי STL, המחלקה המקבילה היא StlLoadOptions. עבור glTF, השתמשו בGltfLoadOptions. ראו את API reference לקבלת רשימה מלאה.


בעיות נפוצות ותיקונים

FileNotFoundError בעת קריאה לScene.from_file()

הנתיב חייב להיות מוחלט או יחסי נכון לתיקיית העבודה בזמן הריצה. השתמש בpathlib.Path כדי לבנות נתיבים אמינים:

from pathlib import Path
from aspose.threed import Scene

path = Path(__file__).parent / "assets" / "model.obj"
scene = Scene.from_file(str(path))

mesh.polygons ריק לאחר טעינת קובץ STL

קבצי STL שומרים משולשים כפרצופים גולמיים, ולא כרשת ממוספרת. לאחר הטעינה, פוליגונים מסונתזים מהפרצופים האלה. אם polygons מופיע ריק, בדוק את len(mesh.control_points); אם הספירה היא כפולה של 3, הגיאומטריה נשמרת בצורה בלתי ממוספרת וכל שלושה קודקודים רצופים יוצרים משולש אחד.

אי התאמת מערכת הקואורדינטות (המודל נראה מסובב או משוקף)

כלים שונים משתמשים בקונבנציות שונות (Y‑up לעומת Z‑up, יד שמאלית לעומת יד ימנית). הגדר ObjLoadOptions.flip_coordinate_system = True או החל סיבוב על Transform של צומת השורש לאחר הטעינה.

AttributeError: 'NoneType' object has no attribute 'polygons'

רשימת הישויות של צומת עשויה להכיל ישויות שאינן רשת (מצלמות, אורות). תמיד יש להגן עם isinstance(entity, Mesh) לפני ההמרה.


שאלות נפוצות (FAQ)

אילו פורמטים תלת‑ממדיים אני יכול לטעון?

OBJ (Wavefront), STL (בינארי ו‑ASCII), glTF 2.0 / GLB, COLLADA (DAE), ו‑3MF. טוקניזציית קבצי FBX נתמכת חלקית אך הפענוח המלא עדיין אינו מושלם.

האם טעינת קובץ OBJ טוענת גם את החומר .mtl?

כן, כאשר ObjLoadOptions.enable_materials = True (ברירת המחדל). הספרייה מחפשת את קובץ .mtl באותה תיקייה כמו קובץ .obj. אם .mtl חסר, הגאומטריה עדיין נטענת והזהרה נשלחת.

האם ניתן לטעון קובץ מזרם בתים במקום נתיב?

כן. scene.open() מקבל כל אובייקט דמוי קובץ עם שיטת .read() בנוסף למחרוזת נתיב קובץ. העבר זרם בינארי פתוח (למשל, io.BytesIO) ישירות. Scene.from_file() מקבל רק מחרוזת נתיב קובץ.

איך אני מקבל נורמליות של משטח?

לאחר הטעינה, בדוק mesh.get_element(VertexElementType.NORMAL). פעולה זו מחזירה VertexElementNormal שהרשימה data שלו מכילה וקטור נורמלי אחד לכל הפנייה, ממופה לפי mapping_mode וreference_mode.

from aspose.threed.entities import Mesh, VertexElementType

normals = mesh.get_element(VertexElementType.NORMAL)
if normals:
    print(normals.data[0])  # First normal vector

האם הספרייה בטוחה לשימוש מרובה‑תהליכים לטעינת קבצים מרובים במקביל?

כל אובייקט Scene הוא עצמאי. טעינת קבצים נפרדים לתוך מופעים נפרדים של Scene מתוך חוטים נפרדים היא בטוחה כל עוד אינך חולק Scene יחיד בין חוטים ללא נעילה חיצונית.

 עברית