گراف صحنه

یک گراف صحنه مدل دادهٔ بنیادی Aspose.3D FOSS برای Python است. هر فایل 3D، چه از دیسک بارگذاری شود و چه در حافظه ساخته شود، به‌صورت درختی از Node اشیائی که ریشه در Scene.root_node. هر گره می‌تواند گره‌های فرزند و یک یا چند Entity اشیاء (مش‌ها، دوربین‌ها، نورها). درک گراف صحنه به شما دسترسی مستقیم به هندسه، مواد و تبدیلات فضایی هر شیء در یک صحنه می‌دهد.

نصب و راه‌اندازی

کتابخانه را از PyPI نصب کنید:

asposefoss/3d is not yet published — build from source until it ships. See the project README for build instructions.

هیچ افزونهٔ بومی، کامپایلر یا بستهٔ سیستمی اضافی لازم نیست. برای دستورالعمل‌های کامل نصب، به راهنمای نصب.


بررسی کلی: مفاهیم گراف صحنه

گراف صحنه در 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.

گام به گام: ساخت گراف صحنه به‌صورت برنامه‌نویسی

گام ۱: ایجاد یک صحنه

یک جدید 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 نقطه ورودی برای همه چیز است: بارگذاری فایل‌ها، ذخیره‌سازی فایل‌ها، ایجاد کلیپ‌های انیمیشن، و دسترسی به درخت گره‌ها.


گام ۲: ایجاد گره‌های فرزند

از 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 برای ساخت درون‌خطی، مختصرتر است.


گام ۳: ایجاد یک موجودیت مش و اتصال آن

یک 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) شاخص‌های راس را می‌پذیرد و یک وجه را در فهرست چندضلعی‌ها ثبت می‌کند. برای یک مثلث سه شاخص، برای یک چهارضلعی چهار شاخص بدهید.


مرحله ۴: تنظیم تبدیلات گره

هر گره دارای یک 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 (غیرقابل تغییر، فقط‑خواندنی).


مرحله ۵: پیمایش بازگشتی گراف صحنه

کل درخت را با بازگشت از طریق 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)

مرحله ۶: ذخیره صحنه

یک مسیر فایل به 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 مستقیم هنگام ساخت هندسه به‌صورت برنامه‌نویسی. این یک محدودیت شناخته‌شده کتابخانه است؛ هنوز API عمومی برای تغییر وجود ندارد.

مشکلات رایج

مسئلهراه‌حل
AttributeError: 'NoneType' object has no attribute 'polygons'با Guard 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 فرزندان را به ترتیب درج (the order add_child_node / create_child_node نامیده شد).
 فارسی