4 from mathutils
import *
5 from gpu_extras
.batch
import batch_for_shader
8 "name":"Carve exporter",
9 "author": "Harry Godden (hgn)",
16 "category":"Import/Export",
19 class mdl_vert(Structure
):
21 _fields_
= [("co",c_float
*3),
26 class mdl_submesh(Structure
):
28 _fields_
= [("indice_start",c_uint32
),
29 ("indice_count",c_uint32
),
30 ("vertex_start",c_uint32
),
31 ("vertex_count",c_uint32
),
32 ("bbx",(c_float
*3)*2),
33 ("material_id",c_uint32
)] # index into the material array
35 class mdl_material(Structure
):
37 _fields_
= [("pstr_name",c_uint32
)]
39 class mdl_node(Structure
):
41 _fields_
= [("co",c_float
*3),
44 ("submesh_start",c_uint32
),
45 ("submesh_count",c_uint32
),
46 ("classtype",c_uint32
),
48 ("pstr_name",c_uint32
)]
50 class mdl_header(Structure
):
52 _fields_
= [("identifier",c_uint32
),
54 ("file_length",c_uint32
),
55 ("vertex_count",c_uint32
),
56 ("vertex_offset",c_uint32
),
58 ("indice_count",c_uint32
),
59 ("indice_offset",c_uint32
),
61 ("submesh_count",c_uint32
),
62 ("submesh_offset",c_uint32
),
64 ("material_count",c_uint32
),
65 ("material_offset",c_uint32
),
67 ("node_count",c_uint32
),
68 ("node_offset",c_uint32
),
70 ("strings_offset",c_uint32
),
71 ("entdata_offset",c_uint32
)
75 # ==========================================
77 class classtype_gate(Structure
):
79 _fields_
= [("target",c_uint32
),
82 class classtype_block(Structure
):
84 _fields_
= [("bbx",(c_float
*3)*2)]
86 class classtype_spawn(Structure
):
88 _fields_
= [("temp",c_uint32
)]
90 class classtype_water(Structure
):
92 _fields_
= [("temp",c_uint32
)]
94 class classtype_car_path(Structure
):
96 _fields_
= [("target",c_uint32
),
100 # ==============================================================================
102 def write_model(name
):
103 print( F
"Create mode {name}" )
105 collection
= bpy
.data
.collections
[name
]
107 header
= mdl_header()
108 header
.identifier
= 0xABCD0000
110 header
.vertex_count
= 0
111 header
.indice_count
= 0
112 header
.submesh_count
= 0
113 header
.node_count
= 0
114 header
.material_count
= 0
115 header
.file_length
= 0
131 def emplace_string( s
):
132 nonlocal string_cache
, strings_buffer
134 if s
in string_cache
:
135 return string_cache
[s
]
137 string_cache
[s
] = len( strings_buffer
)
138 strings_buffer
+= (s
+'\0').encode('utf-8')
139 return string_cache
[s
]
141 def emplace_material( mat
):
142 nonlocal material_cache
, material_buffer
144 if mat
.name
in material_cache
:
145 return material_cache
[mat
.name
]
147 material_cache
[mat
.name
] = header
.material_count
148 dest
= mdl_material()
149 dest
.pstr_name
= emplace_string( mat
.name
)
150 material_buffer
+= [dest
]
152 header
.material_count
+= 1
153 return material_cache
[mat
.name
]
155 # Create root or empty node and materials
157 none_material
= c_uint32(69)
158 none_material
.name
= ""
159 emplace_material( none_material
)
172 root
.pstr_name
= emplace_string('')
173 root
.submesh_start
= 0
174 root
.submesh_count
= 0
177 node_buffer
+= [root
]
181 print( " assigning ids" )
182 header
.node_count
= 1
183 for obj
in collection
.all_objects
:
184 obj
.cv_data
.uid
= header
.node_count
185 header
.node_count
+= 1
187 print( " compiling data" )
188 for obj
in collection
.all_objects
:
189 print( F
" [{obj.cv_data.uid}/{header.node_count-1}] {obj.name}" )
192 node
.co
[0] = obj
.location
[0]
193 node
.co
[1] = obj
.location
[2]
194 node
.co
[2] = -obj
.location
[1]
196 # Convert rotation quat to our space type
197 quat
= obj
.matrix_world
.to_quaternion()
203 node
.s
[0] = obj
.scale
[0]
204 node
.s
[1] = obj
.scale
[2]
205 node
.s
[2] = obj
.scale
[1]
206 node
.pstr_name
= emplace_string( obj
.name
)
208 # Process entity data
210 node
.offset
= entdata_length
211 classtype
= obj
.cv_data
.classtype
213 if classtype
== 'k_classtype_none':
217 elif classtype
== 'k_classtype_gate':
219 entdata_length
+= sizeof( classtype_gate
)
221 gate
= classtype_gate()
223 if obj
.cv_data
.target
!= None:
224 gate
.target
= obj
.cv_data
.target
.cv_data
.uid
226 entdata_buffer
+= [gate
]
228 elif classtype
== 'k_classtype_block':
230 entdata_length
+= sizeof( classtype_block
)
232 source
= obj
.data
.cv_data
234 block
= classtype_block()
235 block
.bbx
[0][0] = source
.v0
[0]
236 block
.bbx
[0][1] = source
.v0
[2]
237 block
.bbx
[0][2] = -source
.v1
[1]
239 block
.bbx
[1][0] = source
.v1
[0]
240 block
.bbx
[1][1] = source
.v1
[2]
241 block
.bbx
[1][2] = -source
.v0
[1]
242 entdata_buffer
+= [block
]
244 elif classtype
== 'k_classtype_spawn':
247 elif classtype
== 'k_classtype_water':
249 elif classtype
== 'k_classtype_car_path':
251 entdata_length
+= sizeof( classtype_car_path
)
253 pn
= classtype_car_path()
257 if obj
.cv_data
.target
!= None:
258 pn
.target
= obj
.cv_data
.target
.cv_data
.uid
259 if obj
.cv_data
.target1
!= None:
260 pn
.target1
= obj
.cv_data
.target1
.cv_data
.uid
262 entdata_buffer
+= [pn
]
266 node
.submesh_start
= header
.submesh_count
267 node
.submesh_count
= 0
269 if obj
.type == 'MESH':
270 default_mat
= c_uint32(69)
271 default_mat
.name
= ""
273 if obj
.data
.name
in mesh_cache
:
274 ref
= mesh_cache
[obj
.data
.name
]
275 node
.submesh_start
= ref
.submesh_start
276 node
.submesh_count
= ref
.submesh_count
277 node_buffer
+= [node
]
280 dgraph
= bpy
.context
.evaluated_depsgraph_get()
281 data
= obj
.evaluated_get(dgraph
).data
282 data
.calc_loop_triangles()
283 data
.calc_normals_split()
285 mat_list
= data
.materials
if len(data
.materials
) > 0 else [default_mat
]
286 for material_id
, mat
in enumerate(mat_list
):
290 sm
.indice_start
= header
.indice_count
291 sm
.vertex_start
= header
.vertex_count
294 sm
.material_id
= emplace_material( mat
)
297 sm
.bbx
[0][i
] = 999999
298 sm
.bbx
[1][i
] = -999999
302 # Write the vertex / indice data
304 for tri_index
, tri
in enumerate(data
.loop_triangles
):
305 if tri
.material_index
!= material_id
:
309 vert
= data
.vertices
[tri
.vertices
[j
]]
313 norm
= data
.loops
[li
].normal
317 uv
= data
.uv_layers
.active
.data
[li
].uv
318 if data
.vertex_colors
:
319 colour
= data
.vertex_colors
.active
.data
[li
].color
322 m
= float(10**TOLERENCE
)
324 key
= (int(co
[0]*m
+0.5),\
332 int(colour
[0]*m
+0.5),\
333 int(colour
[1]*m
+0.5),\
334 int(colour
[2]*m
+0.5),\
335 int(colour
[3]*m
+0.5))
338 indice_buffer
+= [boffa
[key
]]
340 index
= c_uint32(sm
.vertex_count
)
344 indice_buffer
+= [index
]
355 v
.colour
[0] = colour
[0]
356 v
.colour
[1] = colour
[1]
357 v
.colour
[2] = colour
[2]
358 v
.colour
[3] = colour
[3]
362 sm
.bbx
[0][i
] = min( sm
.bbx
[0][i
], v
.co
[i
] )
363 sm
.bbx
[1][i
] = max( sm
.bbx
[1][i
], v
.co
[i
] )
367 if sm
.vertex_count
== 0:
372 submesh_buffer
+= [sm
]
373 node
.submesh_count
+= 1
374 header
.submesh_count
+= 1
375 header
.vertex_count
+= sm
.vertex_count
376 header
.indice_count
+= sm
.indice_count
378 mesh_cache
[obj
.data
.name
] = node
379 node_buffer
+= [node
]
383 print( "Writing data" )
384 fpos
= sizeof(header
)
386 header
.node_offset
= fpos
387 fpos
+= sizeof(mdl_node
)*header
.node_count
389 header
.submesh_offset
= fpos
390 fpos
+= sizeof(mdl_submesh
)*header
.submesh_count
392 header
.material_offset
= fpos
393 fpos
+= sizeof(mdl_material
)*header
.material_count
395 header
.entdata_offset
= fpos
396 fpos
+= entdata_length
398 header
.vertex_offset
= fpos
399 fpos
+= sizeof(mdl_vert
)*header
.vertex_count
401 header
.indice_offset
= fpos
402 fpos
+= sizeof(c_uint32
)*header
.indice_count
404 header
.strings_offset
= fpos
405 fpos
+= len(strings_buffer
)
407 header
.file_length
= fpos
409 fp
= open(F
"/home/harry/Documents/carve/models/{name}.mdl", "wb")
410 fp
.write( bytearray( header
) )
412 for node
in node_buffer
:
413 fp
.write( bytearray(node
) )
414 for sm
in submesh_buffer
:
415 fp
.write( bytearray(sm
) )
416 for mat
in material_buffer
:
417 fp
.write( bytearray(mat
) )
418 for ed
in entdata_buffer
:
419 fp
.write( bytearray(ed
) )
420 for v
in vertex_buffer
:
421 fp
.write( bytearray(v
) )
422 for i
in indice_buffer
:
423 fp
.write( bytearray(i
) )
424 fp
.write( strings_buffer
)
427 print( F
"Completed {name}.mdl" )
430 # ------------------------------------------------------------------------------
432 cv_view_draw_handler
= None
433 cv_view_shader
= gpu
.shader
.from_builtin('3D_SMOOTH_COLOR')
436 global cv_view_shader
437 cv_view_shader
.bind()
438 gpu
.state
.depth_mask_set(False)
439 gpu
.state
.line_width_set(2.0)
440 gpu
.state
.face_culling_set('BACK')
441 gpu
.state
.depth_test_set('NONE')
442 gpu
.state
.blend_set('NONE')
447 def drawbezier(p0
,h0
,p1
,h1
,c0
,c1
):
448 nonlocal verts
, colours
452 colours
+= [(0.5,0.5,0.5,1.0),(0.5,0.5,0.5,1)]
455 colours
+= [(1.0,1.0,1,1),(1,1,1,1)]
464 p
=ttt
*p1
+(3*tt
-3*ttt
)*h1
+(3*ttt
-6*tt
+3*t
)*h0
+(3*tt
-ttt
-3*t
+1)*p0
465 verts
+= [(last
[0],last
[1],last
[2])]
466 verts
+= [(p
[0],p
[1],p
[2])]
467 colours
+= [c0
*a0
+c1
*(1-a0
),c0
*a0
+c1
*(1-a0
)]
470 for obj
in bpy
.context
.collection
.objects
:
471 if obj
.cv_data
.classtype
== 'k_classtype_gate':
472 if obj
.cv_data
.target
!= None:
474 p1
= obj
.cv_data
.target
.location
475 verts
+= [(p0
[0],p0
[1],p0
[2])]
476 verts
+= [(p1
[0],p1
[1],p1
[2])]
477 colours
+= [(0,1,0,1.0),(1,0,0,1.0)]
478 elif obj
.cv_data
.classtype
== 'k_classtype_block':
479 a
= obj
.data
.cv_data
.v0
480 b
= obj
.data
.cv_data
.v1
483 vs
[0] = obj
.matrix_world
@ Vector((a
[0], a
[1], a
[2]))
484 vs
[1] = obj
.matrix_world
@ Vector((a
[0], b
[1], a
[2]))
485 vs
[2] = obj
.matrix_world
@ Vector((b
[0], b
[1], a
[2]))
486 vs
[3] = obj
.matrix_world
@ Vector((b
[0], a
[1], a
[2]))
487 vs
[4] = obj
.matrix_world
@ Vector((a
[0], a
[1], b
[2]))
488 vs
[5] = obj
.matrix_world
@ Vector((a
[0], b
[1], b
[2]))
489 vs
[6] = obj
.matrix_world
@ Vector((b
[0], b
[1], b
[2]))
490 vs
[7] = obj
.matrix_world
@ Vector((b
[0], a
[1], b
[2]))
492 indices
= [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
493 (0,4),(1,5),(2,6),(3,7)]
498 verts
+= [(v0
[0],v0
[1],v0
[2])]
499 verts
+= [(v1
[0],v1
[1],v1
[2])]
500 colours
+= [(1,1,0,1),(1,1,0,1)]
502 elif obj
.cv_data
.classtype
== 'k_classtype_spawn':
504 vs
[0] = obj
.matrix_world
@ Vector((0,0,0))
505 vs
[1] = obj
.matrix_world
@ Vector((0,2,0))
506 vs
[2] = obj
.matrix_world
@ Vector((0.5,1,0))
507 vs
[3] = obj
.matrix_world
@ Vector((-0.5,1,0))
508 indices
= [(0,1),(1,2),(1,3)]
512 verts
+= [(v0
[0],v0
[1],v0
[2])]
513 verts
+= [(v1
[0],v1
[1],v1
[2])]
514 colours
+= [(0,1,1,1),(0,1,1,1)]
516 elif obj
.cv_data
.classtype
== 'k_classtype_car_path':
518 h0
= obj
.matrix_world
@ Vector((1,0,0))
520 v0
= obj
.matrix_world
.to_quaternion() @ Vector((1,0,0))
521 c0
= Vector((v0
.x
*0.5+0.5, v0
.y
*0.5+0.5, 0.0, 1.0))
523 if obj
.cv_data
.target
!= None:
524 p1
= obj
.cv_data
.target
.location
525 h1
= obj
.cv_data
.target
.matrix_world
@ Vector((-1,0,0))
527 v1
= obj
.cv_data
.target
.matrix_world
.to_quaternion()@Vector((1,0,0))
528 c1
= Vector((v1
.x
*0.5+0.5, v1
.y
*0.5+0.5, 0.0, 1.0))
530 drawbezier( p0
, h0
, p1
, h1
, c0
, c1
)
532 if obj
.cv_data
.target1
!= None:
533 p1
= obj
.cv_data
.target1
.location
534 h1
= obj
.cv_data
.target1
.matrix_world
@ Vector((-1,0,0))
536 v1
= obj
.cv_data
.target1
.matrix_world
.to_quaternion()@Vector((1,0,0))
537 c1
= Vector((v1
.x
*0.5+0.5, v1
.y
*0.5+0.5, 0.0, 1.0))
539 drawbezier( p0
, h0
, p1
, h1
, c0
, c1
)
541 lines
= batch_for_shader(\
542 cv_view_shader
, 'LINES', \
543 { "pos":verts
, "color":colours
})
545 lines
.draw( cv_view_shader
)
547 def cv_poll_target(scene
, obj
):
548 if obj
== bpy
.context
.active_object
:
550 if obj
.cv_data
.classtype
== 'k_classtype_none':
554 class CV_MESH_SETTINGS(bpy
.types
.PropertyGroup
):
555 v0
: bpy
.props
.FloatVectorProperty(name
="v0",size
=3)
556 v1
: bpy
.props
.FloatVectorProperty(name
="v1",size
=3)
557 v2
: bpy
.props
.FloatVectorProperty(name
="v2",size
=3)
558 v3
: bpy
.props
.FloatVectorProperty(name
="v3",size
=3)
560 class CV_OBJ_SETTINGS(bpy
.types
.PropertyGroup
):
561 uid
: bpy
.props
.IntProperty( name
="" )
563 target
: bpy
.props
.PointerProperty( type=bpy
.types
.Object
, name
="target", \
564 poll
=cv_poll_target
)
565 target1
: bpy
.props
.PointerProperty( type=bpy
.types
.Object
, name
="target1", \
566 poll
=cv_poll_target
)
568 classtype
: bpy
.props
.EnumProperty(
571 ('k_classtype_none', "k_classtype_none", "", 0),
572 ('k_classtype_gate', "k_classtype_gate", "", 1),
573 ('k_classtype_block', "k_classtype_block", "", 2),
574 ('k_classtype_spawn', "k_classtype_spawn", "", 3),
575 ('k_classtype_water', "k_classtype_water", "", 4),
576 ('k_classtype_car_path', "k_classtype_car_path", "", 5)
579 class CV_OBJ_PANEL(bpy
.types
.Panel
):
580 bl_label
="Entity Config"
581 bl_idname
="SCENE_PT_cv_entity"
582 bl_space_type
='PROPERTIES'
583 bl_region_type
='WINDOW'
587 active_object
= bpy
.context
.active_object
588 if active_object
== None: return
589 _
.layout
.prop( active_object
.cv_data
, "classtype" )
591 if active_object
.cv_data
.classtype
== 'k_classtype_gate':
592 _
.layout
.prop( active_object
.cv_data
, "target" )
593 elif active_object
.cv_data
.classtype
== 'k_classtype_car_path':
594 _
.layout
.prop( active_object
.cv_data
, "target" )
595 _
.layout
.prop( active_object
.cv_data
, "target1" )
596 elif active_object
.cv_data
.classtype
== 'k_classtype_block':
597 mesh
= active_object
.data
599 _
.layout
.label( text
=F
"(i) Data is stored in {mesh.name}" )
600 _
.layout
.prop( mesh
.cv_data
, "v0" )
601 _
.layout
.prop( mesh
.cv_data
, "v1" )
602 _
.layout
.prop( mesh
.cv_data
, "v2" )
603 _
.layout
.prop( mesh
.cv_data
, "v3" )
605 class CV_INTERFACE(bpy
.types
.Panel
):
606 bl_idname
= "VIEW3D_PT_carve"
608 bl_space_type
= 'VIEW_3D'
609 bl_region_type
= 'UI'
610 bl_category
= "Carve"
612 def draw(_
, context
):
614 layout
.operator( "carve.compile_all" )
617 for col
in bpy
.data
.collections
["export"].children
:
618 write_model( col
.name
)
620 class CV_COMPILE(bpy
.types
.Operator
):
621 bl_idname
="carve.compile_all"
622 bl_label
="Compile All"
624 def execute(_
,context
):
626 #cProfile.runctx("test_compile()",globals(),locals(),sort=1)
627 #for col in bpy.data.collections["export"].children:
628 # write_model( col.name )
632 classes
= [CV_OBJ_SETTINGS
,CV_OBJ_PANEL
,CV_COMPILE
,CV_INTERFACE
,\
636 global cv_view_draw_handler
639 bpy
.utils
.register_class(c
)
641 bpy
.types
.Object
.cv_data
= bpy
.props
.PointerProperty(type=CV_OBJ_SETTINGS
)
642 bpy
.types
.Mesh
.cv_data
= bpy
.props
.PointerProperty(type=CV_MESH_SETTINGS
)
644 cv_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
645 cv_draw
,(),'WINDOW','POST_VIEW')
648 global cv_view_draw_handler
651 bpy
.utils
.unregister_class(c
)
653 bpy
.types
.SpaceView3D
.draw_handler_remove(cv_view_draw_handler
,'WINDOW')