رسم بياني للمشهد
أ مخطط المشهد هو نموذج البيانات الأساسي لـ Aspose.3D FOSS لـ Python. كل ملف ثلاثي الأبعاد، سواء تم تحميله من القرص أو تم إنشاؤه في الذاكرة، يُمثَّل كشجرة من Node الكائنات المتجذرة في Scene.root_node. يمكن لكل عقدة أن تحتفظ بعقد فرعية وواحد أو أكثر من Entity الكائنات (meshes, cameras, lights). فهم مخطط المشهد يمنحك وصولًا مباشرًا إلى الهندسة والمواد والتحولات المكانية لكل كائن في المشهد.
التثبيت والإعداد
قم بتثبيت المكتبة من 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)) # 0Scene هو نقطة الدخول لكل شيء: تحميل الملفات، حفظ الملفات، إنشاء مقاطع الرسوم المتحركة، والوصول إلى شجرة العقد.
الخطوة 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لخيارات خاصة بالصيغ، مرّر كائن خيارات الحفظ كالمعامل الثاني:
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مباشرةً عند بناء الهندسة برمجياً. هذه قيود معروفة في المكتبة؛ لا توجد بعد واجهة برمجة تطبيقات عامة للتعديل.
المشكلات الشائعة
| المشكلة | الحل |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | احمِ بـ if node.entity is not None قبل الوصول إلى خصائص الكيان. العقدة التي لا تحتوي على كيانات لديها entity = None. |
تظهر الشبكة في الأصل بالرغم من ضبط translation | transform.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 تم استدعاؤه). |