1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
import bpy
import struct
import os
# Output format:
# Header:
# char[4] magic = "SCN1"
# uint32_t num_objects
# uint32_t num_cameras (reserved)
# uint32_t num_lights (reserved)
#
# Object Block:
# char[64] name
# uint32_t type (0=CUBE, 1=SPHERE, 2=PLANE, 3=TORUS, 4=BOX, 5=SKYBOX, 6=MESH)
# vec3 position
# quat rotation (x, y, z, w)
# vec3 scale
# vec4 color
# float plane_distance (if type == PLANE)
# uint32_t mesh_name_len
# char[] mesh_name (if type == MESH)
# float mass
# float restitution
# uint32_t is_static (bool)
def export_scene(filepath):
print(f"Exporting scene to {filepath}...")
objects = []
for obj in bpy.context.scene.objects:
if obj.visible_get():
if obj.type == 'MESH':
objects.append(obj)
elif obj.type == 'EMPTY' and obj.name.lower().startswith('plane_'):
objects.append(obj)
with open(filepath, 'wb') as f:
# Header
f.write(b'SCN1')
f.write(struct.pack('<III', len(objects), 0, 0))
for obj in objects:
print(f" Exporting {obj.name}...")
# Name (64 bytes, null-padded)
name_bytes = obj.name.encode('utf-8')[:63]
f.write(struct.pack('<64s', name_bytes))
# Type detection
# Default to MESH (6)
obj_type = 6
# Simple heuristic for primitives based on name
# In a real pipeline, we might use custom properties
name_lower = obj.name.lower()
if 'cube' in name_lower and 'mesh' not in name_lower: obj_type = 0
elif 'sphere' in name_lower: obj_type = 1
elif 'plane' in name_lower: obj_type = 2
elif 'torus' in name_lower: obj_type = 3
elif 'box' in name_lower: obj_type = 4
f.write(struct.pack('<I', obj_type))
# Transform
# Blender uses Z-up. We typically use Y-up.
# Conversion:
# Pos: (x, z, -y)
# Rot: Convert quaternion
pos = obj.location
rot = obj.rotation_quaternion
scale = obj.scale
# Position: Convert Blender Z-up (x,y,z) to engine Y-up (x,z,-y)
f.write(struct.pack('<3f', pos.x, pos.z, -pos.y))
# Rotation (x, y, z, w)
# Blender provides (w, x, y, z), so reorder to (x, y, z, w)
f.write(struct.pack('<4f', rot.x, rot.y, rot.z, rot.w))
# Scale
f.write(struct.pack('<3f', scale.x, scale.y, scale.z))
# Color (RGBA)
# Try to get from material
color = (1.0, 1.0, 1.0, 1.0)
if obj.active_material:
c = obj.active_material.diffuse_color
color = (c[0], c[1], c[2], c[3])
f.write(struct.pack('<4f', *color))
# Plane Distance (if type == PLANE)
plane_distance = 0.0
if obj_type == 2: # PLANE
plane_distance = obj.get('plane_distance', 0.0)
f.write(struct.pack('<f', plane_distance))
# Mesh Asset Name
mesh_asset_name = ""
if obj_type == 6: # MESH
# Ensure the mesh name is sanitized for AssetId (e.g. MESH_CUBE)
# Convention: MESH_<ObjectName_Upper>
mesh_asset_name = "MESH_" + obj.name.upper().replace('.', '_')
name_len = len(mesh_asset_name)
f.write(struct.pack('<I', name_len))
if name_len > 0:
f.write(mesh_asset_name.encode('utf-8'))
# Physics properties (from custom properties or defaults)
mass = obj.get('mass', 1.0)
restitution = obj.get('restitution', 0.5)
is_static = obj.get('is_static', 0)
f.write(struct.pack('<ffI', float(mass), float(restitution), int(is_static)))
print("Export complete.")
# To run in Blender:
# import sys
# sys.path.append('/path/to/demo/tools')
# import blender_export
# blender_export.export_scene('/path/to/demo/assets/final/scene.bin')
if __name__ == "__main__":
# Standalone test (won't work outside Blender environment usually)
pass
|