كيفية التنقل في مخطط المشهد ثلاثي الأبعاد في Python
مخطط المشهد في Aspose.3D FOSS هو شجرة من Node كائنات جذورها في scene.root_node. كل ملف ثلاثي الأبعاد، سواء تم تحميله من 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: كتابة دالة استعراض تكرارية
أبسط استعراض يزور كل عقدة بترتيب العمق أولاً:
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)مثال على الإخراج:
[-]
[-] 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=“Copy code”
<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 عقدة مسطحة واحدة مع كيان 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.