גרף סצנה

א גרף סצנה הוא מודל הנתונים הבסיסי של Aspose.3D FOSS עבור Python. כל קובץ תלת‑ממדי, בין אם נטען מהדיסק או נבנה בזיכרון, מיוצג כעץ של Node אובייקטים שמושרשים ב Scene.root_node. כל צומת יכול להכיל צמתים ילדים ואחד או יותר Entity אובייקטים (רשתות, מצלמות, אורות). הבנת גרף הסצנה נותנת לך גישה ישירה לגאומטריה, חומרים, והטרנספורמציות המרחביות של כל אובייקט בסצנה.

התקנה והגדרה

התקן את הספרייה מ‑PyPI:

pip install aspose-3d-foss

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


סקירה: מושגי גרף סצנה

גרף הסצנה ב‑Aspose.3D FOSS פועל על פי היררכיית הכללה פשוטה:

Scene
└── root_node  (Node)
    ├── child_node_A  (Node)
    │   ├── entity: Mesh
    │   └── transform: translation, rotation, scale
    ├── child_node_B  (Node)
    │   └── child_node_C  (Node)
    │       └── entity: Mesh
    └── ...
אובייקטתפקיד
Sceneמכולה ברמת עליונה. מכילה root_node, asset_info, animation_clips, ו sub_scenes.
Nodeצומת עץ בעל שם. יש לו הורה, אפס או יותר צאצאים, אפס או יותר ישויות, ומקומי Transform.
Entityגאומטריה או אובייקט סצנה שמצורף לצומת. סוגי ישויות נפוצים: Mesh, Camera, Light.
Transformמיקום, סיבוב וקנה מידה במרחב מקומי לצומת. תוצאת המרחב העולמי נקראת מ global_transform.

שלב‑אחר‑שלב: בניית גרף סצנה תכנותית

שלב 1: צור סצנה

חדשה Scene תמיד מתחיל בריק root_node:

from aspose.threed import Scene

scene = Scene()
print(scene.root_node.name)   # "" (empty string — root node has no name by default)
print(len(scene.root_node.child_nodes))  # 0

Scene הוא נקודת הכניסה לכל: טעינת קבצים, שמירת קבצים, יצירת קטעי אנימציה, וגישה לעץ הצמתים.


שלב 2: צור צמתים ילדים

השתמש create_child_node(name) להוסיף צמתים בשם לעץ:

from aspose.threed import Scene

scene = Scene()

parent = scene.root_node.create_child_node("parent")
child  = parent.create_child_node("child")

print(parent.name)                          # "parent"
print(child.parent_node.name)               # "parent"
print(len(scene.root_node.child_nodes))     # 1

לחלופין, צור עצמאית Node ולצרף אותה במפורש:

from aspose.threed import Scene, Node

scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)

שתי הגישות מניבות את אותה תוצאה. create_child_node קצר יותר לבנייה משולבת.


שלב 3: צור ישות Mesh וצרף אותה

א Mesh מאחסן נתוני קודקוד (control_points) וְטוֹפּוֹלוֹגְיָה של פנים (polygons). צור אחד, הוסף גאומטריה, ואז צרף אותו לצומת:

from aspose.threed import Scene, Node
from aspose.threed.entities import Mesh
from aspose.threed.utilities import Vector3, Vector4

scene = Scene()
parent = scene.root_node.create_child_node("parent")
child  = parent.create_child_node("child")

##Create a quad mesh (four vertices, one polygon)
# Note: control_points returns a copy of the internal list; append to
# _control_points directly to actually add vertices. This is a known
# library limitation — a public add_control_point() API is not yet available.
mesh = Mesh("cube")
mesh._control_points.append(Vector4(0, 0, 0, 1))
mesh._control_points.append(Vector4(1, 0, 0, 1))
mesh._control_points.append(Vector4(1, 1, 0, 1))
mesh._control_points.append(Vector4(0, 1, 0, 1))
mesh.create_polygon(0, 1, 2, 3)

child.add_entity(mesh)

print(f"Mesh name:      {mesh.name}")
print(f"Vertex count:   {len(mesh.control_points)}")
print(f"Polygon count:  {mesh.polygon_count}")

Vector4(x, y, z, w) מייצג קואורדינטה הומוגנית. השתמש ב w=1 למיקומי נקודות רגילים.

create_polygon(*indices) מקבל אינדקסי קודקוד ורושם פנים אחת ברשימת הפוליגונים. העבר שלושה אינדקסים למשולש, ארבעה לריבוע.


שלב 4: הגדרת טרנספורמציות הצומת

לכל צומת יש Transform ששולט במיקומו, בכיוונו ובגודלו במרחב המקומי:

from aspose.threed.utilities import Vector3, Quaternion

##Translate the node 2 units along the X axis
child.transform.translation = Vector3(2.0, 0.0, 0.0)

##Scale the node to half its natural size
child.transform.scaling = Vector3(0.5, 0.5, 0.5)

