audio&island
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
1 import bpy, math, gpu
2 from ctypes import *
3 from gpu_extras.batch import batch_for_shader
4
5 bl_info = {
6 "name":"Carve exporter",
7 "author": "Harry Godden (hgn)",
8 "version": (0,1),
9 "blender":(3,1,0),
10 "location":"Export",
11 "descriptin":"",
12 "warning":"",
13 "wiki_url":"",
14 "category":"Import/Export",
15 }
16
17 class model(Structure):
18 _pack_ = 1
19 _fields_ = [("identifier",c_uint32),
20 ("vertex_count",c_uint32),
21 ("indice_count",c_uint32),
22 ("layer_count",c_uint32),
23 ("marker_count",c_uint32)]
24
25 class submodel(Structure):
26 _pack_ = 1
27 _fields_ = [("indice_start",c_uint32),
28 ("indice_count",c_uint32),
29 ("vertex_start",c_uint32),
30 ("vertex_count",c_uint32),
31 ("bbx",(c_float*3)*2),
32 ("pivot",c_float*3),
33 ("q",c_float*4),
34 ("name",c_char*32),
35 ("material",c_char*32)]
36
37 class classtype_gate(Structure):
38 _pack_ = 1
39 _fields_ = [("target",c_uint32)]
40
41 class marker(Structure):
42 _pack_ = 1
43 _fields_ = [("co",c_float*3),
44 ( "q",c_float*4),
45 ( "s",c_float*3),
46 ("classtype",c_uint32),
47 ("offset",c_uint32),
48 ("name",c_char*32)]
49
50 class model_vert(Structure):
51 _pack_ = 1
52 _fields_ = [("co",c_float*3),
53 ("norm",c_float*3),
54 ("colour",c_float*4),
55 ("uv",c_float*2)]
56
57 def submesh_set_transform( sm, obj ):
58 sm.pivot[0] = obj.matrix_world.translation[0]
59 sm.pivot[1] = obj.matrix_world.translation[2]
60 sm.pivot[2] = -obj.matrix_world.translation[1]
61
62 quat = obj.matrix_world.to_quaternion()
63 sm.q[0] = quat[1]
64 sm.q[1] = quat[3]
65 sm.q[2] = -quat[2]
66 sm.q[3] = quat[0]
67
68 def write_model(name):
69 fp = open(F"/home/harry/Documents/carve/models/{name}.mdl", "wb")
70 collection = bpy.data.collections[name]
71
72 header = model()
73 header.identifier = 0xABCD0000
74 header.vertex_count = 0
75 header.indice_count = 0
76 header.layer_count = 0
77 header.marker_count = 1
78
79 mesh_cache = {}
80 layers = []
81 vertex_buffer = []
82 indice_buffer = []
83
84 print( F"Create mode {name}" )
85
86 rootmarker = marker()
87 rootmarker.co[0] = 0
88 rootmarker.co[1] = 0
89 rootmarker.co[2] = 0
90 rootmarker.q[0] = 0
91 rootmarker.q[1] = 0
92 rootmarker.q[2] = 0
93 rootmarker.q[3] = 1
94 rootmarker.s[0] = 1
95 rootmarker.s[1] = 1
96 rootmarker.s[2] = 1
97 rootmarker.name = "".encode('utf-8')
98 rootmarker.offset = 0
99 rootmarker.classtype = 0
100
101 markers = [ rootmarker ] # aka entities
102 entdata_structs = []
103 entdata_offset = 0
104
105 entity_count = 1
106
107 for obj in collection.objects:
108 if obj.type == 'EMPTY':
109 obj.cv_data.uid = entity_count
110 entity_count += 1
111
112 for obj in collection.objects:
113 if obj.type == 'EMPTY':
114 mk = marker()
115 mk.co[0] = obj.location[0]
116 mk.co[1] = obj.location[2]
117 mk.co[2] = -obj.location[1]
118
119 # Convert rotation quat to our space type
120 quat = obj.matrix_world.to_quaternion()
121 mk.q[0] = quat[1]
122 mk.q[1] = quat[3]
123 mk.q[2] = -quat[2]
124 mk.q[3] = quat[0]
125
126 mk.s[0] = obj.scale[0]
127 mk.s[1] = obj.scale[2]
128 mk.s[2] = obj.scale[1]
129 mk.name = obj.name.encode('utf-8')
130 mk.offset = entdata_offset
131
132 classtype = obj.cv_data.classtype
133
134 if classtype == 'k_classtype_gate':
135 mk.classtype = 1
136 entdata_offset += sizeof( classtype_gate )
137
138 gate = classtype_gate()
139 gate.target = 0
140 if obj.cv_data.target != None:
141 gate.target = obj.cv_data.target.cv_data.uid
142
143 entdata_structs += [gate]
144
145 elif classtype == 'k_thingummybob':
146 pass
147
148 markers += [mk]
149 header.marker_count += 1
150
151 elif obj.type == 'MESH':
152 default_mat = c_uint32(69)
153 default_mat.name = ""
154
155 if obj.data.name in mesh_cache:
156 ref = mesh_cache[obj.data.name]
157 for material_id, mref in enumerate(ref['sm']):
158 print(F" Link submesh({ref['users']}) '{obj.name}:{mat.name}'")
159
160 sm = submodel()
161 sm.indice_start = mref['indice_start']
162 sm.indice_count = mref['indice_count']
163 sm.vertex_start = mref['vertex_start']
164 sm.vertex_count = mref['vertex_count']
165 sm.name = obj.name.encode('utf-8')
166 sm.material = mref['material']
167 sm.bbx = mref['bbx']
168 submesh_set_transform( sm, obj )
169 layers += [sm]
170 header.layer_count += 1
171
172 ref['users'] += 1
173 continue
174
175 ref = mesh_cache[obj.data.name] = {}
176 ref['users'] = 0
177 ref['sm'] = []
178
179 dgraph = bpy.context.evaluated_depsgraph_get()
180 data = obj.evaluated_get(dgraph).data
181 data.calc_loop_triangles()
182 data.calc_normals_split()
183
184 mat_list = data.materials if len(data.materials) > 0 else [default_mat]
185 for material_id, mat in enumerate(mat_list):
186 mref = {}
187
188 sm = submodel()
189 sm.indice_start = header.indice_count
190 sm.vertex_start = header.vertex_count
191 sm.vertex_count = 0
192 sm.indice_count = 0
193 submesh_set_transform( sm, obj )
194
195 for i in range(3):
196 sm.bbx[0][i] = 999999
197 sm.bbx[1][i] = -999999
198
199 sm.name = obj.name.encode('utf-8')
200 sm.material = mat.name.encode('utf-8')
201 print( F" Creating submesh '{obj.name}:{mat.name}'" )
202 boffa = {}
203
204 hit_count = 0
205 miss_count = 0
206
207 # Write the vertex / indice data
208 #
209 for tri_index, tri in enumerate(data.loop_triangles):
210 if tri.material_index != material_id:
211 continue
212
213 for j in range(3):
214 vert = data.vertices[tri.vertices[j]]
215
216 co = vert.co
217 norm = data.loops[tri.loops[j]].normal
218 uv = (0,0)
219 if data.uv_layers:
220 uv = data.uv_layers.active.data[tri.loops[j]].uv
221
222 key = (round(co[0],4),round(co[1],4),round(co[2],4),\
223 round(norm[0],4),round(norm[1],4),round(norm[2],4),\
224 round(uv[0],4),round(uv[1],4))
225
226 if key in boffa:
227 indice_buffer += [boffa[key]]
228 hit_count += 1
229 else:
230 miss_count += 1
231 index = c_uint32(sm.vertex_count)
232 sm.vertex_count += 1
233
234 boffa[key] = index
235
236 indice_buffer += [index]
237
238 v = model_vert()
239 v.co[0] = co[0]
240 v.co[1] = co[2]
241 v.co[2] = -co[1]
242 v.norm[0] = norm[0]
243 v.norm[1] = norm[2]
244 v.norm[2] = -norm[1]
245 v.uv[0] = uv[0]
246 v.uv[1] = uv[1]
247 v.colour[0] = 1.0
248 v.colour[1] = 1.0
249 v.colour[2] = 1.0
250 v.colour[3] = 1.0
251 vertex_buffer += [v]
252
253 for i in range(3):
254 sm.bbx[0][i] = min( sm.bbx[0][i], v.co[i] )
255 sm.bbx[1][i] = max( sm.bbx[1][i], v.co[i] )
256
257 sm.indice_count += 1
258
259 if sm.vertex_count == 0:
260 for j in range(2):
261 for i in range(3):
262 sm.bbx[j][i] = 0
263
264 layers += [sm]
265 header.layer_count += 1
266 header.vertex_count += sm.vertex_count
267 header.indice_count += sm.indice_count
268
269 mref['indice_start'] = sm.indice_start
270 mref['indice_count'] = sm.indice_count
271 mref['vertex_start'] = sm.vertex_start
272 mref['vertex_count'] = sm.vertex_count
273 mref['bbx'] = sm.bbx
274 print( F"{sm.bbx[0][0]},{sm.bbx[0][1]},{sm.bbx[0][2]}" )
275
276 mref['material'] = sm.material
277 ref['sm'] += [mref]
278
279 fp.write( bytearray( header ) )
280 for l in layers:
281 fp.write( bytearray(l) )
282 for m in markers:
283 fp.write( bytearray(m) )
284 for v in vertex_buffer:
285 fp.write( bytearray(v) )
286 for i in indice_buffer:
287 fp.write( bytearray(i) )
288 for ed in entdata_structs:
289 fp.write( bytearray(ed) )
290
291 fp.close()
292
293 # Clicky clicky GUI
294 # ------------------------------------------------------------------------------
295
296 cv_view_draw_handler = None
297 cv_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
298
299 def cv_draw():
300 global cv_view_shader
301 cv_view_shader.bind()
302 gpu.state.depth_mask_set(False)
303 gpu.state.line_width_set(2.0)
304 gpu.state.face_culling_set('BACK')
305 gpu.state.depth_test_set('NONE')
306 gpu.state.blend_set('ADDITIVE')
307
308 verts = []
309 colours = []
310
311 for obj in bpy.context.collection.all_objects:
312 if obj.cv_data.classtype == 'k_classtype_gate':
313 if obj.cv_data.target != None:
314 p0 = obj.location
315 p1 = obj.cv_data.target.location
316 verts += [(p0[0],p0[1],p0[2])]
317 verts += [(p1[0],p1[1],p1[2])]
318 colours += [(0,1,0,1.0),(1,0,0,1.0)]
319
320 lines = batch_for_shader(\
321 cv_view_shader, 'LINES', \
322 { "pos":verts, "color":colours })
323
324 lines.draw( cv_view_shader )
325
326 def cv_poll_target(scene, obj):
327 if obj == bpy.context.active_object:
328 return False
329 if obj.cv_data.classtype == 'k_classtype_none':
330 return False
331 return True
332
333 class CV_OBJ_SETTINGS(bpy.types.PropertyGroup):
334 uid: bpy.props.IntProperty( name="" )
335
336 target: bpy.props.PointerProperty( type=bpy.types.Object, name="target", \
337 poll=cv_poll_target )
338
339 classtype: bpy.props.EnumProperty(
340 name="Format",
341 items = [
342 ('k_classtype_none', "k_classtype_none", "", 0),
343 ('k_classtype_gate', "k_classtype_gate", "", 1),
344 ])
345
346 class CV_OBJ_PANEL(bpy.types.Panel):
347 bl_label="Entity Config"
348 bl_idname="SCENE_PT_cv_entity"
349 bl_space_type='PROPERTIES'
350 bl_region_type='WINDOW'
351 bl_context="object"
352
353 def draw(_,context):
354 active_object = bpy.context.active_object
355 if active_object == None: return
356 _.layout.prop( active_object.cv_data, "classtype" )
357 _.layout.prop( active_object.cv_data, "target" )
358
359 class CV_INTERFACE(bpy.types.Panel):
360 bl_idname = "VIEW3D_PT_carve"
361 bl_label = "Carve"
362 bl_space_type = 'VIEW_3D'
363 bl_region_type = 'UI'
364 bl_category = "Carve"
365
366 def draw(_, context):
367 layout = _.layout
368 layout.operator( "carve.compile_all" )
369
370 class CV_COMPILE(bpy.types.Operator):
371 bl_idname="carve.compile_all"
372 bl_label="Compile All"
373
374 def execute(_,context):
375 for col in bpy.data.collections["export"].children:
376 write_model( col.name )
377
378 return {'FINISHED'}
379
380 classes = [CV_OBJ_SETTINGS,CV_OBJ_PANEL,CV_COMPILE,CV_INTERFACE]
381
382 def register():
383 global cv_view_draw_handler
384
385 for c in classes:
386 bpy.utils.register_class(c)
387
388 bpy.types.Object.cv_data = bpy.props.PointerProperty(type=CV_OBJ_SETTINGS)
389 cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
390 cv_draw,(),'WINDOW','POST_VIEW')
391
392 def unregister():
393 global cv_view_draw_handler
394
395 for c in classes:
396 bpy.utils.unregister_class(c)
397
398 bpy.types.SpaceView3D.draw_handler_remove(cv_view_draw_handler,'WINDOW')