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
),
99 class classtype_instance(Structure
):
101 _fields_
= [("pstr_file",c_uint32
)]
104 # ==============================================================================
106 def write_model(name
):
107 print( F
"Create mode {name}" )
109 header
= mdl_header()
110 header
.identifier
= 0xABCD0000
112 header
.vertex_count
= 0
113 header
.indice_count
= 0
114 header
.submesh_count
= 0
115 header
.node_count
= 0
116 header
.material_count
= 0
117 header
.file_length
= 0
133 def emplace_string( s
):
134 nonlocal string_cache
, strings_buffer
136 if s
in string_cache
:
137 return string_cache
[s
]
139 string_cache
[s
] = len( strings_buffer
)
140 strings_buffer
+= (s
+'\0').encode('utf-8')
141 return string_cache
[s
]
143 def emplace_material( mat
):
144 nonlocal material_cache
, material_buffer
146 if mat
.name
in material_cache
:
147 return material_cache
[mat
.name
]
149 material_cache
[mat
.name
] = header
.material_count
150 dest
= mdl_material()
151 dest
.pstr_name
= emplace_string( mat
.name
)
152 material_buffer
+= [dest
]
154 header
.material_count
+= 1
155 return material_cache
[mat
.name
]
157 # Create root or empty node and materials
159 none_material
= c_uint32(69)
160 none_material
.name
= ""
161 emplace_material( none_material
)
174 root
.pstr_name
= emplace_string('')
175 root
.submesh_start
= 0
176 root
.submesh_count
= 0
179 node_buffer
+= [root
]
183 print( " assigning ids" )
184 collection
= bpy
.data
.collections
[name
]
186 header
.node_count
= 1
187 for obj
in collection
.all_objects
:
188 obj
.cv_data
.uid
= header
.node_count
189 header
.node_count
+= 1
191 print( " compiling data" )
192 for obj
in collection
.all_objects
:
193 print( F
" [{obj.cv_data.uid}/{header.node_count-1}] {obj.name}" )
196 node
.co
[0] = obj
.location
[0]
197 node
.co
[1] = obj
.location
[2]
198 node
.co
[2] = -obj
.location
[1]
200 # Convert rotation quat to our space type
201 quat
= obj
.matrix_world
.to_quaternion()
207 node
.s
[0] = obj
.scale
[0]
208 node
.s
[1] = obj
.scale
[2]
209 node
.s
[2] = obj
.scale
[1]
210 node
.pstr_name
= emplace_string( obj
.name
)
212 # Process entity data
214 node
.offset
= entdata_length
215 classtype
= obj
.cv_data
.classtype
217 if 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
]
263 elif obj
.is_instancer
:
264 target
= obj
.instance_collection
267 entdata_length
+= sizeof( classtype_instance
)
269 inst
= classtype_instance()
270 inst
.pstr_file
= emplace_string( F
"models/{target.name}.mdl" )
271 entdata_buffer
+= [inst
]
273 # classtype == 'k_classtype_none':
280 node
.submesh_start
= header
.submesh_count
281 node
.submesh_count
= 0
283 if obj
.type == 'MESH':
284 default_mat
= c_uint32(69)
285 default_mat
.name
= ""
287 if obj
.data
.name
in mesh_cache
:
288 ref
= mesh_cache
[obj
.data
.name
]
289 node
.submesh_start
= ref
.submesh_start
290 node
.submesh_count
= ref
.submesh_count
291 node_buffer
+= [node
]
294 dgraph
= bpy
.context
.evaluated_depsgraph_get()
295 data
= obj
.evaluated_get(dgraph
).data
296 data
.calc_loop_triangles()
297 data
.calc_normals_split()
299 mat_list
= data
.materials
if len(data
.materials
) > 0 else [default_mat
]
300 for material_id
, mat
in enumerate(mat_list
):
304 sm
.indice_start
= header
.indice_count
305 sm
.vertex_start
= header
.vertex_count
308 sm
.material_id
= emplace_material( mat
)
311 sm
.bbx
[0][i
] = 999999
312 sm
.bbx
[1][i
] = -999999
316 # Write the vertex / indice data
318 for tri_index
, tri
in enumerate(data
.loop_triangles
):
319 if tri
.material_index
!= material_id
:
323 vert
= data
.vertices
[tri
.vertices
[j
]]
327 norm
= data
.loops
[li
].normal
331 uv
= data
.uv_layers
.active
.data
[li
].uv
332 if data
.vertex_colors
:
333 colour
= data
.vertex_colors
.active
.data
[li
].color
336 m
= float(10**TOLERENCE
)
338 key
= (int(co
[0]*m
+0.5),\
346 int(colour
[0]*m
+0.5),\
347 int(colour
[1]*m
+0.5),\
348 int(colour
[2]*m
+0.5),\
349 int(colour
[3]*m
+0.5))
352 indice_buffer
+= [boffa
[key
]]
354 index
= c_uint32(sm
.vertex_count
)
358 indice_buffer
+= [index
]
369 v
.colour
[0] = colour
[0]
370 v
.colour
[1] = colour
[1]
371 v
.colour
[2] = colour
[2]
372 v
.colour
[3] = colour
[3]
376 sm
.bbx
[0][i
] = min( sm
.bbx
[0][i
], v
.co
[i
] )
377 sm
.bbx
[1][i
] = max( sm
.bbx
[1][i
], v
.co
[i
] )
381 if sm
.vertex_count
== 0:
386 submesh_buffer
+= [sm
]
387 node
.submesh_count
+= 1
388 header
.submesh_count
+= 1
389 header
.vertex_count
+= sm
.vertex_count
390 header
.indice_count
+= sm
.indice_count
392 mesh_cache
[obj
.data
.name
] = node
393 node_buffer
+= [node
]
397 print( "Writing data" )
398 fpos
= sizeof(header
)
400 header
.node_offset
= fpos
401 fpos
+= sizeof(mdl_node
)*header
.node_count
403 header
.submesh_offset
= fpos
404 fpos
+= sizeof(mdl_submesh
)*header
.submesh_count
406 header
.material_offset
= fpos
407 fpos
+= sizeof(mdl_material
)*header
.material_count
409 header
.entdata_offset
= fpos
410 fpos
+= entdata_length
412 header
.vertex_offset
= fpos
413 fpos
+= sizeof(mdl_vert
)*header
.vertex_count
415 header
.indice_offset
= fpos
416 fpos
+= sizeof(c_uint32
)*header
.indice_count
418 header
.strings_offset
= fpos
419 fpos
+= len(strings_buffer
)
421 header
.file_length
= fpos
423 fp
= open(F
"/home/harry/Documents/carve/models/{name}.mdl", "wb")
424 fp
.write( bytearray( header
) )
426 for node
in node_buffer
:
427 fp
.write( bytearray(node
) )
428 for sm
in submesh_buffer
:
429 fp
.write( bytearray(sm
) )
430 for mat
in material_buffer
:
431 fp
.write( bytearray(mat
) )
432 for ed
in entdata_buffer
:
433 fp
.write( bytearray(ed
) )
434 for v
in vertex_buffer
:
435 fp
.write( bytearray(v
) )
436 for i
in indice_buffer
:
437 fp
.write( bytearray(i
) )
438 fp
.write( strings_buffer
)
441 print( F
"Completed {name}.mdl" )
444 # ------------------------------------------------------------------------------
446 cv_view_draw_handler
= None
447 cv_view_shader
= gpu
.shader
.from_builtin('3D_SMOOTH_COLOR')
450 global cv_view_shader
451 cv_view_shader
.bind()
452 gpu
.state
.depth_mask_set(False)
453 gpu
.state
.line_width_set(2.0)
454 gpu
.state
.face_culling_set('BACK')
455 gpu
.state
.depth_test_set('NONE')
456 gpu
.state
.blend_set('NONE')
461 def drawbezier(p0
,h0
,p1
,h1
,c0
,c1
):
462 nonlocal verts
, colours
466 colours
+= [(0.5,0.5,0.5,1.0),(0.5,0.5,0.5,1)]
469 colours
+= [(1.0,1.0,1,1),(1,1,1,1)]
478 p
=ttt
*p1
+(3*tt
-3*ttt
)*h1
+(3*ttt
-6*tt
+3*t
)*h0
+(3*tt
-ttt
-3*t
+1)*p0
479 verts
+= [(last
[0],last
[1],last
[2])]
480 verts
+= [(p
[0],p
[1],p
[2])]
481 colours
+= [c0
*a0
+c1
*(1-a0
),c0
*a0
+c1
*(1-a0
)]
484 for obj
in bpy
.context
.collection
.objects
:
485 if obj
.cv_data
.classtype
== 'k_classtype_gate':
486 if obj
.cv_data
.target
!= None:
488 p1
= obj
.cv_data
.target
.location
489 verts
+= [(p0
[0],p0
[1],p0
[2])]
490 verts
+= [(p1
[0],p1
[1],p1
[2])]
491 colours
+= [(0,1,0,1.0),(1,0,0,1.0)]
492 elif obj
.cv_data
.classtype
== 'k_classtype_block':
493 a
= obj
.data
.cv_data
.v0
494 b
= obj
.data
.cv_data
.v1
497 vs
[0] = obj
.matrix_world
@ Vector((a
[0], a
[1], a
[2]))
498 vs
[1] = obj
.matrix_world
@ Vector((a
[0], b
[1], a
[2]))
499 vs
[2] = obj
.matrix_world
@ Vector((b
[0], b
[1], a
[2]))
500 vs
[3] = obj
.matrix_world
@ Vector((b
[0], a
[1], a
[2]))
501 vs
[4] = obj
.matrix_world
@ Vector((a
[0], a
[1], b
[2]))
502 vs
[5] = obj
.matrix_world
@ Vector((a
[0], b
[1], b
[2]))
503 vs
[6] = obj
.matrix_world
@ Vector((b
[0], b
[1], b
[2]))
504 vs
[7] = obj
.matrix_world
@ Vector((b
[0], a
[1], b
[2]))
506 indices
= [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
507 (0,4),(1,5),(2,6),(3,7)]
512 verts
+= [(v0
[0],v0
[1],v0
[2])]
513 verts
+= [(v1
[0],v1
[1],v1
[2])]
514 colours
+= [(1,1,0,1),(1,1,0,1)]
516 elif obj
.cv_data
.classtype
== 'k_classtype_spawn':
518 vs
[0] = obj
.matrix_world
@ Vector((0,0,0))
519 vs
[1] = obj
.matrix_world
@ Vector((0,2,0))
520 vs
[2] = obj
.matrix_world
@ Vector((0.5,1,0))
521 vs
[3] = obj
.matrix_world
@ Vector((-0.5,1,0))
522 indices
= [(0,1),(1,2),(1,3)]
526 verts
+= [(v0
[0],v0
[1],v0
[2])]
527 verts
+= [(v1
[0],v1
[1],v1
[2])]
528 colours
+= [(0,1,1,1),(0,1,1,1)]
530 elif obj
.cv_data
.classtype
== 'k_classtype_car_path':
532 h0
= obj
.matrix_world
@ Vector((1,0,0))
534 v0
= obj
.matrix_world
.to_quaternion() @ Vector((1,0,0))
535 c0
= Vector((v0
.x
*0.5+0.5, v0
.y
*0.5+0.5, 0.0, 1.0))
537 if obj
.cv_data
.target
!= None:
538 p1
= obj
.cv_data
.target
.location
539 h1
= obj
.cv_data
.target
.matrix_world
@ Vector((-1,0,0))
541 v1
= obj
.cv_data
.target
.matrix_world
.to_quaternion()@Vector((1,0,0))
542 c1
= Vector((v1
.x
*0.5+0.5, v1
.y
*0.5+0.5, 0.0, 1.0))
544 drawbezier( p0
, h0
, p1
, h1
, c0
, c1
)
546 if obj
.cv_data
.target1
!= None:
547 p1
= obj
.cv_data
.target1
.location
548 h1
= obj
.cv_data
.target1
.matrix_world
@ Vector((-1,0,0))
550 v1
= obj
.cv_data
.target1
.matrix_world
.to_quaternion()@Vector((1,0,0))
551 c1
= Vector((v1
.x
*0.5+0.5, v1
.y
*0.5+0.5, 0.0, 1.0))
553 drawbezier( p0
, h0
, p1
, h1
, c0
, c1
)
555 lines
= batch_for_shader(\
556 cv_view_shader
, 'LINES', \
557 { "pos":verts
, "color":colours
})
559 lines
.draw( cv_view_shader
)
561 def cv_poll_target(scene
, obj
):
562 if obj
== bpy
.context
.active_object
:
564 if obj
.cv_data
.classtype
== 'k_classtype_none':
568 class CV_MESH_SETTINGS(bpy
.types
.PropertyGroup
):
569 v0
: bpy
.props
.FloatVectorProperty(name
="v0",size
=3)
570 v1
: bpy
.props
.FloatVectorProperty(name
="v1",size
=3)
571 v2
: bpy
.props
.FloatVectorProperty(name
="v2",size
=3)
572 v3
: bpy
.props
.FloatVectorProperty(name
="v3",size
=3)
574 class CV_OBJ_SETTINGS(bpy
.types
.PropertyGroup
):
575 uid
: bpy
.props
.IntProperty( name
="" )
577 target
: bpy
.props
.PointerProperty( type=bpy
.types
.Object
, name
="target", \
578 poll
=cv_poll_target
)
579 target1
: bpy
.props
.PointerProperty( type=bpy
.types
.Object
, name
="target1", \
580 poll
=cv_poll_target
)
582 classtype
: bpy
.props
.EnumProperty(
585 ('k_classtype_none', "k_classtype_none", "", 0),
586 ('k_classtype_gate', "k_classtype_gate", "", 1),
587 ('k_classtype_block', "k_classtype_block", "", 2),
588 ('k_classtype_spawn', "k_classtype_spawn", "", 3),
589 ('k_classtype_water', "k_classtype_water", "", 4),
590 ('k_classtype_car_path', "k_classtype_car_path", "", 5)
593 class CV_OBJ_PANEL(bpy
.types
.Panel
):
594 bl_label
="Entity Config"
595 bl_idname
="SCENE_PT_cv_entity"
596 bl_space_type
='PROPERTIES'
597 bl_region_type
='WINDOW'
601 active_object
= bpy
.context
.active_object
602 if active_object
== None: return
603 _
.layout
.prop( active_object
.cv_data
, "classtype" )
605 if active_object
.cv_data
.classtype
== 'k_classtype_gate':
606 _
.layout
.prop( active_object
.cv_data
, "target" )
607 elif active_object
.cv_data
.classtype
== 'k_classtype_car_path':
608 _
.layout
.prop( active_object
.cv_data
, "target" )
609 _
.layout
.prop( active_object
.cv_data
, "target1" )
610 elif active_object
.cv_data
.classtype
== 'k_classtype_block':
611 mesh
= active_object
.data
613 _
.layout
.label( text
=F
"(i) Data is stored in {mesh.name}" )
614 _
.layout
.prop( mesh
.cv_data
, "v0" )
615 _
.layout
.prop( mesh
.cv_data
, "v1" )
616 _
.layout
.prop( mesh
.cv_data
, "v2" )
617 _
.layout
.prop( mesh
.cv_data
, "v3" )
619 class CV_INTERFACE(bpy
.types
.Panel
):
620 bl_idname
= "VIEW3D_PT_carve"
622 bl_space_type
= 'VIEW_3D'
623 bl_region_type
= 'UI'
624 bl_category
= "Carve"
626 def draw(_
, context
):
628 layout
.operator( "carve.compile_all" )
631 for col
in bpy
.data
.collections
["export"].children
:
632 write_model( col
.name
)
634 class CV_COMPILE(bpy
.types
.Operator
):
635 bl_idname
="carve.compile_all"
636 bl_label
="Compile All"
638 def execute(_
,context
):
640 #cProfile.runctx("test_compile()",globals(),locals(),sort=1)
641 #for col in bpy.data.collections["export"].children:
642 # write_model( col.name )
646 classes
= [CV_OBJ_SETTINGS
,CV_OBJ_PANEL
,CV_COMPILE
,CV_INTERFACE
,\
650 global cv_view_draw_handler
653 bpy
.utils
.register_class(c
)
655 bpy
.types
.Object
.cv_data
= bpy
.props
.PointerProperty(type=CV_OBJ_SETTINGS
)
656 bpy
.types
.Mesh
.cv_data
= bpy
.props
.PointerProperty(type=CV_MESH_SETTINGS
)
658 cv_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
659 cv_draw
,(),'WINDOW','POST_VIEW')
662 global cv_view_draw_handler
665 bpy
.utils
.unregister_class(c
)
667 bpy
.types
.SpaceView3D
.draw_handler_remove(cv_view_draw_handler
,'WINDOW')