##Rotate 45 degrees around the Y axis using Euler angles
child.transform.euler_angles = Vector3(0.0, 45.0, 0.0)

הטרנספורמציות מצטברות: מיקום המרחב העולמי של צומת בן הוא הרכבה של הטרנספורמציה שלו עם כל הטרנספורמציות של הצמתים הקודמים. קרא את תוצאת המרחב העולמי המחושבת מ node.global_transform (בלתי ניתן לשינוי, קריאה בלבד).


שלב 5: מעבר על גרף הסצנה באופן רקורסיבי

הולך בכל העץ על‑ידי רקורסיה דרך node.child_nodes:

def traverse(node, depth=0):
    indent = "  " * depth
    entity_type = type(node.entity).__name__ if node.entity else "None"
    print(f"{indent}{node.name} [{entity_type}]")
    for child in node.child_nodes:
        traverse(child, depth + 1)

traverse(scene.root_node)

דוגמת פלט עבור הסצנה שנבנתה למעלה (שם צומת השורש הוא מחרוזת ריקה):

 [None]
  parent [None]
    child [Mesh]

לסצנות עם ישויות מרובות לכל צומת, בצע איטרציה node.entities במקום node.entity:

def traverse_full(node, depth=0):
    indent = "  " * depth
    entity_names = [type(e).__name__ for e in node.entities] or ["None"]
    print(f"{indent}{node.name} [{', '.join(entity_names)}]")
    for child in node.child_nodes:
        traverse_full(child, depth + 1)

traverse_full(scene.root_node)

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

העבר נתיב קובץ ל scene.save(). הפורמט נגזר מהרחבת הקובץ:

scene.save("scene.gltf")   # JSON glTF 2.0
scene.save("scene.glb")    # Binary GLB container
scene.save("scene.obj")    # Wavefront OBJ
scene.save("scene.stl")    # STL

לאפשרויות ספציפיות לפורמט, העבר אובייקט save-options כארגומנט השני:

from aspose.threed.formats import GltfSaveOptions

opts = GltfSaveOptions()
scene.save("scene.gltf", opts)

טיפים ושיטות עבודה מומלצות

  • תן שם לכל צומת. מתן שמות משמעותיים לצמתים מקל משמעותית על ניפוי באגים של מסעות ומבטיח שהשמות יישמרו בקובץ המיוצא.
  • רשת אחת לכל צומת. שמירת ישויות ביחס 1:1 עם צמתים מפשטת טרנספורמים ושאילתות התנגשות.
  • השתמש create_child_node במקום חיבור ידני. זה מגדיר את הפנייה להורה אוטומטית ופחות רגיש לטעויות.
  • קרא global_transform לאחר בניית ההיררכיה. תוצאת המרחב העולמי יציבה רק לאחר שכל הטרנספורמים של האבות הוגדרו.
  • אל תשנה את העץ במהלך המסע. הוספה או הסרה של צמתים צאצאים בזמן איטרציה child_nodes תגרום להתנהגות בלתי צפויה. אסוף צמתים תחילה, ואז שנה.
  • נקודות בקרה משתמשות Vector4, לא Vector3. תמיד העבר w=1 עבור מיקומי קודקוד רגילים; w=0 מייצג וקטור כיוון (לא נקודה).
  • mesh.control_points מחזיר עותק. ה control_points המאפיין מחזיר list(self._control_points) — הוספה לרשימה המוחזרת אינה משנה את הרשת. תמיד יש להוסיף ל mesh._control_points בצורה ישירה בעת בניית גאומטריה בתכנות. זו מגבלה ידועה של הספרייה; API ציבורי לשינוי עדיין אינו קיים.

בעיות נפוצות

בעיהפתרון
AttributeError: 'NoneType' object has no attribute 'polygons'הגן עם if node.entity is not None לפני גישה למאפייני ישות. צומת ללא ישויות יש entity = None.
הרשת מופיעה במקור למרות שהוגדר translationtransform.translation מחיל היסט מקומי. אם צומת האב עצמו בעל שינוי שאינו זהות, מיקום העולם עשוי להיות שונה. בדוק global_transform.
צמתים צאצאים חסרים לאחר scene.save() / רענוןחלק מהפורמטים (OBJ) משטחים את ההיררכיה. השתמש ב‑glTF או COLLADA כדי לשמר את עץ הצמתים המלא.
polygon_count הוא 0 לאחר mesh.create_polygon(...)ודא שמספרי הקודקודים שהועברו ל create_polygon בין הטווח (0 עד len(control_points) - 1).
Node.get_child(name) מחזיר Noneהשם רגיש לאותיות גדולות/קטנות. יש לאשר את מחרוזת השם המדויקת שהשתמשו בה בזמן היצירה.
המעבר מבקר צמתים בסדר בלתי צפויchild_nodes מחזיר ילדים בסדר ההוספה (הסדר add_child_node / create_child_node נקרא).
 עברית