everything
[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 layers += [sm]
260 header.layer_count += 1
261 header.vertex_count += sm.vertex_count
262 header.indice_count += sm.indice_count
263
264 mref['indice_start'] = sm.indice_start
265 mref['indice_count'] = sm.indice_count
266 mref['vertex_start'] = sm.vertex_start
267 mref['vertex_count'] = sm.vertex_count
268 mref['bbx'] = sm.bbx
269 mref['material'] = sm.material
270 ref['sm'] += [mref]
271
272 fp.write( bytearray( header ) )
273 for l in layers:
274 fp.write( bytearray(l) )
275 for m in markers:
276 fp.write( bytearray(m) )
277 for v in vertex_buffer:
278 fp.write( bytearray(v) )
279 for i in indice_buffer:
280 fp.write( bytearray(i) )
281 for ed in entdata_structs:
282 fp.write( bytearray(ed) )
283
284 fp.close()
285
286 # Clicky clicky GUI
287 # ------------------------------------------------------------------------------
288
289 cv_view_draw_handler = None
290 cv_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
291
292 def cv_draw():
293 global cv_view_shader
294 cv_view_shader.bind()
295 gpu.state.depth_mask_set(False)
296 gpu.state.line_width_set(2.0)
297 gpu.state.face_culling_set('BACK')
298 gpu.state.depth_test_set('NONE')
299 gpu.state.blend_set('ADDITIVE')
300
301 verts = []
302 colours = []
303
304 for obj in bpy.context.collection.all_objects:
305 if obj.cv_data.classtype == 'k_classtype_gate':
306 if obj.cv_data.target != None:
307 p0 = obj.location
308 p1 = obj.cv_data.target.location
309 verts += [(p0[0],p0[1],p0[2])]
310 verts += [(p1[0],p1[1],p1[2])]
311 colours += [(0,1,0,1.0),(1,0,0,1.0)]
312
313 lines = batch_for_shader(\
314 cv_view_shader, 'LINES', \
315 { "pos":verts, "color":colours })
316
317 lines.draw( cv_view_shader )
318
319 def cv_poll_target(scene, obj):
320 if obj == bpy.context.active_object:
321 return False
322 if obj.cv_data.classtype == 'k_classtype_none':
323 return False
324 return True
325
326 class CV_OBJ_SETTINGS(bpy.types.PropertyGroup):
327 uid: bpy.props.IntProperty( name="" )
328
329 target: bpy.props.PointerProperty( type=bpy.types.Object, name="target", \
330 poll=cv_poll_target )
331
332 classtype: bpy.props.EnumProperty(
333 name="Format",
334 items = [
335 ('k_classtype_none', "k_classtype_none", "", 0),
336 ('k_classtype_gate', "k_classtype_gate", "", 1),
337 ])
338
339 class CV_OBJ_PANEL(bpy.types.Panel):
340 bl_label="Entity Config"
341 bl_idname="SCENE_PT_cv_entity"
342 bl_space_type='PROPERTIES'
343 bl_region_type='WINDOW'
344 bl_context="object"
345
346 def draw(_,context):
347 active_object = bpy.context.active_object
348 if active_object == None: return
349 _.layout.prop( active_object.cv_data, "classtype" )
350 _.layout.prop( active_object.cv_data, "target" )
351
352 class CV_INTERFACE(bpy.types.Panel):
353 bl_idname = "VIEW3D_PT_carve"
354 bl_label = "Carve"
355 bl_space_type = 'VIEW_3D'
356 bl_region_type = 'UI'
357 bl_category = "Carve"
358
359 def draw(_, context):
360 layout = _.layout
361 layout.operator( "carve.compile_all" )
362
363 class CV_COMPILE(bpy.types.Operator):
364 bl_idname="carve.compile_all"
365 bl_label="Compile All"
366
367 def execute(_,context):
368 for col in bpy.data.collections["export"].children:
369 write_model( col.name )
370
371 return {'FINISHED'}
372
373 classes = [CV_OBJ_SETTINGS,CV_OBJ_PANEL,CV_COMPILE,CV_INTERFACE]
374
375 def register():
376 global cv_view_draw_handler
377
378 for c in classes:
379 bpy.utils.register_class(c)
380
381 bpy.types.Object.cv_data = bpy.props.PointerProperty(type=CV_OBJ_SETTINGS)
382 cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
383 cv_draw,(),'WINDOW','POST_VIEW')
384
385 def unregister():
386 global cv_view_draw_handler
387
388 for c in classes:
389 bpy.utils.unregister_class(c)
390
391 bpy.types.SpaceView3D.draw_handler_remove(cv_view_draw_handler,'WINDOW')