مخطط المشهد

مخطط المشهد هو نموذج البيانات الأساسي لـ Aspose.3D FOSS للبايثون. كل ملف ثلاثي الأبعاد، سواء تم تحميله من القرص أو تم إنشاؤه في الذاكرة، يُمثَّل كشجرة من كائنات 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
    └── ...
ObjectRole
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 بيانات الرؤوس (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 مباشرةً عند بناء الهندسة برمجيًا. هذه قيود معروفة للمكتبة؛ لا توجد واجهة برمجة تطبيقات تعديل عامة بعد.

المشكلات الشائعة

IssueResolution
AttributeError: 'NoneType' object has no attribute 'polygons'احمِ باستخدام if node.entity is not None قبل الوصول إلى خصائص الكيان. العقدة بدون كيانات لديها entity = None.
Mesh appears at the origin despite setting translationtransform.translation يطبق إزاحة محلية. إذا كان للعقدة الأصلية نفسها تحويل غير هوية، قد يختلف الموقع العالمي. تحقق من global_transform.
Child nodes missing after scene.save() / reloadبعض الصيغ (OBJ) تُسطّح الهرمية. استخدم glTF أو COLLADA للحفاظ على شجرة العقد الكاملة.
polygon_count is 0 after mesh.create_polygon(...)polygon_count يساوي 0 بعد mesh.create_polygon(...). تحقق من أن مؤشرات الرؤوس الممررة إلى create_polygon ضمن النطاق (0 إلى len(control_points) - 1).
Node.get_child(name) returns Noneالاسم حساس لحالة الأحرف. أكد سلسلة الاسم الدقيقة المستخدمة عند الإنشاء.
Traversal visits nodes in unexpected orderchild_nodes يُعيد الأطفال بترتيب الإدراج (الترتيب الذي تم استدعاء add_child_node / create_child_node به).
 العربية