oh yeah mr crabs
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
1 #
2 # Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
3 #
4
5 import bpy, math, gpu
6 import cProfile
7 from ctypes import *
8 from mathutils import *
9 from gpu_extras.batch import batch_for_shader
10
11 bl_info = {
12 "name":"Carve exporter",
13 "author": "Harry Godden (hgn)",
14 "version": (0,1),
15 "blender":(3,1,0),
16 "location":"Export",
17 "descriptin":"",
18 "warning":"",
19 "wiki_url":"",
20 "category":"Import/Export",
21 }
22
23 class mdl_vert(Structure):
24 _pack_ = 1
25 _fields_ = [("co",c_float*3),
26 ("norm",c_float*3),
27 ("uv",c_float*2),
28 ("colour",c_uint8*4),
29 ("weights",c_uint16*4),
30 ("groups",c_uint8*4)]
31
32 class mdl_submesh(Structure):
33 _pack_ = 1
34 _fields_ = [("indice_start",c_uint32),
35 ("indice_count",c_uint32),
36 ("vertex_start",c_uint32),
37 ("vertex_count",c_uint32),
38 ("bbx",(c_float*3)*2),
39 ("material_id",c_uint32)] # index into the material array
40
41 class mdl_material(Structure):
42 _pack_ = 1
43 _fields_ = [("pstr_name",c_uint32)]
44
45 class mdl_node(Structure):
46 _pack_ = 1
47 _fields_ = [("co",c_float*3),
48 ( "q",c_float*4),
49 ( "s",c_float*3),
50 ("submesh_start",c_uint32),
51 ("submesh_count",c_uint32),
52 ("classtype",c_uint32),
53 ("offset",c_uint32),
54 ("parent",c_uint32),
55 ("pstr_name",c_uint32)]
56
57 class mdl_header(Structure):
58 _pack_ = 1
59 _fields_ = [("identifier",c_uint32),
60 ("version",c_uint32),
61 ("file_length",c_uint32),
62 ("vertex_count",c_uint32),
63 ("vertex_offset",c_uint32),
64
65 ("indice_count",c_uint32),
66 ("indice_offset",c_uint32),
67
68 ("submesh_count",c_uint32),
69 ("submesh_offset",c_uint32),
70
71 ("material_count",c_uint32),
72 ("material_offset",c_uint32),
73
74 ("node_count",c_uint32),
75 ("node_offset",c_uint32),
76
77 ("anim_count",c_uint32),
78 ("anim_offset",c_uint32),
79
80 ("strings_offset",c_uint32),
81 ("entdata_offset",c_uint32),
82 ("animdata_offset",c_uint32)
83 ]
84
85 class mdl_animation(Structure):
86 _pack_ = 1
87 _fields_ = [("pstr_name",c_uint32),
88 ("length",c_uint32),
89 ("rate",c_float),
90 ("offset",c_uint32)]
91
92 class mdl_keyframe(Structure):
93 _pack_ = 1
94 _fields_ = [("co",c_float*3),
95 ("q",c_float*4),
96 ("s",c_float*3)]
97
98 # Entity types
99 # ==========================================
100
101 class classtype_gate(Structure):
102 _pack_ = 1
103 _fields_ = [("target",c_uint32),
104 ("dims",c_float*3)]
105
106 class classtype_block(Structure):
107 _pack_ = 1
108 _fields_ = [("bbx",(c_float*3)*2)]
109
110 class classtype_spawn(Structure):
111 _pack_ = 1
112 _fields_ = [("temp",c_uint32)]
113
114 class classtype_water(Structure):
115 _pack_ = 1
116 _fields_ = [("temp",c_uint32)]
117
118 class classtype_car_path(Structure):
119 _pack_ = 1
120 _fields_ = [("target",c_uint32),
121 ("target1",c_uint32)]
122
123 class classtype_instance(Structure):
124 _pack_ = 1
125 _fields_ = [("pstr_file",c_uint32)]
126
127 class classtype_capsule(Structure):
128 _pack_ = 1
129 _fields_ = [("height",c_float),
130 ("radius",c_float)]
131
132 class classtype_route_node(Structure):
133 _pack_ = 1
134 _fields_ = [("target",c_uint32),
135 ("target1",c_uint32)]
136
137 class classtype_route(Structure):
138 _pack_ = 1
139 _fields_ = [("id_start",c_uint32),
140 ("colour",c_float*3)]
141
142 class classtype_skin(Structure):
143 _pack_ = 1
144 _fields_ = [("skeleton",c_uint32)]
145
146 class classtype_skeleton(Structure):
147 _pack_ = 1
148 _fields_ = [("channels",c_uint32),
149 ("ik_count",c_uint32),
150 ("collider_count",c_uint32),
151 ("anim_start",c_uint32),
152 ("anim_count",c_uint32)]
153
154 class classtype_bone(Structure):
155 _pack_ = 1
156 _fields_ = [("deform",c_uint32),
157 ("ik_target",c_uint32),
158 ("ik_pole",c_uint32),
159 ("collider",c_uint32),
160 ("use_limits",c_uint32),
161 ("angle_limits",(c_float*3)*2),
162 ("hitbox",(c_float*3)*2)]
163
164 class classtype_achievement_box(Structure):
165 _pack_ = 1
166 _fields_ = [("pstr_name",c_uint32),
167 ("trigger",c_uint32)]
168
169 class classtype_audio(Structure):
170 _pack_ = 1
171 _fields_ = [("pstr_file",c_uint32),
172 ("flags",c_uint32),
173 ("volume",c_float)]
174
175 # Exporter
176 # ==============================================================================
177
178 def write_model(collection_name):
179 print( F"Model graph | Create mode '{collection_name}'" )
180
181 header = mdl_header()
182 header.identifier = 0xABCD0000
183 header.version = 0
184 header.vertex_count = 0
185 header.indice_count = 0
186 header.submesh_count = 0
187 header.node_count = 0
188 header.material_count = 0
189 header.file_length = 0
190
191 mesh_cache = {}
192 string_cache = {}
193 material_cache = {}
194
195 strings_buffer = b''
196
197 material_buffer = []
198 submesh_buffer = []
199 vertex_buffer = []
200 indice_buffer = []
201 node_buffer = []
202 entdata_buffer = []
203 entdata_length = 0
204
205 anim_buffer = []
206 animdata_length = 0
207 animdata_buffer = []
208
209 def emplace_string( s ):
210 nonlocal string_cache, strings_buffer
211
212 if s in string_cache:
213 return string_cache[s]
214
215 string_cache[s] = len( strings_buffer )
216 strings_buffer += (s+'\0').encode('utf-8')
217 return string_cache[s]
218
219 def emplace_material( mat ):
220 nonlocal material_cache, material_buffer
221
222 if mat == None:
223 return 0
224
225 if mat.name in material_cache:
226 return material_cache[mat.name]
227
228 material_cache[mat.name] = header.material_count
229 dest = mdl_material()
230 dest.pstr_name = emplace_string( mat.name )
231 material_buffer += [dest]
232
233 header.material_count += 1
234 return material_cache[mat.name]
235
236 # Create root or empty node and materials
237 # this is to designate id 0 as 'NULL'
238 #
239 none_material = c_uint32(69)
240 none_material.name = ""
241 emplace_material( none_material )
242
243 root = mdl_node()
244 root.co[0] = 0
245 root.co[1] = 0
246 root.co[2] = 0
247 root.q[0] = 0
248 root.q[1] = 0
249 root.q[2] = 0
250 root.q[3] = 1
251 root.s[0] = 1
252 root.s[1] = 1
253 root.s[2] = 1
254 root.pstr_name = emplace_string('')
255 root.submesh_start = 0
256 root.submesh_count = 0
257 root.offset = 0
258 root.classtype = 0
259 node_buffer += [root]
260
261 # Do exporting
262 #
263 print( " assigning ids" )
264 collection = bpy.data.collections[collection_name]
265
266 # Scene graph
267 # ==========================================
268
269 header.node_count = 0
270 def _uid():
271 nonlocal header
272 uid = header.node_count
273 header.node_count += 1
274 return uid
275
276 print( " creating scene graph" )
277 graph = {}
278 graph["obj"] = None
279 graph["depth"] = 0
280 graph["children"] = []
281 graph["uid"] = _uid()
282 graph["parent"] = None
283
284 graph_lookup = {} # object can lookup its graph def here
285
286 for obj in collection.all_objects:
287 if not obj.parent:
288
289 def _extend( p, n, d ):
290 uid = _uid()
291 tree = {}
292 tree["uid"] = uid
293 tree["children"] = []
294 tree["depth"] = d
295 tree["obj"] = n
296 tree["parent"] = p
297 n.cv_data.uid = uid
298
299 if n.type == 'ARMATURE':
300 tree["bones"] = [None] # None is the root transform
301 tree["ik_count"] = 0
302 tree["collider_count"] = 0
303
304 def _extendb( p, n, d ):
305 nonlocal tree
306
307 btree = {}
308 btree["bone"] = n
309 btree["uid"] = _uid()
310 btree["children"] = []
311 btree["depth"] = d
312 btree["parent"] = p
313 tree["bones"] += [n.name]
314
315 for c in n.children:
316 _extendb( btree, c, d+1 )
317
318 for c in tree['obj'].pose.bones[n.name].constraints:
319 if c.type == 'IK':
320 btree["target"] = c.subtarget
321 btree["pole"] = c.pole_subtarget
322 tree["ik_count"] += 1
323
324 if n.cv_data.collider:
325 tree['collider_count'] += 1
326
327 btree['deform'] = n.use_deform
328 p['children'] += [btree]
329
330 for b in n.data.bones:
331 if not b.parent:
332 _extendb( tree, b, d+1 )
333
334 for obj1 in n.children:
335 nonlocal collection
336 for c1 in obj1.users_collection:
337 if c1 == collection:
338 _extend( tree, obj1, d+1 )
339 break
340
341 p["children"] += [tree]
342 graph_lookup[n] = tree
343
344 _extend( graph, obj, 1 )
345
346
347 def _graph_iter(p):
348 for c in p['children']:
349 yield c
350 yield from _graph_iter(c)
351
352 it = _graph_iter(graph)
353
354 root.parent = 0xffffffff
355
356 # Compile
357 # ==============================================
358 it = _graph_iter(graph)
359 print( " compiling data" )
360 for node_def in it:
361 if 'obj' in node_def:
362 obj = node_def['obj']
363 objt = obj.type
364 objco = obj.location
365 elif 'bone' in node_def:
366 obj = node_def['bone']
367 objt = 'BONE'
368 objco = obj.head_local
369
370 depth = node_def['depth']
371 uid = node_def['uid']
372
373 node = mdl_node()
374 node.co[0] = objco[0]
375 node.co[1] = objco[2]
376 node.co[2] = -objco[1]
377
378 # Convert rotation quat to our space type
379 quat = obj.matrix_local.to_quaternion()
380 node.q[0] = quat[1]
381 node.q[1] = quat[3]
382 node.q[2] = -quat[2]
383 node.q[3] = quat[0]
384
385 if objt == 'BONE':
386 node.s[0] = obj.tail_local[0] - node.co[0]
387 node.s[1] = obj.tail_local[2] - node.co[1]
388 node.s[2] = -obj.tail_local[1] - node.co[2]
389 else:
390 node.s[0] = obj.scale[0]
391 node.s[1] = obj.scale[2]
392 node.s[2] = obj.scale[1]
393
394 node.pstr_name = emplace_string( obj.name )
395
396 if node_def["parent"]:
397 node.parent = node_def["parent"]["uid"]
398
399 if objt == 'BONE':
400 classtype = 'k_classtype_bone'
401 elif objt == 'ARMATURE':
402 classtype = 'k_classtype_skeleton'
403 else:
404 classtype = obj.cv_data.classtype
405
406 # Process type: MESH
407 # =================================================================
408 #
409
410 # Dont use the cache if we have modifiers that affect the normals
411 #
412 compile_mesh = False
413 if objt == 'MESH':
414 armature_def = None
415 compile_mesh = True
416 can_use_cache = True
417
418 for mod in obj.modifiers:
419 if mod.type == 'DATA_TRANSFER' or mod.type == 'SHRINKWRAP' or \
420 mod.type == 'BOOLEAN' or mod.type == 'CURVE' or \
421 mod.type == 'ARRAY':
422 can_use_cache = False
423
424 if mod.type == 'ARMATURE':
425 classtype = 'k_classtype_skin'
426 armature_def = graph_lookup[mod.object]
427 POSE_OR_REST_CACHE = armature_def['obj'].data.pose_position
428
429 armature_def['obj'].data.pose_position = 'REST'
430
431 if can_use_cache and obj.data.name in mesh_cache:
432 ref = mesh_cache[obj.data.name]
433 node.submesh_start = ref.submesh_start
434 node.submesh_count = ref.submesh_count
435 compile_mesh = False
436
437 if compile_mesh:
438 node.submesh_start = header.submesh_count
439 node.submesh_count = 0
440
441 default_mat = c_uint32(69)
442 default_mat.name = ""
443
444 dgraph = bpy.context.evaluated_depsgraph_get()
445 data = obj.evaluated_get(dgraph).data
446 data.calc_loop_triangles()
447 data.calc_normals_split()
448
449 mat_list = data.materials if len(data.materials) > 0 else [default_mat]
450 for material_id, mat in enumerate(mat_list):
451 mref = {}
452
453 sm = mdl_submesh()
454 sm.indice_start = header.indice_count
455 sm.vertex_start = header.vertex_count
456 sm.vertex_count = 0
457 sm.indice_count = 0
458 sm.material_id = emplace_material( mat )
459
460 for i in range(3):
461 sm.bbx[0][i] = 999999
462 sm.bbx[1][i] = -999999
463
464 boffa = {}
465
466 # Write the vertex / indice data
467 #
468 for tri_index, tri in enumerate(data.loop_triangles):
469 if tri.material_index != material_id:
470 continue
471
472 for j in range(3):
473 vert = data.vertices[tri.vertices[j]]
474 li = tri.loops[j]
475 vi = data.loops[li].vertex_index
476
477 co = vert.co
478 norm = data.loops[li].normal
479 uv = (0,0)
480 colour = (255,255,255,255)
481 groups = [0,0,0,0]
482 weights = [0,0,0,0]
483
484 if data.uv_layers:
485 uv = data.uv_layers.active.data[li].uv
486
487 if data.vertex_colors:
488 colour = data.vertex_colors.active.data[li].color
489 colour = (int(colour[0]*255.0),\
490 int(colour[1]*255.0),\
491 int(colour[2]*255.0),\
492 int(colour[3]*255.0))
493
494 # WEight groups
495 #
496 if armature_def:
497 src_groups = [_ for _ in data.vertices[vi].groups \
498 if obj.vertex_groups[_.group].name in \
499 armature_def['bones']]
500
501 weight_groups = sorted( src_groups, key = \
502 lambda a: a.weight, reverse=True )
503 tot = 0.0
504 for ml in range(3):
505 if len(weight_groups) > ml:
506 g = weight_groups[ml]
507 name = obj.vertex_groups[g.group].name
508 weight = g.weight
509
510 weights[ml] = weight
511 groups[ml] = armature_def['bones'].index(name)
512 tot += weight
513
514 if len(weight_groups) > 0:
515 inv_norm = (1.0/tot) * 65535.0
516 for ml in range(3):
517 weights[ml] = int( weights[ml] * inv_norm )
518 weights[ml] = min( weights[ml], 65535 )
519 weights[ml] = max( weights[ml], 0 )
520
521 TOLERENCE = 4
522 m = float(10**TOLERENCE)
523
524 key = (int(co[0]*m+0.5),\
525 int(co[1]*m+0.5),\
526 int(co[2]*m+0.5),\
527 int(norm[0]*m+0.5),\
528 int(norm[1]*m+0.5),\
529 int(norm[2]*m+0.5),\
530 int(uv[0]*m+0.5),\
531 int(uv[1]*m+0.5),\
532 colour[0],\
533 colour[1],\
534 colour[2],\
535 colour[3],\
536 weights[0],\
537 weights[1],\
538 weights[2],\
539 weights[3],\
540 groups[0],\
541 groups[1],\
542 groups[2],\
543 groups[3])
544
545 if key in boffa:
546 indice_buffer += [boffa[key]]
547 else:
548 index = c_uint32(sm.vertex_count)
549 sm.vertex_count += 1
550
551 boffa[key] = index
552 indice_buffer += [index]
553
554 v = mdl_vert()
555 v.co[0] = co[0]
556 v.co[1] = co[2]
557 v.co[2] = -co[1]
558 v.norm[0] = norm[0]
559 v.norm[1] = norm[2]
560 v.norm[2] = -norm[1]
561 v.uv[0] = uv[0]
562 v.uv[1] = uv[1]
563 v.colour[0] = colour[0]
564 v.colour[1] = colour[1]
565 v.colour[2] = colour[2]
566 v.colour[3] = colour[3]
567 v.weights[0] = weights[0]
568 v.weights[1] = weights[1]
569 v.weights[2] = weights[2]
570 v.weights[3] = weights[3]
571 v.groups[0] = groups[0]
572 v.groups[1] = groups[1]
573 v.groups[2] = groups[2]
574 v.groups[3] = groups[3]
575
576 vertex_buffer += [v]
577
578 for i in range(3):
579 sm.bbx[0][i] = min( sm.bbx[0][i], v.co[i] )
580 sm.bbx[1][i] = max( sm.bbx[1][i], v.co[i] )
581
582 sm.indice_count += 1
583
584 if sm.vertex_count == 0:
585 for j in range(2):
586 for i in range(3):
587 sm.bbx[j][i] = 0
588
589 submesh_buffer += [sm]
590 node.submesh_count += 1
591 header.submesh_count += 1
592 header.vertex_count += sm.vertex_count
593 header.indice_count += sm.indice_count
594
595 mesh_cache[obj.data.name] = node
596
597 # Process entity data
598 # ==================================================================
599 node.offset = entdata_length
600
601 if classtype != 'k_classtype_none':
602 disptype = classtype
603 else:
604 disptype = objt
605
606 s000 = F" [{uid: 3}/{header.node_count-1}]" + " |"*(depth-1)
607 s001 = F" L {obj.name}"
608 s002 = s000+s001
609 s003 = F"{disptype}"
610 s004 = F"{node.parent: 3}"
611 s005 = ""
612
613 if classtype == 'k_classtype_skin':
614 armature_def['obj'].data.pose_position = POSE_OR_REST_CACHE
615 s005 = F" [armature -> {armature_def['obj'].cv_data.uid}]"
616
617 scmp = F"{s002:<32} {s003:<22} {s004} {s005}"
618 print( scmp )
619
620 if classtype == 'k_classtype_INSTANCE' or \
621 classtype == 'k_classtype_BONE' or \
622 classtype == 'k_classtype_SKELETON' or \
623 classtype == 'k_classtype_SKIN':
624 print( "ERROR: user classtype cannot be _INSTANCE or _BONE" )
625 node.classtype = 0
626 node.offset = 0
627
628 elif classtype == 'k_classtype_skin':
629 node.classtype = 12
630
631 armature = armature_def['obj']
632 entdata_length += sizeof( classtype_skin )
633
634 skin = classtype_skin()
635 skin.skeleton = armature.cv_data.uid
636 entdata_buffer += [skin]
637
638 elif classtype == 'k_classtype_skeleton':
639 node.classtype = 11
640 entdata_length += sizeof( classtype_skeleton )
641 skeleton = classtype_skeleton()
642
643 armature_def = graph_lookup[obj]
644 armature = obj
645 bones = armature_def['bones']
646 skeleton.channels = len(bones)
647 skeleton.ik_count = armature_def["ik_count"]
648 skeleton.collider_count = armature_def["collider_count"]
649
650 if armature.animation_data:
651 previous_frame = bpy.context.scene.frame_current
652 previous_action = armature.animation_data.action
653
654 skeleton.anim_start = len(anim_buffer)
655 skeleton.anim_count = 0
656
657 for NLALayer in obj.animation_data.nla_tracks:
658 for NLAStrip in NLALayer.strips:
659 # Use action
660 for a in bpy.data.actions:
661 if a.name == NLAStrip.name:
662 armature.animation_data.action = a
663 break
664
665 anim_start = int(NLAStrip.action_frame_start)
666 anim_end = int(NLAStrip.action_frame_end)
667
668 # export strips
669 anim = mdl_animation()
670 anim.pstr_name = emplace_string( NLAStrip.action.name )
671 anim.rate = 30.0
672 anim.offset = animdata_length
673 anim.length = anim_end-anim_start
674
675 # Export the fucking keyframes
676 for frame in range(anim_start,anim_end):
677 bpy.context.scene.frame_set(frame)
678
679 for bone_name in bones:
680 for pb in armature.pose.bones:
681 if pb.name == bone_name:
682 rb = armature.data.bones[ bone_name ]
683
684 # relative bone matrix
685 if rb.parent is not None:
686 offset_mtx = rb.parent.matrix_local
687 offset_mtx = offset_mtx.inverted_safe() @ \
688 rb.matrix_local
689
690 inv_parent = pb.parent.matrix @ offset_mtx
691 inv_parent.invert_safe()
692 fpm = inv_parent @ pb.matrix
693 else:
694 bone_mtx = rb.matrix.to_4x4()
695 local_inv = rb.matrix_local.inverted_safe()
696 fpm = bone_mtx @ local_inv @ pb.matrix
697
698 loc, rot, sca = fpm.decompose()
699
700 # local position
701 final_pos = Vector(( loc[0], loc[2], -loc[1] ))
702
703 # rotation
704 lc_m = pb.matrix_channel.to_3x3()
705 if pb.parent is not None:
706 smtx = pb.parent.matrix_channel.to_3x3()
707 lc_m = smtx.inverted() @ lc_m
708 rq = lc_m.to_quaternion()
709
710 kf = mdl_keyframe()
711 kf.co[0] = final_pos[0]
712 kf.co[1] = final_pos[1]
713 kf.co[2] = final_pos[2]
714
715 kf.q[0] = rq[1]
716 kf.q[1] = rq[3]
717 kf.q[2] = -rq[2]
718 kf.q[3] = rq[0]
719
720 # scale
721 kf.s[0] = sca[0]
722 kf.s[1] = sca[2]
723 kf.s[2] = sca[1]
724
725 animdata_buffer += [kf]
726 animdata_length += sizeof(mdl_keyframe)
727 break
728
729 anim_buffer += [anim]
730 skeleton.anim_count += 1
731
732 s000 = F" [{uid: 3}/{header.node_count-1}]" + " |"*(depth-1)
733 print( F"{s000} | *anim: {NLAStrip.action.name}" )
734
735 bpy.context.scene.frame_set( previous_frame )
736 armature.animation_data.action = previous_action
737
738 entdata_buffer += [skeleton]
739
740 elif classtype == 'k_classtype_bone':
741 node.classtype = 10
742 entdata_length += sizeof( classtype_bone )
743
744 bone = classtype_bone()
745 bone.deform = node_def['deform']
746
747 if 'target' in node_def:
748 bone.ik_target = armature_def['bones'].index( node_def['target'] )
749 bone.ik_pole = armature_def['bones'].index( node_def['pole'] )
750 else:
751 bone.ik_target = 0
752 bone.ik_pole = 0
753
754 bone.collider = 1 if obj.cv_data.collider else 0
755 if obj.cv_data.collider:
756 bone.hitbox[0][0] = obj.cv_data.v0[0]
757 bone.hitbox[0][1] = obj.cv_data.v0[2]
758 bone.hitbox[0][2] = -obj.cv_data.v1[1]
759 bone.hitbox[1][0] = obj.cv_data.v1[0]
760 bone.hitbox[1][1] = obj.cv_data.v1[2]
761 bone.hitbox[1][2] = -obj.cv_data.v0[1]
762 else:
763 bone.hitbox[0][0] = 0.0
764 bone.hitbox[0][1] = 0.0
765 bone.hitbox[0][2] = 0.0
766 bone.hitbox[1][0] = 0.0
767 bone.hitbox[1][1] = 0.0
768 bone.hitbox[1][2] = 0.0
769
770 if obj.cv_data.con0:
771 bone.use_limits = 1
772 bone.angle_limits[0][0] = obj.cv_data.mins[0]
773 bone.angle_limits[0][1] = obj.cv_data.mins[2]
774 bone.angle_limits[0][2] = -obj.cv_data.maxs[1]
775 bone.angle_limits[1][0] = obj.cv_data.maxs[0]
776 bone.angle_limits[1][1] = obj.cv_data.maxs[2]
777 bone.angle_limits[1][2] = -obj.cv_data.mins[1]
778 else:
779 bone.use_limits = 0
780 bone.angle_limits[0][0] = 0.0
781 bone.angle_limits[0][1] = 0.0
782 bone.angle_limits[0][2] = 0.0
783 bone.angle_limits[1][0] = 0.0
784 bone.angle_limits[1][1] = 0.0
785 bone.angle_limits[1][2] = 0.0
786
787 bone.deform = node_def['deform']
788 entdata_buffer += [bone]
789
790 elif classtype == 'k_classtype_gate':
791 node.classtype = 1
792 entdata_length += sizeof( classtype_gate )
793
794 gate = classtype_gate()
795 gate.target = 0
796 if obj.cv_data.target != None:
797 gate.target = obj.cv_data.target.cv_data.uid
798
799 if obj.type == 'MESH':
800 gate.dims[0] = obj.data.cv_data.v0[0]
801 gate.dims[1] = obj.data.cv_data.v0[1]
802 gate.dims[2] = obj.data.cv_data.v0[2]
803 else:
804 gate.dims[0] = obj.cv_data.v0[0]
805 gate.dims[1] = obj.cv_data.v0[1]
806 gate.dims[2] = obj.cv_data.v0[2]
807
808 entdata_buffer += [gate]
809
810 elif classtype == 'k_classtype_block':
811 node.classtype = 2
812 entdata_length += sizeof( classtype_block )
813
814 source = obj.data.cv_data
815
816 block = classtype_block()
817 block.bbx[0][0] = source.v0[0]
818 block.bbx[0][1] = source.v0[2]
819 block.bbx[0][2] = -source.v1[1]
820
821 block.bbx[1][0] = source.v1[0]
822 block.bbx[1][1] = source.v1[2]
823 block.bbx[1][2] = -source.v0[1]
824 entdata_buffer += [block]
825
826 elif classtype == 'k_classtype_achievement_box':
827 node.classtype = 13
828
829 entdata_length += sizeof( classtype_achievement_box )
830 ach = classtype_achievement_box()
831 ach.pstr_name = emplace_string( obj.cv_data.strp )
832 ach.trigger = 0
833
834 if obj.cv_data.target != None:
835 ach.trigger = obj.cv_data.target.cv_data.uid
836
837 entdata_buffer += [ach]
838
839 elif classtype == 'k_classtype_audio':
840 node.classtype = 14
841
842 entdata_length += sizeof( classtype_audio )
843 aud = classtype_audio()
844 aud.pstr_file = emplace_string( obj.cv_data.strp )
845 aud.flags = obj.cv_data.intp
846 aud.volume = obj.cv_data.fltp
847
848 entdata_buffer += [aud]
849
850 elif classtype == 'k_classtype_spawn':
851 node.classtype = 3
852
853 elif classtype == 'k_classtype_water':
854 node.classtype = 4
855
856 elif classtype == 'k_classtype_car_path':
857 node.classtype = 5
858 entdata_length += sizeof( classtype_car_path )
859
860 pn = classtype_car_path()
861 pn.target = 0
862 pn.target1 = 0
863
864 if obj.cv_data.target != None:
865 pn.target = obj.cv_data.target.cv_data.uid
866 if obj.cv_data.target1 != None:
867 pn.target1 = obj.cv_data.target1.cv_data.uid
868
869 entdata_buffer += [pn]
870
871 elif obj.is_instancer:
872 target = obj.instance_collection
873
874 node.classtype = 6
875 entdata_length += sizeof( classtype_instance )
876
877 inst = classtype_instance()
878 inst.pstr_file = emplace_string( F"models/{target.name}.mdl" )
879 entdata_buffer += [inst]
880
881 elif classtype == 'k_classtype_capsule':
882 node.classtype = 7
883
884 elif classtype == 'k_classtype_route_node':
885 node.classtype = 8
886 entdata_length += sizeof( classtype_route_node )
887
888 rn = classtype_route_node()
889 if obj.cv_data.target != None:
890 rn.target = obj.cv_data.target.cv_data.uid
891 if obj.cv_data.target1 != None:
892 rn.target1 = obj.cv_data.target1.cv_data.uid
893
894 entdata_buffer += [rn]
895
896 elif classtype == 'k_classtype_route':
897 node.classtype = 9
898 entdata_length += sizeof( classtype_route )
899 r = classtype_route()
900 r.colour[0] = obj.cv_data.colour[0]
901 r.colour[1] = obj.cv_data.colour[1]
902 r.colour[2] = obj.cv_data.colour[2]
903
904 if obj.cv_data.target != None:
905 r.id_start = obj.cv_data.target.cv_data.uid
906
907 entdata_buffer += [r]
908
909 # classtype == 'k_classtype_none':
910 else:
911 node.classtype = 0
912 node.offset = 0
913
914 node_buffer += [node]
915
916 # Write data arrays
917 #
918 header.anim_count = len(anim_buffer)
919
920 print( "Writing data" )
921 fpos = sizeof(header)
922
923 print( F"Nodes: {header.node_count}" )
924 header.node_offset = fpos
925 fpos += sizeof(mdl_node)*header.node_count
926
927 print( F"Submeshes: {header.submesh_count}" )
928 header.submesh_offset = fpos
929 fpos += sizeof(mdl_submesh)*header.submesh_count
930
931 print( F"Materials: {header.material_count}" )
932 header.material_offset = fpos
933 fpos += sizeof(mdl_material)*header.material_count
934
935 print( F"Animation count: {header.anim_count}" )
936 header.anim_offset = fpos
937 fpos += sizeof(mdl_animation)*header.anim_count
938
939 print( F"Entdata length: {entdata_length}" )
940 header.entdata_offset = fpos
941 fpos += entdata_length
942
943 print( F"Vertex count: {header.vertex_count}" )
944 header.vertex_offset = fpos
945 fpos += sizeof(mdl_vert)*header.vertex_count
946
947 print( F"Indice count: {header.indice_count}" )
948 header.indice_offset = fpos
949 fpos += sizeof(c_uint32)*header.indice_count
950
951 print( F"Keyframe count: {animdata_length}" )
952 header.animdata_offset = fpos
953 fpos += animdata_length
954
955 print( F"Strings length: {len(strings_buffer)}" )
956 header.strings_offset = fpos
957 fpos += len(strings_buffer)
958
959 header.file_length = fpos
960
961 path = F"/home/harry/Documents/carve/models_src/{collection_name}.mdl"
962 fp = open( path, "wb" )
963
964 fp.write( bytearray( header ) )
965
966 for node in node_buffer:
967 fp.write( bytearray(node) )
968 for sm in submesh_buffer:
969 fp.write( bytearray(sm) )
970 for mat in material_buffer:
971 fp.write( bytearray(mat) )
972 for a in anim_buffer:
973 fp.write( bytearray(a) )
974 for ed in entdata_buffer:
975 fp.write( bytearray(ed) )
976 for v in vertex_buffer:
977 fp.write( bytearray(v) )
978 for i in indice_buffer:
979 fp.write( bytearray(i) )
980 for kf in animdata_buffer:
981 fp.write( bytearray(kf) )
982
983 fp.write( strings_buffer )
984 fp.close()
985
986 print( F"Completed {collection_name}.mdl" )
987
988 # Clicky clicky GUI
989 # ------------------------------------------------------------------------------
990
991 cv_view_draw_handler = None
992 cv_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
993
994 def cv_draw():
995 global cv_view_shader
996 cv_view_shader.bind()
997 gpu.state.depth_mask_set(False)
998 gpu.state.line_width_set(2.0)
999 gpu.state.face_culling_set('BACK')
1000 gpu.state.depth_test_set('LESS')
1001 gpu.state.blend_set('NONE')
1002
1003 verts = []
1004 colours = []
1005
1006 #def drawbezier(p0,h0,p1,h1,c0,c1):
1007 # nonlocal verts, colours
1008
1009 # verts += [p0]
1010 # verts += [h0]
1011 # colours += [(0.5,0.5,0.5,1.0),(0.5,0.5,0.5,1)]
1012 # verts += [p1]
1013 # verts += [h1]
1014 # colours += [(1.0,1.0,1,1),(1,1,1,1)]
1015 #
1016 # last = p0
1017 # for i in range(10):
1018 # t = (i+1)/10
1019 # a0 = 1-t
1020
1021 # tt = t*t
1022 # ttt = tt*t
1023 # p=ttt*p1+(3*tt-3*ttt)*h1+(3*ttt-6*tt+3*t)*h0+(3*tt-ttt-3*t+1)*p0
1024 # verts += [(last[0],last[1],last[2])]
1025 # verts += [(p[0],p[1],p[2])]
1026 # colours += [c0*a0+c1*(1-a0),c0*a0+c1*(1-a0)]
1027 # last = p
1028
1029 course_count = 0
1030
1031 def drawbhandle(obj, direction, colour):
1032 nonlocal verts, colours
1033 p0 = obj.location
1034 h0 = obj.matrix_world @ Vector((0,direction,0))
1035 verts += [p0]
1036 verts += [h0]
1037 colours += [colour,colour]
1038
1039 def drawbezier(p0,h0,p1,h1,c0,c1):
1040 nonlocal verts, colours
1041
1042 last = p0
1043 for i in range(10):
1044 t = (i+1)/10
1045 a0 = 1-t
1046
1047 tt = t*t
1048 ttt = tt*t
1049 p=ttt*p1+(3*tt-3*ttt)*h1+(3*ttt-6*tt+3*t)*h0+(3*tt-ttt-3*t+1)*p0
1050 verts += [(last[0],last[1],last[2])]
1051 verts += [(p[0],p[1],p[2])]
1052 colours += [c0*a0+c1*(1-a0),c0*a0+c1*(1-a0)]
1053 last = p
1054
1055 def drawsbpath(o0,o1,c0,c1,s0,s1):
1056 nonlocal course_count
1057
1058 offs = ((course_count % 2)*2-1) * course_count * 0.02
1059
1060 p0 = o0.matrix_world @ Vector((offs, 0,0))
1061 h0 = o0.matrix_world @ Vector((offs, s0,0))
1062 p1 = o1.matrix_world @ Vector((offs, 0,0))
1063 h1 = o1.matrix_world @ Vector((offs,-s1,0))
1064 drawbezier(p0,h0,p1,h1,c0,c1)
1065
1066 def drawbpath(o0,o1,c0,c1):
1067 drawsbpath(o0,o1,c0,c1,1.0,1.0)
1068
1069 def drawbline(p0,p1,c0,c1):
1070 nonlocal verts, colours
1071 verts += [p0,p1]
1072 colours += [c0,c1]
1073
1074 for obj in bpy.context.collection.objects:
1075 if obj.type == 'ARMATURE':
1076 for bone in obj.data.bones:
1077 if bone.cv_data.collider and obj.data.pose_position == 'REST':
1078 c = bone.head_local
1079 a = bone.cv_data.v0
1080 b = bone.cv_data.v1
1081
1082 vs = [None]*8
1083 vs[0]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+a[2]))
1084 vs[1]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+a[2]))
1085 vs[2]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+a[2]))
1086 vs[3]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+a[2]))
1087 vs[4]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+b[2]))
1088 vs[5]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+b[2]))
1089 vs[6]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+b[2]))
1090 vs[7]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+b[2]))
1091
1092 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
1093 (0,4),(1,5),(2,6),(3,7)]
1094
1095 for l in indices:
1096 v0 = vs[l[0]]
1097 v1 = vs[l[1]]
1098 verts += [(v0[0],v0[1],v0[2])]
1099 verts += [(v1[0],v1[1],v1[2])]
1100 colours += [(0.5,0.5,0.5,0.5),(0.5,0.5,0.5,0.5)]
1101
1102 center=obj.matrix_world@c
1103
1104 def _angle_lim( major, minor, amin, amax, colour ):
1105 nonlocal verts, colours
1106 f = 0.05
1107 ay = major*f
1108 ax = minor*f
1109
1110 for x in range(16):
1111 t0 = x/16
1112 t1 = (x+1)/16
1113 a0 = amin*(1.0-t0)+amax*t0
1114 a1 = amin*(1.0-t1)+amax*t1
1115
1116 p0 = c + major*f*math.cos(a0) + minor*f*math.sin(a0)
1117 p1 = c + major*f*math.cos(a1) + minor*f*math.sin(a1)
1118
1119 p0=obj.matrix_world @ p0
1120 p1=obj.matrix_world @ p1
1121 verts += [p0,p1]
1122 colours += [colour,colour]
1123
1124 if x == 0:
1125 verts += [p0,c]
1126 colours += [colour,colour]
1127 if x == 15:
1128 verts += [p1,c]
1129 colours += [colour,colour]
1130
1131 verts += [c+major*1.2*f,c+major*f*0.8]
1132 colours += [colour,colour]
1133
1134 if bone.cv_data.con0:
1135 _angle_lim( Vector((0,1,0)),Vector((0,0,1)), \
1136 bone.cv_data.mins[0], bone.cv_data.maxs[0], \
1137 (1,0,0,1))
1138 _angle_lim( Vector((0,0,1)),Vector((1,0,0)), \
1139 bone.cv_data.mins[1], bone.cv_data.maxs[1], \
1140 (0,1,0,1))
1141 _angle_lim( Vector((1,0,0)),Vector((0,1,0)), \
1142 bone.cv_data.mins[2], bone.cv_data.maxs[2], \
1143 (0,0,1,1))
1144
1145
1146 if obj.cv_data.classtype == 'k_classtype_gate':
1147 if obj.type == 'MESH':
1148 dims = obj.data.cv_data.v0
1149 else:
1150 dims = obj.cv_data.v0
1151
1152 vs = [None]*9
1153 c = Vector((0,0,dims[2]))
1154
1155 vs[0] = obj.matrix_world @ Vector((-dims[0],0.0,-dims[1]+dims[2]))
1156 vs[1] = obj.matrix_world @ Vector((-dims[0],0.0, dims[1]+dims[2]))
1157 vs[2] = obj.matrix_world @ Vector(( dims[0],0.0, dims[1]+dims[2]))
1158 vs[3] = obj.matrix_world @ Vector(( dims[0],0.0,-dims[1]+dims[2]))
1159 vs[4] = obj.matrix_world @ (c+Vector((-1,0,-2)))
1160 vs[5] = obj.matrix_world @ (c+Vector((-1,0, 2)))
1161 vs[6] = obj.matrix_world @ (c+Vector(( 1,0, 2)))
1162 vs[7] = obj.matrix_world @ (c+Vector((-1,0, 0)))
1163 vs[8] = obj.matrix_world @ (c+Vector(( 1,0, 0)))
1164
1165 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(7,8)]
1166
1167 for l in indices:
1168 v0 = vs[l[0]]
1169 v1 = vs[l[1]]
1170 verts += [(v0[0],v0[1],v0[2])]
1171 verts += [(v1[0],v1[1],v1[2])]
1172 colours += [(1,1,0,1),(1,1,0,1)]
1173
1174 sw = (0.4,0.4,0.4,0.2)
1175 if obj.cv_data.target != None:
1176 drawbline( obj.location, obj.cv_data.target.location, sw,sw )
1177
1178 elif obj.cv_data.classtype == 'k_classtype_route_node':
1179 sw = Vector((0.4,0.4,0.4,0.2))
1180 sw2 = Vector((1.5,0.2,0.2,0.0))
1181 if obj.cv_data.target != None:
1182 drawbpath( obj, obj.cv_data.target, sw, sw )
1183 if obj.cv_data.target1 != None:
1184 drawbpath( obj, obj.cv_data.target1, sw, sw )
1185
1186 drawbhandle( obj, 1.0, (0.8,0.8,0.8,1.0) )
1187 drawbhandle( obj, -1.0, (0.4,0.4,0.4,1.0) )
1188
1189 p1 = obj.location+ \
1190 obj.matrix_world.to_quaternion() @ Vector((0,0,-6+1.5))
1191 drawbline( obj.location, p1, sw,sw2 )
1192
1193 elif obj.cv_data.classtype == 'k_classtype_achievement_box':
1194 a = Vector((-1,-1,-1))
1195 b = Vector((1,1,1))
1196
1197 vs = [None]*8
1198 vs[0] = obj.matrix_world @ Vector((a[0], a[1], a[2]))
1199 vs[1] = obj.matrix_world @ Vector((a[0], b[1], a[2]))
1200 vs[2] = obj.matrix_world @ Vector((b[0], b[1], a[2]))
1201 vs[3] = obj.matrix_world @ Vector((b[0], a[1], a[2]))
1202 vs[4] = obj.matrix_world @ Vector((a[0], a[1], b[2]))
1203 vs[5] = obj.matrix_world @ Vector((a[0], b[1], b[2]))
1204 vs[6] = obj.matrix_world @ Vector((b[0], b[1], b[2]))
1205 vs[7] = obj.matrix_world @ Vector((b[0], a[1], b[2]))
1206
1207 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
1208 (0,4),(1,5),(2,6),(3,7)]
1209
1210 for l in indices:
1211 v0 = vs[l[0]]
1212 v1 = vs[l[1]]
1213 verts += [(v0[0],v0[1],v0[2])]
1214 verts += [(v1[0],v1[1],v1[2])]
1215 colours += [(0,1,0,1),(0,1,0,1)]
1216
1217 if obj.cv_data.target != None:
1218 vs = [None]*2
1219 vs[0] = obj.location
1220 vs[1] = obj.cv_data.target.location
1221 indices = [(0,1)]
1222 for l in indices:
1223 v0 = vs[l[0]]
1224 v1 = vs[l[1]]
1225 verts += [(v0[0],v0[1],v0[2])]
1226 verts += [(v1[0],v1[1],v1[2])]
1227 colours += [(0,1,1,1),(0,1,1,1)]
1228
1229
1230 elif obj.cv_data.classtype == 'k_classtype_block':
1231 a = obj.data.cv_data.v0
1232 b = obj.data.cv_data.v1
1233
1234 vs = [None]*8
1235 vs[0] = obj.matrix_world @ Vector((a[0], a[1], a[2]))
1236 vs[1] = obj.matrix_world @ Vector((a[0], b[1], a[2]))
1237 vs[2] = obj.matrix_world @ Vector((b[0], b[1], a[2]))
1238 vs[3] = obj.matrix_world @ Vector((b[0], a[1], a[2]))
1239 vs[4] = obj.matrix_world @ Vector((a[0], a[1], b[2]))
1240 vs[5] = obj.matrix_world @ Vector((a[0], b[1], b[2]))
1241 vs[6] = obj.matrix_world @ Vector((b[0], b[1], b[2]))
1242 vs[7] = obj.matrix_world @ Vector((b[0], a[1], b[2]))
1243
1244 indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
1245 (0,4),(1,5),(2,6),(3,7)]
1246
1247 for l in indices:
1248 v0 = vs[l[0]]
1249 v1 = vs[l[1]]
1250 verts += [(v0[0],v0[1],v0[2])]
1251 verts += [(v1[0],v1[1],v1[2])]
1252 colours += [(1,1,0,1),(1,1,0,1)]
1253
1254 elif obj.cv_data.classtype == 'k_classtype_capsule':
1255 h = obj.data.cv_data.v0[0]
1256 r = obj.data.cv_data.v0[1]
1257
1258 vs = [None]*10
1259 vs[0] = obj.matrix_world @ Vector((0.0,0.0, h*0.5 ))
1260 vs[1] = obj.matrix_world @ Vector((0.0,0.0,-h*0.5 ))
1261 vs[2] = obj.matrix_world @ Vector(( r,0.0, h*0.5-r))
1262 vs[3] = obj.matrix_world @ Vector(( -r,0.0, h*0.5-r))
1263 vs[4] = obj.matrix_world @ Vector(( r,0.0,-h*0.5+r))
1264 vs[5] = obj.matrix_world @ Vector(( -r,0.0,-h*0.5+r))
1265 vs[6] = obj.matrix_world @ Vector((0.0, r , h*0.5-r))
1266 vs[7] = obj.matrix_world @ Vector((0.0,-r , h*0.5-r))
1267 vs[8] = obj.matrix_world @ Vector((0.0, r ,-h*0.5+r))
1268 vs[9] = obj.matrix_world @ Vector((0.0,-r ,-h*0.5+r))
1269
1270 indices = [(0,1),(2,3),(4,5),(6,7),(8,9)]
1271
1272 for l in indices:
1273 v0 = vs[l[0]]
1274 v1 = vs[l[1]]
1275 verts += [(v0[0],v0[1],v0[2])]
1276 verts += [(v1[0],v1[1],v1[2])]
1277 colours += [(0.5,1,0,1),(0.5,1,0,1)]
1278
1279 elif obj.cv_data.classtype == 'k_classtype_spawn':
1280 vs = [None]*4
1281 vs[0] = obj.matrix_world @ Vector((0,0,0))
1282 vs[1] = obj.matrix_world @ Vector((0,2,0))
1283 vs[2] = obj.matrix_world @ Vector((0.5,1,0))
1284 vs[3] = obj.matrix_world @ Vector((-0.5,1,0))
1285 indices = [(0,1),(1,2),(1,3)]
1286 for l in indices:
1287 v0 = vs[l[0]]
1288 v1 = vs[l[1]]
1289 verts += [(v0[0],v0[1],v0[2])]
1290 verts += [(v1[0],v1[1],v1[2])]
1291 colours += [(0,1,1,1),(0,1,1,1)]
1292
1293 elif obj.cv_data.classtype == 'k_classtype_route':
1294 vs = [None]*2
1295 vs[0] = obj.location
1296 vs[1] = obj.cv_data.target.location
1297 indices = [(0,1)]
1298 for l in indices:
1299 v0 = vs[l[0]]
1300 v1 = vs[l[1]]
1301 verts += [(v0[0],v0[1],v0[2])]
1302 verts += [(v1[0],v1[1],v1[2])]
1303 colours += [(0,1,1,1),(0,1,1,1)]
1304
1305 stack = [None]*64
1306 stack_i = [0]*64
1307 stack[0] = obj.cv_data.target
1308 si = 1
1309 loop_complete = False
1310
1311 while si > 0:
1312 if stack_i[si-1] == 2:
1313 si -= 1
1314 continue
1315
1316 if si == 0: # Loop failed to complete
1317 break
1318
1319 node = stack[si-1]
1320
1321 targets = [None,None]
1322 targets[0] = node.cv_data.target
1323
1324 if node.cv_data.classtype == 'k_classtype_route_node':
1325 targets[1] = node.cv_data.target1
1326
1327 nextnode = targets[stack_i[si-1]]
1328 stack_i[si-1] += 1
1329
1330 if nextnode != None: # branch
1331 if nextnode == stack[0]: # Loop completed
1332 loop_complete = True
1333 break
1334
1335 valid=True
1336 for sj in range(si):
1337 if stack[sj] == nextnode: # invalidated path
1338 valid=False
1339 break
1340
1341 if valid:
1342 stack_i[si] = 0
1343 stack[si] = nextnode
1344 si += 1
1345 continue
1346
1347 if loop_complete:
1348 cc = Vector((obj.cv_data.colour[0],\
1349 obj.cv_data.colour[1],\
1350 obj.cv_data.colour[2],\
1351 1.0))
1352
1353 for sj in range(si):
1354 sk = (sj+1)%si
1355
1356 if stack[sj].cv_data.classtype == 'k_classtype_gate' and \
1357 stack[sk].cv_data.classtype == 'k_classtype_gate':
1358 dist = (stack[sj].location-stack[sk].location).magnitude
1359 drawsbpath( stack[sj], stack[sk], cc*0.4, cc, dist, dist )
1360
1361 else:
1362 drawbpath( stack[sj], stack[sk], cc, cc )
1363
1364 course_count += 1
1365
1366 elif obj.cv_data.classtype == 'k_classtype_car_path':
1367 v0 = obj.matrix_world.to_quaternion() @ Vector((0,1,0))
1368 c0 = Vector((v0.x*0.5+0.5, v0.y*0.5+0.5, 0.0, 1.0))
1369 drawbhandle( obj, 1.0, (0.9,0.9,0.9,1.0) )
1370
1371 if obj.cv_data.target != None:
1372 v1 = obj.cv_data.target.matrix_world.to_quaternion()@Vector((0,1,0))
1373 c1 = Vector((v1.x*0.5+0.5, v1.y*0.5+0.5, 0.0, 1.0))
1374
1375 drawbhandle( obj.cv_data.target, -1.0, (0.5,0.5,0.5,1.0) )
1376 drawbpath( obj, obj.cv_data.target, c0, c1 )
1377
1378 if obj.cv_data.target1 != None:
1379 v1 = obj.cv_data.target1.matrix_world.to_quaternion()@Vector((0,1,0))
1380 c1 = Vector((v1.x*0.5+0.5, v1.y*0.5+0.5, 0.0, 1.0))
1381
1382 drawbhandle( obj.cv_data.target1, -1.0, (0.5,0.5,0.5,1.0) )
1383 drawbpath( obj, obj.cv_data.target1, c0, c1 )
1384
1385 lines = batch_for_shader(\
1386 cv_view_shader, 'LINES', \
1387 { "pos":verts, "color":colours })
1388
1389 lines.draw( cv_view_shader )
1390
1391 def cv_poll_target(scene, obj):
1392 if obj == bpy.context.active_object:
1393 return False
1394 if obj.cv_data.classtype == 'k_classtype_none':
1395 return False
1396 return True
1397
1398 class CV_MESH_SETTINGS(bpy.types.PropertyGroup):
1399 v0: bpy.props.FloatVectorProperty(name="v0",size=3)
1400 v1: bpy.props.FloatVectorProperty(name="v1",size=3)
1401 v2: bpy.props.FloatVectorProperty(name="v2",size=3)
1402 v3: bpy.props.FloatVectorProperty(name="v3",size=3)
1403
1404 class CV_OBJ_SETTINGS(bpy.types.PropertyGroup):
1405 uid: bpy.props.IntProperty( name="" )
1406
1407 strp: bpy.props.StringProperty( name="strp" )
1408 intp: bpy.props.IntProperty( name="intp" )
1409 fltp: bpy.props.FloatProperty( name="fltp" )
1410
1411 target: bpy.props.PointerProperty( type=bpy.types.Object, name="target", \
1412 poll=cv_poll_target )
1413 target1: bpy.props.PointerProperty( type=bpy.types.Object, name="target1", \
1414 poll=cv_poll_target )
1415
1416 colour: bpy.props.FloatVectorProperty(name="colour",subtype='COLOR',\
1417 min=0.0,max=1.0)
1418
1419 classtype: bpy.props.EnumProperty(
1420 name="Format",
1421 items = [
1422 ('k_classtype_none', "k_classtype_none", "", 0),
1423 ('k_classtype_gate', "k_classtype_gate", "", 1),
1424 ('k_classtype_block', "k_classtype_block", "", 2),
1425 ('k_classtype_spawn', "k_classtype_spawn", "", 3),
1426 ('k_classtype_water', "k_classtype_water", "", 4),
1427 ('k_classtype_car_path', "k_classtype_car_path", "", 5),
1428 ('k_classtype_INSTANCE', "","", 6 ),
1429 ('k_classtype_capsule', "k_classtype_capsule", "", 7 ),
1430 ('k_classtype_route_node', "k_classtype_route_node", "", 8 ),
1431 ('k_classtype_route', "k_classtype_route", "", 9 ),
1432 ('k_classtype_bone',"k_classtype_bone","",10),
1433 ('k_classtype_SKELETON', "","", 11 ),
1434 ('k_classtype_SKIN',"","",12),
1435 ('k_classtype_achievement_box',"k_classtype_achievement_box","",13),
1436 ('k_classtype_audio',"k_classtype_audio","",14),
1437 ])
1438
1439 class CV_BONE_SETTINGS(bpy.types.PropertyGroup):
1440 collider: bpy.props.BoolProperty(name="Collider",default=False)
1441 v0: bpy.props.FloatVectorProperty(name="v0",size=3)
1442 v1: bpy.props.FloatVectorProperty(name="v1",size=3)
1443
1444 con0: bpy.props.BoolProperty(name="Constriant 0",default=False)
1445 mins: bpy.props.FloatVectorProperty(name="mins",size=3)
1446 maxs: bpy.props.FloatVectorProperty(name="maxs",size=3)
1447
1448 class CV_BONE_PANEL(bpy.types.Panel):
1449 bl_label="Bone Config"
1450 bl_idname="SCENE_PT_cv_bone"
1451 bl_space_type='PROPERTIES'
1452 bl_region_type='WINDOW'
1453 bl_context='bone'
1454
1455 def draw(_,context):
1456 active_object = context.active_object
1457 if active_object == None: return
1458
1459 bone = active_object.data.bones.active
1460 if bone == None: return
1461
1462 _.layout.prop( bone.cv_data, "collider" )
1463 _.layout.prop( bone.cv_data, "v0" )
1464 _.layout.prop( bone.cv_data, "v1" )
1465
1466 _.layout.label( text="Angle Limits" )
1467 _.layout.prop( bone.cv_data, "con0" )
1468 _.layout.prop( bone.cv_data, "mins" )
1469 _.layout.prop( bone.cv_data, "maxs" )
1470
1471 class CV_SCENE_SETTINGS(bpy.types.PropertyGroup):
1472 use_hidden: bpy.props.BoolProperty( name="use hidden", default=False )
1473
1474 class CV_OBJ_PANEL(bpy.types.Panel):
1475 bl_label="Entity Config"
1476 bl_idname="SCENE_PT_cv_entity"
1477 bl_space_type='PROPERTIES'
1478 bl_region_type='WINDOW'
1479 bl_context="object"
1480
1481 def draw(_,context):
1482 active_object = bpy.context.active_object
1483 if active_object == None: return
1484 _.layout.prop( active_object.cv_data, "classtype" )
1485
1486 if active_object.cv_data.classtype == 'k_classtype_gate':
1487 _.layout.prop( active_object.cv_data, "target" )
1488
1489 mesh = active_object.data
1490 _.layout.label( text=F"(i) Data is stored in {mesh.name}" )
1491 _.layout.prop( mesh.cv_data, "v0" )
1492
1493 elif active_object.cv_data.classtype == 'k_classtype_car_path' or \
1494 active_object.cv_data.classtype == 'k_classtype_route_node':
1495 _.layout.prop( active_object.cv_data, "target" )
1496 _.layout.prop( active_object.cv_data, "target1" )
1497
1498 elif active_object.cv_data.classtype == 'k_classtype_route':
1499 _.layout.prop( active_object.cv_data, "target" )
1500 _.layout.prop( active_object.cv_data, "colour" )
1501
1502 elif active_object.cv_data.classtype == 'k_classtype_block':
1503 mesh = active_object.data
1504
1505 _.layout.label( text=F"(i) Data is stored in {mesh.name}" )
1506 _.layout.prop( mesh.cv_data, "v0" )
1507 _.layout.prop( mesh.cv_data, "v1" )
1508 _.layout.prop( mesh.cv_data, "v2" )
1509 _.layout.prop( mesh.cv_data, "v3" )
1510 elif active_object.cv_data.classtype == 'k_classtype_capsule':
1511 mesh = active_object.data
1512 _.layout.label( text=F"(i) Data is stored in {mesh.name}" )
1513 _.layout.prop( mesh.cv_data, "v0" )
1514 elif active_object.cv_data.classtype == 'k_classtype_achievement_box':
1515 _.layout.prop( active_object.cv_data, "strp" )
1516 _.layout.prop( active_object.cv_data, "target" )
1517 elif active_object.cv_data.classtype == 'k_classtype_audio':
1518 _.layout.prop( active_object.cv_data, "strp" )
1519 _.layout.prop( active_object.cv_data, "intp" )
1520 _.layout.prop( active_object.cv_data, "fltp" )
1521
1522 class CV_INTERFACE(bpy.types.Panel):
1523 bl_idname = "VIEW3D_PT_carve"
1524 bl_label = "Carve"
1525 bl_space_type = 'VIEW_3D'
1526 bl_region_type = 'UI'
1527 bl_category = "Carve"
1528
1529 def draw(_, context):
1530 layout = _.layout
1531 layout.prop( context.scene.cv_data, "use_hidden")
1532 layout.operator( "carve.compile_all" )
1533
1534 def test_compile():
1535 view_layer = bpy.context.view_layer
1536 for col in view_layer.layer_collection.children["export"].children:
1537 if not col.hide_viewport or bpy.context.scene.cv_data.use_hidden:
1538 write_model( col.name )
1539
1540 class CV_COMPILE(bpy.types.Operator):
1541 bl_idname="carve.compile_all"
1542 bl_label="Compile All"
1543
1544 def execute(_,context):
1545 test_compile()
1546 #cProfile.runctx("test_compile()",globals(),locals(),sort=1)
1547 #for col in bpy.data.collections["export"].children:
1548 # write_model( col.name )
1549
1550 return {'FINISHED'}
1551
1552 classes = [CV_OBJ_SETTINGS,CV_OBJ_PANEL,CV_COMPILE,CV_INTERFACE,\
1553 CV_MESH_SETTINGS, CV_SCENE_SETTINGS, CV_BONE_SETTINGS,\
1554 CV_BONE_PANEL]
1555
1556 def register():
1557 global cv_view_draw_handler
1558
1559 for c in classes:
1560 bpy.utils.register_class(c)
1561
1562 bpy.types.Object.cv_data = bpy.props.PointerProperty(type=CV_OBJ_SETTINGS)
1563 bpy.types.Mesh.cv_data = bpy.props.PointerProperty(type=CV_MESH_SETTINGS)
1564 bpy.types.Scene.cv_data = bpy.props.PointerProperty(type=CV_SCENE_SETTINGS)
1565 bpy.types.Bone.cv_data = bpy.props.PointerProperty(type=CV_BONE_SETTINGS)
1566
1567 cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
1568 cv_draw,(),'WINDOW','POST_VIEW')
1569
1570 def unregister():
1571 global cv_view_draw_handler
1572
1573 for c in classes:
1574 bpy.utils.unregister_class(c)
1575
1576 bpy.types.SpaceView3D.draw_handler_remove(cv_view_draw_handler,'WINDOW')