Refactor el big #2
[convexer.git] / __init__.py
1 # Copyright (C) 2022 Harry Godden (hgn)
2
3 bl_info = {
4 "name":"Convexer",
5 "author": "Harry Godden (hgn)",
6 "version": (0,1),
7 "blender":(3,1,0),
8 "location":"Export",
9 "descriptin":"",
10 "warning":"",
11 "wiki_url":"",
12 "category":"Import/Export",
13 }
14
15 print( "Convexer reload" )
16
17 #from mathutils import *
18 import bpy, gpu, math, os, time, mathutils
19 from ctypes import *
20 from gpu_extras.batch import batch_for_shader
21 from bpy.app.handlers import persistent
22
23 # Globals and tweaks
24 vmt_param_dynamic_class = None
25
26 # libcxr interface (TODO: We can probably automate this)
27 # ======================================================
28 libcxr = None
29 libc_dlclose = None
30 libc_dlclose = cdll.LoadLibrary(None).dlclose
31 libc_dlclose.argtypes = [c_void_p]
32
33 c_libcxr_log_callback = None
34 c_libcxr_line_callback = None
35
36 libcxr_write_test_data = None
37 libcxr_context_reset = None
38 libcxr_set_offset = None
39 libcxr_set_scale_factor = None
40
41 # Vdf writing interface
42 libcxr_vdf_open = None
43 libcxr_vdf_close = None
44 libcxr_vdf_put = None
45 libcxr_vdf_node = None
46 libcxr_vdf_edon = None
47 libcxr_vdf_kv = None
48
49 # libnbvtf interface
50 # ==================
51 libnbvtf = None
52 libnbvtf_convert = None
53
54 # NBVTF constants
55 # ===============
56
57 NBVTF_IMAGE_FORMAT_RGBA8888 = 0
58 NBVTF_IMAGE_FORMAT_RGB888 = 2
59 NBVTF_IMAGE_FORMAT_DXT1 = 13
60 NBVTF_IMAGE_FORMAT_DXT5 = 15
61 NBVTF_TEXTUREFLAGS_CLAMPS = 0x00000004
62 NBVTF_TEXTUREFLAGS_CLAMPT = 0x00000008
63 NBVTF_TEXTUREFLAGS_NORMAL = 0x00000080
64 NBVTF_TEXTUREFLAGS_NOMIP = 0x00000100
65 NBVTF_TEXTUREFLAGS_NOLOD = 0x00000200
66
67 # Wrapper for vdf functions to allow: with o = vdf_structure ...
68 class vdf_structure():
69 def __init__(_,path):
70 _.path = path
71 def __enter__(_):
72 _.fp = libcxr_vdf_open( _.path.encode('utf-8') )
73 if _.fp == None:
74 print( F"Could not open file {_.path}" )
75 return None
76 return _
77 def __exit__(_,type,value,traceback):
78 if _.fp != None:
79 libcxr_vdf_close(_.fp)
80
81 def put(_,s):
82 libcxr_vdf_put(_.fp, s.encode('utf-8') )
83 def node(_,name):
84 libcxr_vdf_node(_.fp, name.encode('utf-8') )
85 def edon(_):
86 libcxr_vdf_edon(_.fp)
87 def kv(_,k,v):
88 libcxr_vdf_kv(_.fp, k.encode('utf-8'), v.encode('utf-8'))
89
90 class cxr_object_context():
91 def __init__(_,scale,offset_z):
92 _.scale=scale
93 _.offset_z=offset_z
94
95 libcxr_convert_mesh_to_vmf = None
96
97 debug_gpu_lines = None
98 debug_gpu_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
99 debug_draw_handler = None
100
101 class cxr_settings(Structure):
102 _fields_ = [("debug",c_int32),
103 ("lightmap_scale",c_int32),
104 ("light_scale",c_double)]
105
106 class cxr_input_loop(Structure):
107 _fields_ = [("index",c_int32),
108 ("edge_index",c_int32),
109 ("uv",c_double * 2)]
110
111 class cxr_polygon(Structure):
112 _fields_ = [("loop_start",c_int32),
113 ("loop_total",c_int32),
114 ("normal",c_double * 3),
115 ("center",c_double * 3),
116 ("material_id",c_int32)]
117
118 class cxr_edge(Structure):
119 _fields_ = [("i0",c_int32),
120 ("i1",c_int32),
121 ("freestyle",c_int32)]
122
123 class cxr_material(Structure):
124 _fields_ = [("res",c_int32 * 2),
125 ("vmt_path",c_char_p)]
126
127 class cxr_input_mesh(Structure):
128 _fields_ = [("vertices",POINTER(c_double * 3)),
129 ("edges",POINTER(cxr_edge)),
130 ("loops",POINTER(cxr_input_loop)),
131 ("polys",POINTER(cxr_polygon)),
132 ("materials",POINTER(cxr_material)),
133
134 ("poly_count",c_int32),
135 ("vertex_count",c_int32),
136 ("edge_count",c_int32),
137 ("loop_count",c_int32),
138 ("material_count",c_int32)]
139
140 class cxr_output_mesh(Structure):
141 _fields_ = []
142
143 def libcxr_log_callback(logStr):
144 print( F"{logStr.decode('utf-8')}",end='' )
145
146 debug_lines_positions = None
147 debug_lines_colours = None
148
149 def libcxr_reset_debug_lines():
150 global debug_lines_positions
151 global debug_lines_colours
152
153 debug_lines_positions = []
154 debug_lines_colours = []
155
156 def libcxr_batch_debug_lines():
157 global debug_lines_positions
158 global debug_lines_colours
159 global debug_gpu_lines
160 global debug_gpu_shader
161
162 debug_gpu_lines = batch_for_shader(\
163 debug_gpu_shader, 'LINES',\
164 { "pos": debug_lines_positions, "color": debug_lines_colours })
165
166 @persistent
167 def cxr_on_load(dummy):
168 libcxr_reset_debug_lines()
169 libcxr_batch_debug_lines()
170
171 @persistent
172 def cxr_dgraph_update(scene,dgraph):
173 return
174 print( F"Hallo {time.time()}" )
175
176 def libcxr_line_callback(p0,p1,colour):
177 global debug_lines_positions
178 global debug_lines_colours
179 debug_lines_positions += [(p0[0],p0[1],p0[2])]
180 debug_lines_positions += [(p1[0],p1[1],p1[2])]
181 debug_lines_colours += [(colour[0],colour[1],colour[2],colour[3])]
182 debug_lines_colours += [(colour[0],colour[1],colour[2],colour[3])]
183
184 def cxr_draw():
185 global debug_gpu_lines
186 global debug_gpu_shader
187
188 gpu.state.blend_set('ALPHA')
189 if debug_gpu_lines != None:
190 debug_gpu_lines.draw(debug_gpu_shader)
191
192 class CXR_RELOAD(bpy.types.Operator):
193 bl_idname="convexer.reload"
194 bl_label="Reload convexer"
195
196 def execute(_,context):
197 global libcxr, libnbvtf, libnbvtf_convert
198
199 # Load vtf library
200 libnbvtf = cdll.LoadLibrary( os.path.dirname(__file__)+'/libnbvtf.so')
201 libnbvtf_convert = libnbvtf.nbvtf_convert
202 libnbvtf_convert.argtypes = [\
203 c_char_p, \
204 c_int32, \
205 c_int32, \
206 c_int32, \
207 c_int32, \
208 c_uint32, \
209 c_char_p ]
210 libnbvtf_convert.restype = c_int32
211
212 if libcxr != None:
213 _handle = libcxr._handle
214
215 # TODO: Find a propper way to do this
216 for i in range(10): libc_dlclose( _handle )
217 del libcxr
218
219 libcxr = None
220 libcxr = cdll.LoadLibrary( os.path.dirname(__file__)+'/libcxr.so')
221
222 build_time = c_char_p.in_dll(libcxr,'cxr_build_time')
223 print( F"libcxr build time: {build_time.value}" )
224
225 # Public API
226 global libcxr_write_test_data, libcxr_convert_mesh_to_vmf
227
228 libcxr_write_test_data = libcxr.cxr_write_test_data
229 libcxr_write_test_data.argtypes = [\
230 POINTER(cxr_input_mesh)
231 ]
232 libcxr_write_test_data.restype = c_int32
233
234 libcxr_convert_mesh_to_vmf = libcxr.cxr_convert_mesh_to_vmf
235 libcxr_convert_mesh_to_vmf.argtypes = [\
236 POINTER(cxr_input_mesh),\
237 c_void_p,\
238 ]
239 libcxr_convert_mesh_to_vmf.restype = c_int32
240
241 global libcxr_context_reset, libcxr_set_offset,\
242 libcxr_set_scale_factor
243 libcxr_context_reset = libcxr.cxr_context_reset
244
245 libcxr_set_offset = libcxr.cxr_set_offset
246 libcxr_set_offset.argtypes = [ c_double ]
247
248 libcxr_set_scale_factor = libcxr.cxr_set_scale_factor
249 libcxr_set_scale_factor.argtypes = [ c_double ]
250
251 # VDF
252 global libcxr_vdf_open, \
253 libcxr_vdf_close, \
254 libcxr_vdf_put, \
255 libcxr_vdf_node, \
256 libcxr_vdf_edon, \
257 libcxr_vdf_kv
258
259 libcxr_vdf_open = libcxr.cxr_vdf_open
260 libcxr_vdf_open.argtypes = [ c_char_p ]
261 libcxr_vdf_open.restype = c_void_p
262
263 libcxr_vdf_close = libcxr.cxr_vdf_close
264 libcxr_vdf_close.argtypes = [ c_void_p ]
265
266 libcxr_vdf_put = libcxr.cxr_vdf_put
267 libcxr_vdf_put.argtypes = [ c_void_p, c_char_p ]
268
269 libcxr_vdf_node = libcxr.cxr_vdf_node
270 libcxr_vdf_node.argtypes = [ c_void_p, c_char_p ]
271
272 libcxr_vdf_edon = libcxr.cxr_vdf_edon
273 libcxr_vdf_edon.argtypes = [ c_void_p ]
274
275 libcxr_vdf_kv = libcxr.cxr_vdf_kv
276 libcxr_vdf_kv.argtypes = [ c_void_p, c_char_p, c_char_p ]
277
278 # Callbacks
279 global c_libcxr_log_callback
280 global c_libcxr_line_callback
281
282 LOG_FUNCTION_TYPE = CFUNCTYPE(None,c_char_p)
283 c_libcxr_log_callback = LOG_FUNCTION_TYPE(libcxr_log_callback)
284 libcxr.cxr_set_log_function(cast(c_libcxr_log_callback,c_void_p))
285
286 LINE_FUNCTION_TYPE = CFUNCTYPE(None,\
287 POINTER(c_double),POINTER(c_double),POINTER(c_double))
288 c_libcxr_line_callback = LINE_FUNCTION_TYPE(libcxr_line_callback)
289 libcxr.cxr_set_line_function(cast(c_libcxr_line_callback,c_void_p))
290
291 return {'FINISHED'}
292
293 def libcxr_use():
294 global libcxr
295
296 if libcxr == None:
297 bpy.ops.convexer.reload()
298
299 def _bool_int(b):
300 return 1 if b else 0
301
302 scene = bpy.context.scene
303 cxr = scene.cxr_data
304
305 settings=cxr_settings()
306 settings.debug=_bool_int(cxr.debug)
307
308 settings.lightmap_scale=cxr.lightmap_scale
309 settings.light_scale=cxr.light_scale
310
311 libcxr.cxr_settings_update(pointer(settings))
312
313 def to_aeiou( v ):
314 ret = ""
315 if v == 0:
316 return "z"
317 dig = []
318 while v:
319 dig.append( int( v % 5 ) )
320 v //= 5
321 for d in dig[::-1]:
322 ret += [ 'a','e','i','o','u' ][d]
323 return ret
324
325 def asset_uid(asset):
326 if isinstance(asset,str):
327 return asset
328 name = to_aeiou(asset.cxr_data.asset_id)
329 if bpy.context.scene.cxr_data.include_names:
330 name += asset.name.replace('.','_')
331 return name
332
333 # -> <project_name>/<asset_name>
334 def asset_name(asset):
335 return F"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
336
337 # -> <subdir>/<project_name>/<asset_name>
338 def asset_path(subdir, asset):
339 return F"{subdir}/{asset_name(asset_uid(asset))}"
340
341 # -> <csgo>/<subdir>/<project_name>/<asset_name>
342 def asset_full_path(sdir,asset):
343 return F"{bpy.context.scene.cxr_data.subdir}/"+\
344 F"{asset_path(sdir,asset_uid(asset))}"
345
346 # view_layer.update() doesnt seem to work,
347 # tag_redraw() seems to have broken
348 # therefore, change a property
349 def scene_redraw():
350 ob = bpy.context.scene.objects[0]
351 ob.hide_render = ob.hide_render
352
353 # the 'real' way to refresh the scene
354 #for area in bpy.context.window.screen.areas:
355 # if area.type == 'view_3d':
356 # area.tag_redraw()
357
358 # The default shader is the first entry
359 #
360 cxr_shaders = {
361 "LightMappedGeneric":
362 {
363 "name": "Light Mapped",
364 "id": 0
365 },
366 "VertexLitGeneric":
367 {
368 "name": "Vertex Lit",
369 "id": 1
370 },
371 "UnlitGeneric":
372 {
373 "name": "Unlit",
374 "id": 2
375 },
376 "Builtin":
377 {
378 "name": "Builtin",
379 "id": 3
380 }
381 }
382
383 def material_tex_image(v):
384 return {\
385 "ShaderNodeTexImage":
386 {
387 "image": F"${v}"
388 }
389 }
390
391 cxr_graph_mapping = {
392 "ShaderNodeBsdfPrincipled":
393 {
394 "Base Color":
395 {
396 "ShaderNodeMixRGB":
397 {
398 "Color1": material_tex_image("basetexture"),
399 "Color2": material_tex_image("decaltexture")
400 },
401 "ShaderNodeTexImage":
402 {
403 "image":"$basetexture"
404 },
405 "default":
406 [("VertexLitGeneric","$color2"),\
407 ("UnlitGeneric","$color2"),\
408 ("LightMappedGeneric","$color")]
409 },
410 "Normal":
411 {
412 "ShaderNodeNormalMap":
413 {
414 "Color": material_tex_image("bumpmap")
415 }
416 }
417 }
418 }
419
420 cxr_shader_params = {
421 "Textures":
422 {
423 "type": "ui",
424 "shaders": ("UnlitGeneric","VertexLitGeneric","LightMappedGeneric"),
425
426 "$basetexture":
427 {
428 "name": "Base Texture",
429 "type": "intrinsic",
430 "default": None
431 },
432 "$decaltexture":
433 {
434 "name": "Decal Texture",
435 "type": "intrinsic",
436 "default": None,
437
438 "$decalblendmode":
439 {
440 "name": "Blend Mode",
441 "type": "enum",
442 "items": [
443 ('0',"AlphaOver","Default",'',0),
444 ('1',"Multiply","",'',1),
445 ('2',"Modulate","",'',2),
446 ('3',"Additive","",'',3)
447 ],
448 "default": 0,
449 "always": True
450 }
451 },
452 "$bumpmap":
453 {
454 "name": "Normal Map",
455 "type": "intrinsic",
456 "flags": NBVTF_TEXTUREFLAGS_NORMAL,
457 "default": None
458 }
459 },
460 "$color":
461 {
462 "name": "Color",
463 "type": "intrinsic",
464 "default": None,
465 "exponent": 2.2
466 },
467 "$color2":
468 {
469 "name": "Color2",
470 "type": "intrinsic",
471 "default": None,
472 "exponent": 2.2
473 },
474 "Lighting":
475 {
476 "type": "ui",
477 "shaders": ("VertexLitGeneric", "LightMappedGeneric"),
478
479 "$phong":
480 {
481 "name": "Phong",
482 "type": "bool",
483 "default": False,
484
485 "$phongexponent":
486 {
487 "name": "Exponent",
488 "type": "float",
489 "default": 5.0
490 },
491 "$phongboost":
492 {
493 "name": "Boost",
494 "type": "float",
495 "default": 1.0
496 },
497 "$phongfresnelranges":
498 {
499 "name": "Fresnel Ranges",
500 "type": "vector",
501 "default":(1.0,1.0,1.0)
502 }
503 },
504 "$envmap":
505 {
506 "name": "Cubemap",
507 "type": "string",
508 "default": "",
509
510 "$envmaptint":
511 {
512 "name": "Tint",
513 "type": "vector",
514 "subtype": 'COLOR',
515 "default": (1.0,1.0,1.0)
516 },
517 "$envmaplightscale":
518 {
519 "name": "Light Scale",
520 "type": "float",
521 "default": 0.0
522 },
523 "$envmaplightscaleminmax":
524 {
525 "name": "Min/Max",
526 "type": "vector",
527 "default": (0.0,1.0)
528 }
529 }
530 },
531 "Transparency":
532 {
533 "type": "ui",
534 "shaders": ("UnlitGeneric","VertexLitGeneric","LightMappedGeneric"),
535
536 "$translucent":
537 {
538 "name": "Translucent",
539 "type": "bool",
540 "default": False
541 },
542 "$alphatest":
543 {
544 "name": "Alpha Test",
545 "type": "bool",
546 "default": False,
547
548 "$alphatestreference":
549 {
550 "name": "Step",
551 "type": "float",
552 "default": 0.5
553 }
554 },
555 "$nocull":
556 {
557 "name": "No Cull",
558 "type": "bool",
559 "default": False
560 }
561 }
562 }
563
564 def ent_get_origin(obj,context):
565 return obj.location * context.scale
566
567 def ent_get_angles(obj,context):
568 euler = [ a*57.295779513 for a in obj.rotation_euler ]
569 angle = [0,0,0]
570 angle[0] = euler[1]
571 angle[1] = euler[2]
572 angle[2] = euler[0]
573 return angle
574
575 def ent_baseclass(classes, other):
576 base = other.copy()
577 for x in classes:
578 base.update(x.copy())
579 return base
580
581 ent_origin = { "origin": ent_get_origin }
582 ent_angles = { "angles": ent_get_angles }
583 ent_transform = ent_baseclass( [ent_origin], ent_angles )
584
585 def ent_lights(obj,context):
586 kvs = ent_baseclass([ent_origin],\
587 {
588 "_distance": (0.0 if obj.data.cxr_data.realtime else -1.0),
589 "_light": [int(pow(obj.data.color[i],1.0/2.2)*255.0) for i in range(3)] +\
590 [int(obj.data.energy * bpy.context.scene.cxr_data.light_scale)],
591 "_lightHDR": '-1 -1 -1 1',
592 "_lightscaleHDR": 1
593 })
594
595 if obj.data.type == 'SPOT':
596 kvs['_cone'] = obj.data.spot_size*(57.295779513/2.0)
597 kvs['_inner_cone'] = (1.0-obj.data.spot_blend)*kvs['_cone']
598
599 # Blenders spotlights are -z forward
600 # Source is +x, however, it seems to use a completely different system.
601 # Since we dont care about roll for spotlights, we just take the
602 # pitch and yaw via trig
603
604 _,mtx_rot,_ = obj.matrix_world.decompose()
605 fwd = mtx_rot @ mathutils.Vector((0,0,-1))
606
607 kvs['pitch'] = math.asin(fwd[2]) * 57.295779513
608 kvs['angles'] = [ 0.0, math.atan2(fwd[1],fwd[0]) * 57.295779513, 0.0 ]
609 kvs['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
610 # Really bad...
611 #
612 # Blender's default has a much more 'nice'
613 # look.
614 kvs['_linear_attn'] = 1.0
615
616 elif obj.data.type == 'POINT':
617 kvs['_quadratic_attn'] = 1.0
618 kvs['_linear_attn'] = 0.0
619
620 elif obj.data.type == 'SUN':
621 pass # TODO
622
623 return kvs
624
625 def ent_cubemap(obj,context):
626 return ent_baseclass([ent_origin],\
627 {"cubemapsize": obj.data.cxr_data.size})
628
629 cxr_entities = {
630 "info_player_counterterrorist":
631 {
632 "gizmo": [],
633 "allow": ('EMPTY',),
634 "keyvalues": ent_baseclass([ent_transform],\
635 {
636 "priority": {"type": "int", "default": 0 },
637 "enabled": {"type": "int", "default": 1 },
638 })
639 },
640 "info_player_terrorist":
641 {
642 "gizmo": [],
643 "allow": ('EMPTY',),
644 "keyvalues": ent_baseclass([ent_transform],\
645 {
646 "priority": {"type": "int", "default": 0 },
647 "enabled": {"type": "int", "default": 1 },
648 })
649 },
650 "light": { "keyvalues": ent_lights },
651 "light_spot": { "keyvalues": ent_lights },
652 # SUN
653 "env_cubemap": { "keyvalues": ent_cubemap },
654
655 # Brush entites
656 "func_buyzone":
657 {
658 "allow": ('MESH',),
659 "keyvalues":
660 {
661 "TeamNum": {"type": "int", "default": 0 }
662 }
663 }
664 }
665
666 def cxr_intrinsic_classname(obj):
667 if obj.type == 'LIGHT':
668 return {
669 'SPOT': "light_spot",
670 'POINT': "light",
671 'SUN': "light_directional" }[ obj.data.type ]
672
673 elif obj.type == 'LIGHT_PROBE':
674 return "env_cubemap"
675 elif obj.type == 'EMPTY':
676 if obj.is_instancer:
677 return "prop_static"
678
679 return None
680
681 def cxr_custom_class(obj):
682 if obj.type == 'MESH': custom_class = obj.cxr_data.brushclass
683 else: custom_class = obj.cxr_data.classname
684
685 return custom_class
686
687 def cxr_classname(obj):
688 intr = cxr_intrinsic_classname(obj)
689 if intr != None: return intr
690
691 custom_class = cxr_custom_class(obj)
692 if custom_class != 'NONE':
693 return custom_class
694
695 return None
696
697 # Returns array of:
698 # intinsic: (k, False, value)
699 # property: (k, True, value or default)
700 #
701 # Error: None
702 #
703 def cxr_entity_keyvalues(obj,context,classname):
704 if classname not in cxr_entities: return None
705
706 result = []
707
708 entdef = cxr_entities[classname]
709 kvs = entdef['keyvalues']
710
711 if callable(kvs): kvs = kvs(obj, context)
712
713 for k in kvs:
714 kv = kvs[k]
715 value = kv
716 isprop = False
717
718 if isinstance(kv,dict):
719 isprop = True
720 value = obj[ F"cxrkv_{k}" ]
721 else:
722 if callable(kv):
723 value = kv(obj,context)
724
725 if isinstance(value,mathutils.Vector):
726 value = [_ for _ in value]
727
728 result += [(k, isprop, value)]
729
730 return result
731
732 def material_info(mat):
733 info = {}
734 info['res'] = (512,512)
735 info['name'] = 'tools/toolsnodraw'
736
737 if mat == None or mat.use_nodes == False:
738 return info
739
740 # Builtin shader
741 if mat.cxr_data.shader == 'Builtin':
742 info['name'] = mat.name
743 return info
744
745 if not hasattr(material_info,'references'):
746 material_info.references = set()
747
748 # Custom material
749 material_info.references.add(mat)
750 info['name'] = asset_name(mat)
751
752 # Using the cxr_graph_mapping as a reference, go through the shader
753 # graph and gather all $props from it.
754 #
755 def _graph_read( node_def, node=None, depth=0 ):
756 nonlocal mat
757 nonlocal info
758
759 def _variant_apply( val ):
760 nonlocal mat
761
762 if isinstance( val, str ):
763 return val
764 else:
765 for shader_variant in val:
766 if shader_variant[0] == mat.cxr_data.shader:
767 return shader_variant[1]
768
769 # Find rootnodes
770 if node == None:
771 _graph_read.extracted = []
772
773 for node_idname in node_def:
774 for n in mat.node_tree.nodes:
775 if n.bl_idname == node_idname:
776 node_def = node_def[node_idname]
777 node = n
778 break
779
780 for link in node_def:
781 if isinstance( node_def[link], dict ):
782 inputt = node.inputs[link]
783 inputt_def = node_def[link]
784
785 if inputt.is_linked:
786
787 # look for definitions for the connected node type
788 con = inputt.links[0].from_node
789
790 for node_idname in inputt_def:
791 if con.bl_idname == node_idname:
792 con_def = inputt_def[ node_idname ]
793 _graph_read( con_def, con, depth+1 )
794
795 # No definition found! :(
796 # TODO: Make a warning for this?
797
798 else:
799 if "default" in inputt_def:
800 prop = _variant_apply( inputt_def['default'] )
801 info[prop] = inputt.default_value
802 else:
803 prop = _variant_apply( node_def[link] )
804 info[prop] = getattr(node,link)
805
806 _graph_read(cxr_graph_mapping)
807
808 if "$basetexture" in info:
809 export_res = info['$basetexture'].cxr_data.export_res
810 info['res'] = (export_res[0], export_res[1])
811
812 return info
813
814 def mesh_cxr_format(obj):
815 dgraph = bpy.context.evaluated_depsgraph_get()
816 data = obj.evaluated_get(dgraph).data
817
818 _,mtx_rot,_ = obj.matrix_world.decompose()
819
820 mesh = cxr_input_mesh()
821
822 vertex_data = ((c_double*3)*len(data.vertices))()
823 for i, vert in enumerate(data.vertices):
824 v = obj.matrix_world @ vert.co
825 vertex_data[i][0] = c_double(v[0])
826 vertex_data[i][1] = c_double(v[1])
827 vertex_data[i][2] = c_double(v[2])
828
829 loop_data = (cxr_input_loop*len(data.loops))()
830 polygon_data = (cxr_polygon*len(data.polygons))()
831
832 for i, poly in enumerate(data.polygons):
833 loop_start = poly.loop_start
834 loop_end = poly.loop_start + poly.loop_total
835 for loop_index in range(loop_start, loop_end):
836 loop = data.loops[loop_index]
837 loop_data[loop_index].index = loop.vertex_index
838 loop_data[loop_index].edge_index = loop.edge_index
839
840 if data.uv_layers:
841 uv = data.uv_layers.active.data[loop_index].uv
842 loop_data[loop_index].uv[0] = c_double(uv[0])
843 loop_data[loop_index].uv[1] = c_double(uv[1])
844 else:
845 loop_data[loop_index].uv[0] = c_double(0.0)
846 loop_data[loop_index].uv[1] = c_double(0.0)
847 center = obj.matrix_world @ poly.center
848 normal = mtx_rot @ poly.normal
849
850 polygon_data[i].loop_start = poly.loop_start
851 polygon_data[i].loop_total = poly.loop_total
852 polygon_data[i].normal[0] = normal[0]
853 polygon_data[i].normal[1] = normal[1]
854 polygon_data[i].normal[2] = normal[2]
855 polygon_data[i].center[0] = center[0]
856 polygon_data[i].center[1] = center[1]
857 polygon_data[i].center[2] = center[2]
858 polygon_data[i].material_id = poly.material_index
859
860 edge_data = (cxr_edge*len(data.edges))()
861
862 for i, edge in enumerate(data.edges):
863 edge_data[i].i0 = edge.vertices[0]
864 edge_data[i].i1 = edge.vertices[1]
865 edge_data[i].freestyle = edge.use_freestyle_mark
866
867 material_data = (cxr_material*len(obj.material_slots))()
868
869 for i, ms in enumerate(obj.material_slots):
870 inf = material_info(ms.material)
871 material_data[i].res[0] = inf['res'][0]
872 material_data[i].res[1] = inf['res'][1]
873 material_data[i].vmt_path = inf['name'].encode('utf-8')
874
875 mesh.edges = cast(edge_data, POINTER(cxr_edge))
876 mesh.vertices = cast(vertex_data, POINTER(c_double*3))
877 mesh.loops = cast(loop_data,POINTER(cxr_input_loop))
878 mesh.polys = cast(polygon_data, POINTER(cxr_polygon))
879 mesh.materials = cast(material_data, POINTER(cxr_material))
880
881 mesh.poly_count = len(data.polygons)
882 mesh.vertex_count = len(data.vertices)
883 mesh.edge_count = len(data.edges)
884 mesh.loop_count = len(data.loops)
885 mesh.material_count = len(obj.material_slots)
886
887 return mesh
888
889 class CXR_WRITE_VMF(bpy.types.Operator):
890 bl_idname="convexer.write_vmf"
891 bl_label="Write VMF"
892
893 def execute(_,context):
894 libcxr_use()
895 libcxr_reset_debug_lines()
896
897 # Setup output and state
898 filepath = bpy.data.filepath
899 directory = os.path.dirname(filepath)
900 settings = context.scene.cxr_data
901
902 asset_dir = F"{directory}/bin"
903 material_dir = F"{settings.subdir}/materials/{settings.project_name}"
904 model_dir = F"{settings.subdir}/models/{settings.project_name}"
905
906 os.makedirs( asset_dir, exist_ok=True )
907 os.makedirs( material_dir, exist_ok=True )
908 os.makedirs( model_dir, exist_ok=True )
909
910 # States
911 material_info.references = set()
912 libcxr_context_reset()
913
914 output_vmf = F"{directory}/{settings.project_name}.vmf"
915
916 with vdf_structure(output_vmf) as m:
917 print( F"Write: {output_vmf}" )
918
919 m.node('versioninfo')
920 m.kv('editorversion','400')
921 m.kv('editorbuild','8456')
922 m.kv('mapversion','4')
923 m.kv('formatversion','100')
924 m.kv('prefab','0')
925 m.edon()
926
927 m.node('visgroups')
928 m.edon()
929
930 m.node('viewsettings')
931 m.kv('bSnapToGrid','1')
932 m.kv('bShowGrid','1')
933 m.kv('bShowLogicalGrid','0')
934 m.kv('nGridSpacing','64')
935 m.kv('bShow3DGrid','0')
936 m.edon()
937
938 m.node('world')
939 m.kv('id','1')
940 m.kv('mapversion','1')
941 m.kv('classname','worldspawn')
942 m.kv('skyname','sky_csgo_night02b')
943 m.kv('maxpropscreenwidth','-1')
944 m.kv('detailvbsp','detail.vbsp')
945 m.kv('detailmaterial','detail/detailsprites')
946
947 # Make sure all of our asset types have a unique ID
948 def _uid_prepare(objtype):
949 used_ids = [0]
950 to_generate = []
951 id_max = 0
952 for o in objtype:
953 vs = o.cxr_data
954 if vs.asset_id in used_ids:
955 to_generate+=[vs]
956 else:
957 id_max = max(id_max,vs.asset_id)
958 used_ids+=[vs.asset_id]
959 for vs in to_generate:
960 id_max += 1
961 vs.asset_id = id_max
962
963 _uid_prepare(bpy.data.materials)
964 _uid_prepare(bpy.data.images)
965 _uid_prepare(bpy.data.collections)
966
967 # Export Brushes and displacement
968 def _collect(collection,transform):
969 if collection.name.startswith('.'):
970 return
971
972 if collection.hide_render:
973 return
974
975 if collection.name.startswith('mdl_'):
976 _collect.heros += [(collection,transform)]
977 return
978
979 for obj in collection.objects:
980 if obj.hide_get(): continue
981
982 classname = cxr_classname( obj )
983
984 if classname != None:
985 _collect.entities += [( obj,transform,classname )]
986 elif obj.type == 'MESH':
987 _collect.geo += [(obj,transform)]
988
989 for c in collection.children:
990 _collect( c, transform )
991
992 _collect.a_models = set()
993 _collect.entities = []
994 _collect.geo = []
995 _collect.heros = []
996
997 transform_main = cxr_object_context( \
998 context.scene.cxr_data.scale_factor, 0.0 )
999
1000 transform_sky = cxr_object_context( \
1001 context.scene.cxr_data.skybox_scale_factor, \
1002 context.scene.cxr_data.skybox_offset )
1003
1004 if 'main' in bpy.data.collections:
1005 _collect( bpy.data.collections['main'], transform_main )
1006
1007 if 'skybox' in bpy.data.collections:
1008 _collect( bpy.data.collections['skybox'], transform_sky )
1009
1010 # World geometry
1011 for brush in _collect.geo:
1012 baked = mesh_cxr_format( brush[0] )
1013 libcxr_set_scale_factor( brush[1].scale )
1014 libcxr_set_offset( brush[1].offset_z )
1015 libcxr_convert_mesh_to_vmf(baked,m.fp)
1016
1017 m.edon()
1018
1019 # Entities
1020 for entity in _collect.entities:
1021 obj = entity[0]
1022 ctx = entity[1]
1023 cls = entity[2]
1024 m.node( 'entity' )
1025 m.kv( 'classname', cls )
1026
1027 kvs = cxr_entity_keyvalues( obj, ctx, cls )
1028
1029 for kv in kvs:
1030 if isinstance(kv[2], list):
1031 m.kv( kv[0], ' '.join([str(_) for _ in kv[2]]) )
1032 else: m.kv( kv[0], str(kv[2]) )
1033
1034 if obj.type == 'MESH':
1035 baked = mesh_cxr_format( obj )
1036 libcxr_set_scale_factor( ctx.scale )
1037 libcxr_set_offset( ctx.offset_z )
1038 libcxr_convert_mesh_to_vmf(baked,m.fp)
1039
1040 m.edon()
1041
1042 print( "[CONVEXER] Compile materials / textures" )
1043
1044 for mat in material_info.references:
1045 compile_material(mat)
1046
1047 print( "[CONVEXER] Compiling models" )
1048
1049 libcxr_batch_debug_lines()
1050 scene_redraw()
1051
1052 return {'FINISHED'}
1053
1054 class CXR_DEV_OPERATOR(bpy.types.Operator):
1055 bl_idname="convexer.dev_test"
1056 bl_label="Export development data"
1057
1058 def execute(_,context):
1059 libcxr_use()
1060
1061 # Prepare input data
1062 mesh_src = mesh_cxr_format(context.active_object)
1063
1064 libcxr_reset_debug_lines()
1065 libcxr_write_test_data( pointer(mesh_src) )
1066 libcxr_batch_debug_lines()
1067
1068 scene_redraw()
1069 return {'FINISHED'}
1070
1071 class CXR_INTERFACE(bpy.types.Panel):
1072 bl_label="Convexer"
1073 bl_idname="SCENE_PT_convexer"
1074 bl_space_type='PROPERTIES'
1075 bl_region_type='WINDOW'
1076 bl_context="scene"
1077
1078 def draw(_,context):
1079 _.layout.operator("convexer.reload")
1080 _.layout.operator("convexer.dev_test")
1081 _.layout.operator("convexer.write_vmf")
1082
1083 settings = context.scene.cxr_data
1084
1085 _.layout.prop(settings, "debug")
1086 _.layout.prop(settings, "scale_factor")
1087 _.layout.prop(settings, "lightmap_scale")
1088 _.layout.prop(settings, "light_scale" )
1089
1090 box = _.layout.box()
1091
1092 box.prop(settings, "project_name")
1093 box.prop(settings, "subdir")
1094
1095 box = _.layout.box()
1096 box.operator("convexer.detect_compilers")
1097 box.prop(settings, "exe_studiomdl")
1098 box.prop(settings, "exe_vbsp")
1099 box.prop(settings, "exe_vvis")
1100 box.prop(settings, "exe_vrad")
1101
1102 # COmpile image using NBVTF and hash it
1103 def compile_image(img,flags):
1104 if img==None:
1105 return None
1106
1107 name = asset_name(img)
1108 src_path = bpy.path.abspath(img.filepath)
1109
1110 dims = img.cxr_data.export_res
1111 fmt = {
1112 'RGBA': NBVTF_IMAGE_FORMAT_RGBA8888,
1113 'DXT1': NBVTF_IMAGE_FORMAT_DXT1,
1114 'DXT5': NBVTF_IMAGE_FORMAT_DXT5,
1115 'RGB': NBVTF_IMAGE_FORMAT_RGB888
1116 }[ img.cxr_data.fmt ]
1117
1118 mipmap = img.cxr_data.mipmap
1119 lod = img.cxr_data.lod
1120 clamp = img.cxr_data.clamp
1121
1122 userflag_hash = F"{mipmap}.{lod}.{clamp}"
1123 file_hash = F"{name}.{os.path.getmtime(src_path)}"
1124 comphash = F"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}"
1125
1126 if img.cxr_data.last_hash != comphash:
1127 print( F"Texture update: {img.filepath}" )
1128
1129 src = src_path.encode('utf-8')
1130 dst = (asset_full_path('materials',img)+'.vtf').encode('utf-8')
1131
1132 flags_full = flags
1133
1134 # texture setting flags
1135 if not lod: flags_full |= NBVTF_TEXTUREFLAGS_NOLOD
1136 if clamp:
1137 flags_full |= NBVTF_TEXTUREFLAGS_CLAMPS
1138 flags_full |= NBVTF_TEXTUREFLAGS_CLAMPT
1139
1140 if libnbvtf_convert(src,dims[0],dims[1],mipmap,fmt,flags_full,dst):
1141 img.cxr_data.last_hash = comphash
1142
1143 return name
1144
1145 def compile_material(mat):
1146 print( F"Compile {asset_full_path('materials',mat)}.vmt" )
1147
1148 info = material_info(mat)
1149 properties = mat.cxr_data
1150
1151 props = []
1152
1153 def _mlayer( layer ):
1154 nonlocal properties, props
1155
1156 for decl in layer:
1157 if isinstance(layer[decl],dict): # $property definition
1158 pdef = layer[decl]
1159 ptype = pdef['type']
1160
1161 subdefines = False
1162 default = None
1163 prop = None
1164
1165 if 'shaders' in pdef and properties.shader not in pdef['shaders']:
1166 continue
1167
1168 # Group expansion (does it have subdefinitions?)
1169 for ch in pdef:
1170 if isinstance(pdef[ch],dict):
1171 subdefines = True
1172 break
1173
1174 expandview = False
1175
1176 if ptype == 'ui':
1177 expandview = True
1178 else:
1179 if ptype == 'intrinsic':
1180 if decl in info:
1181 prop = info[decl]
1182 else:
1183 prop = getattr(properties,decl)
1184 default = pdef['default']
1185
1186 if not isinstance(prop,str) and \
1187 not isinstance(prop,bpy.types.Image) and \
1188 hasattr(prop,'__getitem__'):
1189 prop = tuple([p for p in prop])
1190
1191 if prop != default:
1192 # write prop
1193 props += [(decl,pdef,prop)]
1194
1195 if subdefines:
1196 expandview = True
1197
1198 if expandview: _mlayer(pdef)
1199
1200 _mlayer( cxr_shader_params )
1201
1202 with vdf_structure( F"{asset_full_path('materials',mat)}.vmt" ) as vmt:
1203 vmt.node( properties.shader )
1204 vmt.put( "// Convexer export\n" )
1205
1206 for pair in props:
1207 decl = pair[0]
1208 pdef = pair[1]
1209 prop = pair[2]
1210
1211 def _numeric(v):
1212 nonlocal pdef
1213
1214 if 'exponent' in pdef: return str(pow( v, pdef['exponent'] ))
1215 else: return str(v)
1216
1217 if isinstance(prop,bpy.types.Image):
1218 flags = 0
1219 if 'flags' in pdef:
1220 flags = pdef['flags']
1221 vmt.kv( decl,compile_image(prop,flags))
1222
1223 elif isinstance(prop,bool):
1224 vmt.kv( decl, '1' if prop else '0' )
1225 elif isinstance(prop,str):
1226 vmt.kv( decl, prop )
1227 elif isinstance(prop,float) or isinstance(prop,int):
1228 vmt.kv( decl, _numeric(prop) )
1229 elif isinstance(prop,tuple):
1230 vmt.kv( decl, F"[{' '.join([_numeric(_) for _ in prop])}]" )
1231 else:
1232 vmt.put( F"// (cxr) unkown shader value type'{type(prop)}'" )
1233
1234 vmt.edon()
1235
1236 class CXR_MATERIAL_PANEL(bpy.types.Panel):
1237 bl_label="VMT Properties"
1238 bl_idname="SCENE_PT_convexer_vmt"
1239 bl_space_type='PROPERTIES'
1240 bl_region_type='WINDOW'
1241 bl_context="material"
1242
1243 def draw(_,context):
1244 active_object = bpy.context.active_object
1245 if active_object == None: return
1246
1247 active_material = active_object.active_material
1248 if active_material == None: return
1249
1250 properties = active_material.cxr_data
1251 info = material_info( active_material )
1252
1253 _.layout.label(text=F"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1254 _.layout.prop( properties, "shader" )
1255
1256 for xk in info:
1257 _.layout.label(text=F"{xk}:={info[xk]}")
1258
1259 def _mtex( name, img, uiParent ):
1260 nonlocal properties
1261
1262 box = uiParent.box()
1263 box.label( text=F'{name} "{img.filepath}"' )
1264 def _p2( x ):
1265 if ((x & (x - 1)) == 0):
1266 return x
1267 closest = 0
1268 closest_diff = 10000000
1269 for i in range(16):
1270 dist = abs((1 << i)-x)
1271 if dist < closest_diff:
1272 closest_diff = dist
1273 closest = i
1274 real = 1 << closest
1275 if x > real:
1276 return 1 << (closest+1)
1277 if x < real:
1278 return 1 << (closest-1)
1279 return x
1280
1281 if img is not None:
1282 row = box.row()
1283 row.prop( img.cxr_data, "export_res" )
1284 row.prop( img.cxr_data, "fmt" )
1285
1286 row = box.row()
1287 row.prop( img.cxr_data, "mipmap" )
1288 row.prop( img.cxr_data, "lod" )
1289 row.prop( img.cxr_data, "clamp" )
1290
1291 img.cxr_data.export_res[0] = _p2( img.cxr_data.export_res[0] )
1292 img.cxr_data.export_res[1] = _p2( img.cxr_data.export_res[1] )
1293
1294 def _mview( layer, uiParent ):
1295 nonlocal properties
1296
1297 for decl in layer:
1298 if isinstance(layer[decl],dict): # $property definition
1299 pdef = layer[decl]
1300 ptype = pdef['type']
1301
1302 thisnode = uiParent
1303 expandview = True
1304 drawthis = True
1305
1306 if ('shaders' in pdef) and \
1307 (properties.shader not in pdef['shaders']):
1308 continue
1309
1310 if ptype == 'intrinsic':
1311 if decl not in info:
1312 drawthis = False
1313
1314 if drawthis:
1315 for ch in pdef:
1316 if isinstance(pdef[ch],dict):
1317 if ptype == 'ui' or ptype == 'intrinsic':
1318 pass
1319 elif getattr(properties,decl) == pdef['default']:
1320 expandview = False
1321
1322 thisnode = uiParent.box()
1323 break
1324
1325 if ptype == 'ui':
1326 thisnode.label( text=decl )
1327 elif ptype == 'intrinsic':
1328 if isinstance(info[decl], bpy.types.Image):
1329 _mtex( decl, info[decl], thisnode )
1330 else:
1331 # hidden intrinsic value.
1332 # Means its a float array or something not an image
1333 thisnode.label(text=F"-- hidden intrinsic '{decl}' --")
1334 else:
1335 thisnode.prop(properties,decl)
1336 if expandview: _mview(pdef,thisnode)
1337
1338 _mview( cxr_shader_params, _.layout )
1339
1340 def cxr_entity_changeclass(_,context):
1341 active_object = context.active_object
1342
1343 # Create ID properties
1344 entdef = None
1345 classname = active_object.cxr_data.classname
1346
1347 if classname in cxr_entities:
1348 entdef = cxr_entities[classname]
1349
1350 kvs = entdef['keyvalues']
1351 if callable(kvs): kvs = kvs(active_object)
1352
1353 for k in kvs:
1354 kv = kvs[k]
1355 key = F'cxrkv_{k}'
1356
1357 if callable(kv) or not isinstance(kv,dict): continue
1358
1359 if key not in active_object:
1360 active_object[key] = kv['default']
1361 id_prop = active_object.id_properties_ui(key)
1362 id_prop.update(default=kv['default'])
1363
1364 class CXR_ENTITY_PANEL(bpy.types.Panel):
1365 bl_label="Entity Config"
1366 bl_idname="SCENE_PT_convexer_entity"
1367 bl_space_type='PROPERTIES'
1368 bl_region_type='WINDOW'
1369 bl_context="object"
1370
1371 def draw(_,context):
1372 active_object = bpy.context.active_object
1373
1374 if active_object == None: return
1375
1376 default_context = cxr_object_context( \
1377 bpy.context.scene.cxr_data.scale_factor, 0.0 )
1378
1379 ecn = cxr_intrinsic_classname( active_object )
1380 classname = cxr_custom_class( active_object )
1381
1382 if ecn == None:
1383 if active_object.type == 'MESH':
1384 _.layout.prop( active_object.cxr_data, 'brushclass' )
1385 else: _.layout.prop( active_object.cxr_data, 'classname' )
1386
1387 if classname == 'NONE':
1388 return
1389 else:
1390 _.layout.label(text=F"<implementation defined ({ecn})>")
1391 _.layout.enabled=False
1392 classname = ecn
1393
1394 kvs = cxr_entity_keyvalues( active_object, default_context, classname )
1395 if kvs != None:
1396 for kv in kvs:
1397 if kv[1]:
1398 _.layout.prop( active_object, F'["cxrkv_{kv[0]}"]', text=kv[0])
1399 else:
1400 row = _.layout.row()
1401 row.enabled = False
1402 row.label( text=F'{kv[0]}: {repr(kv[2])}' )
1403 else:
1404 _.layout.label( text=F"ERROR: NO CLASS DEFINITION" )
1405
1406 class CXR_LIGHT_PANEL(bpy.types.Panel):
1407 bl_label = "Source Settings"
1408 bl_idname = "LIGHT_PT_cxr"
1409 bl_space_type = 'PROPERTIES'
1410 bl_region_type = 'WINDOW'
1411 bl_context = "data"
1412
1413 def draw(self, context):
1414 layout = self.layout
1415 scene = context.scene
1416
1417 active_object = bpy.context.active_object
1418 if active_object == None: return
1419
1420 if active_object.type == 'LIGHT' or \
1421 active_object.type == 'LIGHT_PROBE':
1422
1423 properties = active_object.data.cxr_data
1424
1425 if active_object.type == 'LIGHT':
1426 layout.prop( properties, "realtime" )
1427 elif active_object.type == 'LIGHT_PROBE':
1428 layout.prop( properties, "size" )
1429
1430 class CXR_IMAGE_SETTINGS(bpy.types.PropertyGroup):
1431 export_res: bpy.props.IntVectorProperty(
1432 name="",
1433 description="Texture Export Resolution",
1434 default=(512,512),
1435 min=1,
1436 max=4096,
1437 size=2)
1438
1439 fmt: bpy.props.EnumProperty(
1440 name="Format",
1441 items = [
1442 ('DXT1', "DXT1", "BC1 compressed", '', 0),
1443 ('DXT5', "DXT5", "BC3 compressed", '', 1),
1444 ('RGB', "RGB", "Uncompressed", '', 2),
1445 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
1446 ],
1447 description="Image format",
1448 default=0)
1449
1450 last_hash: bpy.props.StringProperty( name="" )
1451 asset_id: bpy.props.IntProperty(name="intl_assetid",default=0)
1452
1453 mipmap: bpy.props.BoolProperty(name="MIP",default=True)
1454 lod: bpy.props.BoolProperty(name="LOD",default=True)
1455 clamp: bpy.props.BoolProperty(name="CLAMP",default=False)
1456
1457 class CXR_LIGHT_SETTINGS(bpy.types.PropertyGroup):
1458 realtime: bpy.props.BoolProperty(name="Realtime Light", default=True)
1459
1460 class CXR_CUBEMAP_SETTINGS(bpy.types.PropertyGroup):
1461 size: bpy.props.EnumProperty(
1462 name="Resolution",
1463 items = [
1464 ('1',"1x1",'','',0),
1465 ('2',"2x2",'','',1),
1466 ('3',"4x4",'','',2),
1467 ('4',"8x8",'','',3),
1468 ('5',"16x16",'','',4),
1469 ('6',"32x32",'','',5),
1470 ('7',"64x64",'','',6),
1471 ('8',"128x128",'','',7),
1472 ('9',"256x256",'','',8)
1473 ],
1474 description="Texture resolution",
1475 default=7)
1476
1477 class CXR_ENTITY_SETTINGS(bpy.types.PropertyGroup):
1478 entity: bpy.props.BoolProperty(name="")
1479
1480 enum_pointents = [('NONE',"None","")]
1481 enum_brushents = [('NONE',"None","")]
1482
1483 for classname in cxr_entities:
1484 entdef = cxr_entities[classname]
1485 if 'allow' in entdef:
1486 itm = [(classname, classname, "")]
1487 if 'EMPTY' in entdef['allow']: enum_pointents += itm
1488 else: enum_brushents += itm
1489
1490 classname: bpy.props.EnumProperty(items=enum_pointents, name="Class", \
1491 update=cxr_entity_changeclass, default='NONE' )
1492
1493 brushclass: bpy.props.EnumProperty(items=enum_brushents, name="Class", \
1494 update=cxr_entity_changeclass, default='NONE' )
1495
1496 class CXR_MODEL_SETTINGS(bpy.types.PropertyGroup):
1497 last_hash: bpy.props.StringProperty( name="" )
1498 asset_id: bpy.props.IntProperty(name="vmf_settings",default=0)
1499
1500 class CXR_SCENE_SETTINGS(bpy.types.PropertyGroup):
1501 project_name: bpy.props.StringProperty( name="Project Name" )
1502 subdir: bpy.props.StringProperty( name="Subdirectory" )
1503
1504 exe_studiomdl: bpy.props.StringProperty( name="studiomdl" )
1505 exe_vbsp: bpy.props.StringProperty( name="vbsp" )
1506 opt_vbsp: bpy.props.StringProperty( name="args" )
1507 exe_vvis: bpy.props.StringProperty( name="vvis" )
1508 opt_vvis: bpy.props.StringProperty( name="args" )
1509 exe_vrad: bpy.props.StringProperty( name="vrad" )
1510 opt_vrad: bpy.props.StringProperty( name="args" )
1511
1512 debug: bpy.props.BoolProperty(name="Debug",default=False)
1513 scale_factor: bpy.props.FloatProperty( name="VMF Scale factor", \
1514 default=32.0,min=1.0)
1515 skybox_scale_factor: bpy.props.FloatProperty( name="Sky Scale factor", \
1516 default=1.0,min=0.01)
1517
1518 skybox_offset: bpy.props.FloatProperty(name="Sky offset",default=-4096.0)
1519 light_scale: bpy.props.FloatProperty(name="Light Scale",default=1.0/5.0)
1520 include_names: bpy.props.BoolProperty(name="Append original file names",\
1521 default=True)
1522 lightmap_scale: bpy.props.IntProperty(name="Global Lightmap Scale",\
1523 default=12)
1524
1525 class CXR_DETECT_COMPILERS(bpy.types.Operator):
1526 bl_idname="convexer.detect_compilers"
1527 bl_label="Find compilers"
1528
1529 def execute(self,context):
1530 scene = context.scene
1531 settings = scene.cxr_data
1532 subdir = settings.subdir
1533
1534 for exename in ['studiomdl','vbsp','vvis','vrad']:
1535 searchpath = os.path.normpath(F'{subdir}/../bin/{exename}.exe')
1536 if os.path.exists(searchpath):
1537 settings[F'exe_{exename}'] = searchpath
1538
1539 return {'FINISHED'}
1540
1541 classes = [ CXR_RELOAD, CXR_DEV_OPERATOR, CXR_INTERFACE, \
1542 CXR_WRITE_VMF, CXR_MATERIAL_PANEL, CXR_IMAGE_SETTINGS,\
1543 CXR_MODEL_SETTINGS, CXR_ENTITY_SETTINGS, CXR_CUBEMAP_SETTINGS,\
1544 CXR_LIGHT_SETTINGS, CXR_SCENE_SETTINGS, CXR_DETECT_COMPILERS,\
1545 CXR_ENTITY_PANEL, CXR_LIGHT_PANEL ]
1546
1547 def register():
1548 global debug_draw_handler, vmt_param_dynamic_class
1549
1550 for c in classes:
1551 bpy.utils.register_class(c)
1552
1553 # Build dynamic VMT properties class defined by cxr_shader_params
1554 annotations_dict = {}
1555
1556 def _dvmt_propogate(layer):
1557 nonlocal annotations_dict
1558
1559 for decl in layer:
1560 if isinstance(layer[decl],dict): # $property definition
1561 pdef = layer[decl]
1562
1563 prop = None
1564 if pdef['type'] == 'bool':
1565 prop = bpy.props.BoolProperty(\
1566 name = pdef['name'],\
1567 default = pdef['default'])
1568
1569 elif pdef['type'] == 'float':
1570 prop = bpy.props.FloatProperty(\
1571 name = pdef['name'],\
1572 default = pdef['default'])
1573
1574 elif pdef['type'] == 'vector':
1575 if 'subtype' in pdef:
1576 prop = bpy.props.FloatVectorProperty(\
1577 name = pdef['name'],\
1578 subtype = pdef['subtype'],\
1579 default = pdef['default'],\
1580 size = len(pdef['default']))
1581 else:
1582 prop = bpy.props.FloatVectorProperty(\
1583 name = pdef['name'],\
1584 default = pdef['default'],\
1585 size = len(pdef['default']))
1586
1587 elif pdef['type'] == 'string':
1588 prop = bpy.props.StringProperty(\
1589 name = pdef['name'],\
1590 default = pdef['default'])
1591
1592 elif pdef['type'] == 'enum':
1593 prop = bpy.props.EnumProperty(\
1594 name = pdef['name'],\
1595 items = pdef['items'],\
1596 default = pdef['default'])
1597
1598 if prop != None:
1599 annotations_dict[decl] = prop
1600
1601 # Recurse into sub-definitions
1602 _dvmt_propogate(pdef)
1603
1604 annotations_dict["shader"] = bpy.props.EnumProperty(\
1605 name = "Shader",\
1606 items = [( _,\
1607 cxr_shaders[_]["name"],\
1608 '') for _ in cxr_shaders],\
1609 default = next(iter(cxr_shaders)))
1610
1611 annotations_dict["asset_id"] = bpy.props.IntProperty(name="intl_assetid",\
1612 default=0)
1613
1614 _dvmt_propogate( cxr_shader_params )
1615 vmt_param_dynamic_class = type(
1616 "CXR_VMT_DYNAMIC",
1617 (bpy.types.PropertyGroup,),{
1618 "__annotations__": annotations_dict
1619 },
1620 )
1621
1622 bpy.utils.register_class( vmt_param_dynamic_class )
1623
1624 # Pointer types
1625 bpy.types.Material.cxr_data = \
1626 bpy.props.PointerProperty(type=vmt_param_dynamic_class)
1627 bpy.types.Image.cxr_data = \
1628 bpy.props.PointerProperty(type=CXR_IMAGE_SETTINGS)
1629 bpy.types.Object.cxr_data = \
1630 bpy.props.PointerProperty(type=CXR_ENTITY_SETTINGS)
1631 bpy.types.Collection.cxr_data = \
1632 bpy.props.PointerProperty(type=CXR_MODEL_SETTINGS)
1633 bpy.types.Light.cxr_data = \
1634 bpy.props.PointerProperty(type=CXR_LIGHT_SETTINGS)
1635 bpy.types.LightProbe.cxr_data = \
1636 bpy.props.PointerProperty(type=CXR_CUBEMAP_SETTINGS)
1637 bpy.types.Scene.cxr_data = \
1638 bpy.props.PointerProperty(type=CXR_SCENE_SETTINGS)
1639
1640 # CXR Scene settings
1641
1642 # GPU / callbacks
1643 debug_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
1644 cxr_draw,(),'WINDOW','POST_VIEW')
1645
1646 bpy.app.handlers.load_post.append(cxr_on_load)
1647 bpy.app.handlers.depsgraph_update_post.append(cxr_dgraph_update)
1648
1649 def unregister():
1650 global debug_draw_handler, vmt_param_dynamic_class
1651
1652 bpy.utils.unregister_class( vmt_param_dynamic_class )
1653 for c in classes:
1654 bpy.utils.unregister_class(c)
1655
1656 bpy.app.handlers.depsgraph_update_post.remove(cxr_dgraph_update)
1657 bpy.app.handlers.load_post.remove(cxr_on_load)
1658
1659 bpy.types.SpaceView3D.draw_handler_remove(debug_draw_handler,'WINDOW')