init
[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_decompose = 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
122 class cxr_material(Structure):
123 _fields_ = [("res",c_int32 * 2),
124 ("vmt_path",c_char_p)]
125
126 class cxr_input_mesh(Structure):
127 _fields_ = [("vertices",POINTER(c_double * 3)),
128 ("edges",POINTER(cxr_edge)),
129 ("loops",POINTER(cxr_input_loop)),
130 ("polys",POINTER(cxr_polygon)),
131 ("materials",POINTER(cxr_material)),
132
133 ("poly_count",c_int32),
134 ("vertex_count",c_int32),
135 ("edge_count",c_int32),
136 ("loop_count",c_int32),
137 ("material_count",c_int32)]
138
139 class cxr_output_mesh(Structure):
140 _fields_ = []
141
142 def libcxr_log_callback(logStr):
143 print( F"{logStr.decode('utf-8')}",end='' )
144
145 debug_lines_positions = None
146 debug_lines_colours = None
147
148 def libcxr_reset_debug_lines():
149 global debug_lines_positions
150 global debug_lines_colours
151
152 debug_lines_positions = []
153 debug_lines_colours = []
154
155 def libcxr_batch_debug_lines():
156 global debug_lines_positions
157 global debug_lines_colours
158 global debug_gpu_lines
159 global debug_gpu_shader
160
161 debug_gpu_lines = batch_for_shader(\
162 debug_gpu_shader, 'LINES',\
163 { "pos": debug_lines_positions, "color": debug_lines_colours })
164
165 @persistent
166 def cxr_on_load(dummy):
167 libcxr_reset_debug_lines()
168 libcxr_batch_debug_lines()
169
170 @persistent
171 def cxr_dgraph_update(scene,dgraph):
172 return
173 print( F"Hallo {time.time()}" )
174
175 def libcxr_line_callback(p0,p1,colour):
176 global debug_lines_positions
177 global debug_lines_colours
178 debug_lines_positions += [(p0[0],p0[1],p0[2])]
179 debug_lines_positions += [(p1[0],p1[1],p1[2])]
180 debug_lines_colours += [(colour[0],colour[1],colour[2],colour[3])]
181 debug_lines_colours += [(colour[0],colour[1],colour[2],colour[3])]
182
183 def cxr_draw():
184 global debug_gpu_lines
185 global debug_gpu_shader
186
187 gpu.state.blend_set('ALPHA')
188 if debug_gpu_lines != None:
189 debug_gpu_lines.draw(debug_gpu_shader)
190
191 class CXR_RELOAD(bpy.types.Operator):
192 bl_idname="convexer.reload"
193 bl_label="Reload convexer"
194
195 def execute(_,context):
196 global libcxr, libnbvtf, libnbvtf_convert
197
198 # Load vtf library
199 libnbvtf = cdll.LoadLibrary( os.path.dirname(__file__)+'/libnbvtf.so')
200 libnbvtf_convert = libnbvtf.nbvtf_convert
201 libnbvtf_convert.argtypes = [\
202 c_char_p, \
203 c_int32, \
204 c_int32, \
205 c_int32, \
206 c_int32, \
207 c_uint32, \
208 c_char_p ]
209 libnbvtf_convert.restype = c_int32
210
211 if libcxr != None:
212 _handle = libcxr._handle
213
214 # TODO: Find a propper way to do this
215 for i in range(10): libc_dlclose( _handle )
216 del libcxr
217
218 libcxr = None
219 libcxr = cdll.LoadLibrary( os.path.dirname(__file__)+'/libcxr.so')
220
221 build_time = c_char_p.in_dll(libcxr,'cxr_build_time')
222 print( F"libcxr build time: {build_time.value}" )
223
224 # Public API
225 global libcxr_decompose
226 global libcxr_convert_mesh_to_vmf
227
228 libcxr_decompose = libcxr.cxr_decompose
229 libcxr_decompose.argtypes = [\
230 POINTER(cxr_input_mesh)
231 ]
232 libcxr_decompose.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 awful.
610 # Blender's default has a much more 'accurate' look
611 # They appear correct when using linear scale.
612 kvs['_linear_attn'] = 1.0
613
614 elif obj.data.type == 'POINT':
615 kvs['_quadratic_attn'] = 1.0
616 kvs['_linear_attn'] = 0.0
617
618 elif obj.data.type == 'SUN':
619 pass # TODO
620
621 return kvs
622
623 def ent_cubemap(obj,context):
624 return ent_baseclass([ent_origin],\
625 {"cubemapsize": obj.data.cxr_data.size})
626
627 cxr_entities = {
628 "info_player_counterterrorist":
629 {
630 "gizmo": [],
631 "allow": ('EMPTY',),
632 "keyvalues": ent_baseclass([ent_transform],\
633 {
634 "priority": {"type": "int", "default": 0 },
635 "enabled": {"type": "int", "default": 1 },
636 })
637 },
638 "light": { "keyvalues": ent_lights },
639 "light_spot": { "keyvalues": ent_lights },
640 # SUN
641 "env_cubemap": { "keyvalues": ent_cubemap },
642
643 # Brush entites
644 "func_buyzone":
645 {
646 "allow": ('MESH',),
647 "keyvalues":
648 {
649 "TeamNum": {"type": "int", "default": 0 }
650 }
651 }
652 }
653
654 def cxr_intrinsic_classname(obj):
655 if obj.type == 'LIGHT':
656 return {
657 'SPOT': "light_spot",
658 'POINT': "light",
659 'SUN': "light_directional" }[ obj.data.type ]
660
661 elif obj.type == 'LIGHT_PROBE':
662 return "env_cubemap"
663 elif obj.type == 'EMPTY':
664 if obj.is_instancer:
665 return "prop_static"
666
667 return None
668
669 def cxr_custom_class(obj):
670 if obj.type == 'MESH': custom_class = obj.cxr_data.brushclass
671 else: custom_class = obj.cxr_data.classname
672
673 return custom_class
674
675 def cxr_classname(obj):
676 intr = cxr_intrinsic_classname(obj)
677 if intr != None: return intr
678
679 custom_class = cxr_custom_class(obj)
680 if custom_class != 'NONE':
681 return custom_class
682
683 return None
684
685 # Returns array of:
686 # intinsic: (k, False, value)
687 # property: (k, True, value or default)
688 #
689 # Error: None
690 #
691 def cxr_entity_keyvalues(obj,context,classname):
692 if classname not in cxr_entities: return None
693
694 result = []
695
696 entdef = cxr_entities[classname]
697 kvs = entdef['keyvalues']
698
699 if callable(kvs): kvs = kvs(obj, context)
700
701 for k in kvs:
702 kv = kvs[k]
703 value = kv
704 isprop = False
705
706 if isinstance(kv,dict):
707 isprop = True
708 value = obj[ F"cxrkv_{k}" ]
709 else:
710 if callable(kv):
711 value = kv(obj,context)
712
713 if isinstance(value,mathutils.Vector):
714 value = [_ for _ in value]
715
716 result += [(k, isprop, value)]
717
718 return result
719
720 def material_info(mat):
721 info = {}
722 info['res'] = (512,512)
723 info['name'] = 'tools/toolsnodraw'
724
725 if mat == None or mat.use_nodes == False:
726 return info
727
728 # Builtin shader
729 if mat.cxr_data.shader == 'Builtin':
730 info['name'] = mat.name
731 return info
732
733 if not hasattr(material_info,'references'):
734 material_info.references = set()
735
736 # Custom material
737 material_info.references.add(mat)
738 info['name'] = asset_name(mat)
739
740 # Using the cxr_graph_mapping as a reference, go through the shader
741 # graph and gather all $props from it.
742 #
743 def _graph_read( node_def, node=None, depth=0 ):
744 nonlocal mat
745 nonlocal info
746
747 def _variant_apply( val ):
748 nonlocal mat
749
750 if isinstance( val, str ):
751 return val
752 else:
753 for shader_variant in val:
754 if shader_variant[0] == mat.cxr_data.shader:
755 return shader_variant[1]
756
757 # Find rootnodes
758 if node == None:
759 _graph_read.extracted = []
760
761 for node_idname in node_def:
762 for n in mat.node_tree.nodes:
763 if n.bl_idname == node_idname:
764 node_def = node_def[node_idname]
765 node = n
766 break
767
768 for link in node_def:
769 if isinstance( node_def[link], dict ):
770 inputt = node.inputs[link]
771 inputt_def = node_def[link]
772
773 if inputt.is_linked:
774
775 # look for definitions for the connected node type
776 con = inputt.links[0].from_node
777
778 for node_idname in inputt_def:
779 if con.bl_idname == node_idname:
780 con_def = inputt_def[ node_idname ]
781 _graph_read( con_def, con, depth+1 )
782
783 # No definition found! :(
784 # TODO: Make a warning for this?
785
786 else:
787 if "default" in inputt_def:
788 prop = _variant_apply( inputt_def['default'] )
789 info[prop] = inputt.default_value
790 else:
791 prop = _variant_apply( node_def[link] )
792 info[prop] = getattr(node,link)
793
794 _graph_read(cxr_graph_mapping)
795
796 if "$basetexture" in info:
797 export_res = info['$basetexture'].cxr_data.export_res
798 info['res'] = (export_res[0], export_res[1])
799
800 return info
801
802 def mesh_cxr_format(obj):
803 dgraph = bpy.context.evaluated_depsgraph_get()
804 data = obj.evaluated_get(dgraph).data
805
806 _,mtx_rot,_ = obj.matrix_world.decompose()
807
808 mesh = cxr_input_mesh()
809
810 vertex_data = ((c_double*3)*len(data.vertices))()
811 for i, vert in enumerate(data.vertices):
812 v = obj.matrix_world @ vert.co
813 vertex_data[i][0] = c_double(v[0])
814 vertex_data[i][1] = c_double(v[1])
815 vertex_data[i][2] = c_double(v[2])
816
817 loop_data = (cxr_input_loop*len(data.loops))()
818 polygon_data = (cxr_polygon*len(data.polygons))()
819
820 for i, poly in enumerate(data.polygons):
821 loop_start = poly.loop_start
822 loop_end = poly.loop_start + poly.loop_total
823 for loop_index in range(loop_start, loop_end):
824 loop = data.loops[loop_index]
825 loop_data[loop_index].index = loop.vertex_index
826 loop_data[loop_index].edge_index = loop.edge_index
827
828 if data.uv_layers:
829 uv = data.uv_layers.active.data[loop_index].uv
830 loop_data[loop_index].uv[0] = c_double(uv[0])
831 loop_data[loop_index].uv[1] = c_double(uv[1])
832 else:
833 loop_data[loop_index].uv[0] = c_double(0.0)
834 loop_data[loop_index].uv[1] = c_double(0.0)
835 center = obj.matrix_world @ poly.center
836 normal = mtx_rot @ poly.normal
837
838 polygon_data[i].loop_start = poly.loop_start
839 polygon_data[i].loop_total = poly.loop_total
840 polygon_data[i].normal[0] = normal[0]
841 polygon_data[i].normal[1] = normal[1]
842 polygon_data[i].normal[2] = normal[2]
843 polygon_data[i].center[0] = center[0]
844 polygon_data[i].center[1] = center[1]
845 polygon_data[i].center[2] = center[2]
846 polygon_data[i].material_id = poly.material_index
847
848 edge_data = (cxr_edge*len(data.edges))()
849
850 for i, edge in enumerate(data.edges):
851 edge_data[i].i0 = edge.vertices[0]
852 edge_data[i].i1 = edge.vertices[1]
853
854 material_data = (cxr_material*len(obj.material_slots))()
855
856 for i, ms in enumerate(obj.material_slots):
857 inf = material_info(ms.material)
858 material_data[i].res[0] = inf['res'][0]
859 material_data[i].res[1] = inf['res'][1]
860 material_data[i].vmt_path = inf['name'].encode('utf-8')
861
862 mesh.edges = cast(edge_data, POINTER(cxr_edge))
863 mesh.vertices = cast(vertex_data, POINTER(c_double*3))
864 mesh.loops = cast(loop_data,POINTER(cxr_input_loop))
865 mesh.polys = cast(polygon_data, POINTER(cxr_polygon))
866 mesh.materials = cast(material_data, POINTER(cxr_material))
867
868 mesh.poly_count = len(data.polygons)
869 mesh.vertex_count = len(data.vertices)
870 mesh.edge_count = len(data.edges)
871 mesh.loop_count = len(data.loops)
872 mesh.material_count = len(obj.material_slots)
873
874 return mesh
875
876 class CXR_WRITE_VMF(bpy.types.Operator):
877 bl_idname="convexer.write_vmf"
878 bl_label="Write VMF"
879
880 def execute(_,context):
881 libcxr_use()
882 libcxr_reset_debug_lines()
883
884 # Setup output and state
885 filepath = bpy.data.filepath
886 directory = os.path.dirname(filepath)
887 settings = context.scene.cxr_data
888
889 asset_dir = F"{directory}/bin"
890 material_dir = F"{settings.subdir}/materials/{settings.project_name}"
891 model_dir = F"{settings.subdir}/models/{settings.project_name}"
892
893 os.makedirs( asset_dir, exist_ok=True )
894 os.makedirs( material_dir, exist_ok=True )
895 os.makedirs( model_dir, exist_ok=True )
896
897 # States
898 material_info.references = set()
899 libcxr_context_reset()
900
901 output_vmf = F"{directory}/{settings.project_name}.vmf"
902
903 with vdf_structure(output_vmf) as m:
904 print( F"Write: {output_vmf}" )
905
906 m.node('versioninfo')
907 m.kv('editorversion','400')
908 m.kv('editorbuild','8456')
909 m.kv('mapversion','4')
910 m.kv('formatversion','100')
911 m.kv('prefab','0')
912 m.edon()
913
914 m.node('visgroups')
915 m.edon()
916
917 m.node('viewsettings')
918 m.kv('bSnapToGrid','1')
919 m.kv('bShowGrid','1')
920 m.kv('bShowLogicalGrid','0')
921 m.kv('nGridSpacing','64')
922 m.kv('bShow3DGrid','0')
923 m.edon()
924
925 m.node('world')
926 m.kv('id','1')
927 m.kv('mapversion','1')
928 m.kv('classname','worldspawn')
929 m.kv('skyname','sky_csgo_night02b')
930 m.kv('maxpropscreenwidth','-1')
931 m.kv('detailvbsp','detail.vbsp')
932 m.kv('detailmaterial','detail/detailsprites')
933
934 # Make sure all of our asset types have a unique ID
935 def _uid_prepare(objtype):
936 used_ids = [0]
937 to_generate = []
938 id_max = 0
939 for o in objtype:
940 vs = o.cxr_data
941 if vs.asset_id in used_ids:
942 to_generate+=[vs]
943 else:
944 id_max = max(id_max,vs.asset_id)
945 used_ids+=[vs.asset_id]
946 for vs in to_generate:
947 id_max += 1
948 vs.asset_id = id_max
949
950 _uid_prepare(bpy.data.materials)
951 _uid_prepare(bpy.data.images)
952 _uid_prepare(bpy.data.collections)
953
954 # Export Brushes and displacement
955 def _collect(collection,transform):
956 if collection.name.startswith('.'):
957 return
958
959 if collection.name.startswith('mdl_'):
960 _collect.heros += [(collection,transform)]
961 return
962
963 for obj in collection.objects:
964 classname = cxr_classname( obj )
965
966 if classname != None:
967 _collect.entities += [( obj,transform,classname )]
968 elif obj.type == 'MESH':
969 _collect.geo += [(obj,transform)]
970
971 for c in collection.children:
972 _collect( c, transform )
973
974 _collect.a_models = set()
975 _collect.entities = []
976 _collect.geo = []
977
978 transform_main = cxr_object_context( context.scene.cxr_data.scale_factor, 0.0 )
979 transform_sky = cxr_object_context( context.scene.cxr_data.skybox_scale_factor, \
980 context.scene.cxr_data.skybox_offset )
981
982 if 'main' in bpy.data.collections:
983 _collect( bpy.data.collections['main'], transform_main )
984
985 if 'skybox' in bpy.data.collections:
986 _collect( bpy.data.collections['skybox'], transform_sky )
987
988 # World geometry
989 for brush in _collect.geo:
990 baked = mesh_cxr_format( brush[0] )
991 libcxr_set_scale_factor( brush[1].scale )
992 libcxr_set_offset( brush[1].offset_z )
993 libcxr_convert_mesh_to_vmf(baked,m.fp)
994
995 m.edon()
996
997 # Entities
998 for entity in _collect.entities:
999 obj = entity[0]
1000 ctx = entity[1]
1001 cls = entity[2]
1002 m.node( 'entity' )
1003 m.kv( 'classname', cls )
1004
1005 kvs = cxr_entity_keyvalues( obj, ctx, cls )
1006
1007 for kv in kvs:
1008 if isinstance(kv[2], list):
1009 m.kv( kv[0], ' '.join([str(_) for _ in kv[2]]) )
1010 else: m.kv( kv[0], str(kv[2]) )
1011
1012 if obj.type == 'MESH':
1013 baked = mesh_cxr_format( obj )
1014 libcxr_set_scale_factor( ctx.scale )
1015 libcxr_set_offset( ctx.offset_z )
1016 libcxr_convert_mesh_to_vmf(baked,m.fp)
1017
1018 m.edon()
1019
1020 print( "[CONVEXER] Compile materials / textures" )
1021
1022 for mat in material_info.references:
1023 compile_material(mat)
1024
1025 print( "[CONVEXER] Compiling models" )
1026
1027 libcxr_batch_debug_lines()
1028 scene_redraw()
1029
1030 return {'FINISHED'}
1031
1032 class CXR_DECOMPOSE_SOLID(bpy.types.Operator):
1033 bl_idname="convexer.decompose_solid"
1034 bl_label="Decompose Solid"
1035
1036 def execute(_,context):
1037 libcxr_use()
1038
1039 # Prepare input data
1040 mesh_src = mesh_cxr_format(context.active_object)
1041
1042 libcxr_reset_debug_lines()
1043 libcxr_decompose( pointer(mesh_src) )
1044 libcxr_batch_debug_lines()
1045
1046 scene_redraw()
1047 return {'FINISHED'}
1048
1049 class CXR_INTERFACE(bpy.types.Panel):
1050 bl_label="Convexer"
1051 bl_idname="SCENE_PT_convexer"
1052 bl_space_type='PROPERTIES'
1053 bl_region_type='WINDOW'
1054 bl_context="scene"
1055
1056 def draw(_,context):
1057 _.layout.operator("convexer.reload")
1058 _.layout.operator("convexer.decompose_solid")
1059 _.layout.operator("convexer.write_vmf")
1060
1061 settings = context.scene.cxr_data
1062
1063 _.layout.prop(settings, "debug")
1064 _.layout.prop(settings, "scale_factor")
1065 _.layout.prop(settings, "lightmap_scale")
1066 _.layout.prop(settings, "light_scale" )
1067
1068 box = _.layout.box()
1069
1070 box.prop(settings, "project_name")
1071 box.prop(settings, "subdir")
1072
1073 box = _.layout.box()
1074 box.operator("convexer.detect_compilers")
1075 box.prop(settings, "exe_studiomdl")
1076 box.prop(settings, "exe_vbsp")
1077 box.prop(settings, "exe_vvis")
1078 box.prop(settings, "exe_vrad")
1079
1080 # COmpile image using NBVTF and hash it
1081 def compile_image(img,flags):
1082 if img==None:
1083 return None
1084
1085 name = asset_name(img)
1086 src_path = bpy.path.abspath(img.filepath)
1087
1088 dims = img.cxr_data.export_res
1089 fmt = {
1090 'RGBA': NBVTF_IMAGE_FORMAT_RGBA8888,
1091 'DXT1': NBVTF_IMAGE_FORMAT_DXT1,
1092 'DXT5': NBVTF_IMAGE_FORMAT_DXT5,
1093 'RGB': NBVTF_IMAGE_FORMAT_RGB888
1094 }[ img.cxr_data.fmt ]
1095
1096 mipmap = img.cxr_data.mipmap
1097 lod = img.cxr_data.lod
1098 clamp = img.cxr_data.clamp
1099
1100 userflag_hash = F"{mipmap}.{lod}.{clamp}"
1101 file_hash = F"{name}.{os.path.getmtime(src_path)}"
1102 comphash = F"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}"
1103
1104 if img.cxr_data.last_hash != comphash:
1105 print( F"Texture update: {img.filepath}" )
1106
1107 src = src_path.encode('utf-8')
1108 dst = (asset_full_path('materials',img)+'.vtf').encode('utf-8')
1109
1110 flags_full = flags
1111
1112 # texture setting flags
1113 if not lod: flags_full |= NBVTF_TEXTUREFLAGS_NOLOD
1114 if clamp:
1115 flags_full |= NBVTF_TEXTUREFLAGS_CLAMPS
1116 flags_full |= NBVTF_TEXTUREFLAGS_CLAMPT
1117
1118 if libnbvtf_convert(src,dims[0],dims[1],mipmap,fmt,flags_full,dst):
1119 img.cxr_data.last_hash = comphash
1120
1121 return name
1122
1123 def compile_material(mat):
1124 print( F"Compile {asset_full_path('materials',mat)}.vmt" )
1125
1126 info = material_info(mat)
1127 properties = mat.cxr_data
1128
1129 props = []
1130
1131 def _mlayer( layer ):
1132 nonlocal properties, props
1133
1134 for decl in layer:
1135 if isinstance(layer[decl],dict): # $property definition
1136 pdef = layer[decl]
1137 ptype = pdef['type']
1138
1139 subdefines = False
1140 default = None
1141 prop = None
1142
1143 if 'shaders' in pdef and properties.shader not in pdef['shaders']:
1144 continue
1145
1146 # Group expansion (does it have subdefinitions?)
1147 for ch in pdef:
1148 if isinstance(pdef[ch],dict):
1149 subdefines = True
1150 break
1151
1152 expandview = False
1153
1154 if ptype == 'ui':
1155 expandview = True
1156 else:
1157 if ptype == 'intrinsic':
1158 if decl in info:
1159 prop = info[decl]
1160 else:
1161 prop = getattr(properties,decl)
1162 default = pdef['default']
1163
1164 if not isinstance(prop,str) and \
1165 not isinstance(prop,bpy.types.Image) and \
1166 hasattr(prop,'__getitem__'):
1167 prop = tuple([p for p in prop])
1168
1169 if prop != default:
1170 # write prop
1171 props += [(decl,pdef,prop)]
1172
1173 if subdefines:
1174 expandview = True
1175
1176 if expandview: _mlayer(pdef)
1177
1178 _mlayer( cxr_shader_params )
1179
1180 with vdf_structure( F"{asset_full_path('materials',mat)}.vmt" ) as vmt:
1181 vmt.node( properties.shader )
1182 vmt.put( "// Convexer export\n" )
1183
1184 for pair in props:
1185 decl = pair[0]
1186 pdef = pair[1]
1187 prop = pair[2]
1188
1189 def _numeric(v):
1190 nonlocal pdef
1191
1192 if 'exponent' in pdef: return str(pow( v, pdef['exponent'] ))
1193 else: return str(v)
1194
1195 if isinstance(prop,bpy.types.Image):
1196 flags = 0
1197 if 'flags' in pdef:
1198 flags = pdef['flags']
1199 vmt.kv( decl,compile_image(prop,flags))
1200
1201 elif isinstance(prop,bool):
1202 vmt.kv( decl, '1' if prop else '0' )
1203 elif isinstance(prop,str):
1204 vmt.kv( decl, prop )
1205 elif isinstance(prop,float) or isinstance(prop,int):
1206 vmt.kv( decl, _numeric(prop) )
1207 elif isinstance(prop,tuple):
1208 vmt.kv( decl, F"[{' '.join([_numeric(_) for _ in prop])}]" )
1209 else:
1210 vmt.put( F"// (cxr) unkown shader value type'{type(prop)}'" )
1211
1212 vmt.edon()
1213
1214 class CXR_MATERIAL_PANEL(bpy.types.Panel):
1215 bl_label="VMT Properties"
1216 bl_idname="SCENE_PT_convexer_vmt"
1217 bl_space_type='PROPERTIES'
1218 bl_region_type='WINDOW'
1219 bl_context="material"
1220
1221 def draw(_,context):
1222 active_object = bpy.context.active_object
1223 if active_object == None: return
1224
1225 active_material = active_object.active_material
1226 if active_material == None: return
1227
1228 properties = active_material.cxr_data
1229 info = material_info( active_material )
1230
1231 _.layout.label(text=F"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1232 _.layout.prop( properties, "shader" )
1233
1234 for xk in info:
1235 _.layout.label(text=F"{xk}:={info[xk]}")
1236
1237 def _mtex( name, img, uiParent ):
1238 nonlocal properties
1239
1240 box = uiParent.box()
1241 box.label( text=F'{name} "{img.filepath}"' )
1242 def _p2( x ):
1243 if ((x & (x - 1)) == 0):
1244 return x
1245 closest = 0
1246 closest_diff = 10000000
1247 for i in range(16):
1248 dist = abs((1 << i)-x)
1249 if dist < closest_diff:
1250 closest_diff = dist
1251 closest = i
1252 real = 1 << closest
1253 if x > real:
1254 return 1 << (closest+1)
1255 if x < real:
1256 return 1 << (closest-1)
1257 return x
1258
1259 if img is not None:
1260 row = box.row()
1261 row.prop( img.cxr_data, "export_res" )
1262 row.prop( img.cxr_data, "fmt" )
1263
1264 row = box.row()
1265 row.prop( img.cxr_data, "mipmap" )
1266 row.prop( img.cxr_data, "lod" )
1267 row.prop( img.cxr_data, "clamp" )
1268
1269 img.cxr_data.export_res[0] = _p2( img.cxr_data.export_res[0] )
1270 img.cxr_data.export_res[1] = _p2( img.cxr_data.export_res[1] )
1271
1272 def _mview( layer, uiParent ):
1273 nonlocal properties
1274
1275 for decl in layer:
1276 if isinstance(layer[decl],dict): # $property definition
1277 pdef = layer[decl]
1278 ptype = pdef['type']
1279
1280 thisnode = uiParent
1281 expandview = True
1282 drawthis = True
1283
1284 if 'shaders' in pdef and properties.shader not in pdef['shaders']:
1285 continue
1286
1287 if ptype == 'intrinsic':
1288 if decl not in info:
1289 drawthis = False
1290
1291 if drawthis:
1292 for ch in pdef:
1293 if isinstance(pdef[ch],dict):
1294 if ptype == 'ui' or ptype == 'intrinsic':
1295 pass
1296 elif getattr(properties,decl) == pdef['default']:
1297 expandview = False
1298
1299 thisnode = uiParent.box()
1300 break
1301
1302 if ptype == 'ui':
1303 thisnode.label( text=decl )
1304 elif ptype == 'intrinsic':
1305 if isinstance(info[decl], bpy.types.Image):
1306 _mtex( decl, info[decl], thisnode )
1307 else:
1308 # hidden intrinsic value.
1309 # Means its a float array or something not an image
1310 thisnode.label( text=F"-- hidden intrinsic '{decl}' --" )
1311 else:
1312 thisnode.prop(properties,decl)
1313 if expandview: _mview(pdef,thisnode)
1314
1315 _mview( cxr_shader_params, _.layout )
1316
1317 def cxr_entity_changeclass(_,context):
1318 active_object = context.active_object
1319
1320 # Create ID properties
1321 entdef = None
1322 classname = active_object.cxr_data.classname
1323
1324 if classname in cxr_entities:
1325 entdef = cxr_entities[classname]
1326
1327 kvs = entdef['keyvalues']
1328 if callable(kvs): kvs = kvs(active_object)
1329
1330 for k in kvs:
1331 kv = kvs[k]
1332 key = F'cxrkv_{k}'
1333
1334 if callable(kv) or not isinstance(kv,dict): continue
1335
1336 if key not in active_object:
1337 active_object[key] = kv['default']
1338 id_prop = active_object.id_properties_ui(key)
1339 id_prop.update(default=kv['default'])
1340
1341 class CXR_ENTITY_PANEL(bpy.types.Panel):
1342 bl_label="Entity Config"
1343 bl_idname="SCENE_PT_convexer_entity"
1344 bl_space_type='PROPERTIES'
1345 bl_region_type='WINDOW'
1346 bl_context="object"
1347
1348 def draw(_,context):
1349 active_object = bpy.context.active_object
1350
1351 if active_object == None: return
1352
1353 default_context = cxr_object_context( bpy.context.scene.cxr_data.scale_factor, 0.0 )
1354 ecn = cxr_intrinsic_classname( active_object )
1355 classname = cxr_custom_class( active_object )
1356
1357 if ecn == None:
1358 if active_object.type == 'MESH': _.layout.prop( active_object.cxr_data, 'brushclass' )
1359 else: _.layout.prop( active_object.cxr_data, 'classname' )
1360
1361 if classname == 'NONE':
1362 return
1363 else:
1364 _.layout.label(text=F"<implementation defined ({ecn})>")
1365 _.layout.enabled=False
1366 classname = ecn
1367
1368 kvs = cxr_entity_keyvalues( active_object, default_context, classname )
1369 if kvs != None:
1370 for kv in kvs:
1371 if kv[1]:
1372 _.layout.prop( active_object, F'["cxrkv_{kv[0]}"]', text=kv[0])
1373 else:
1374 row = _.layout.row()
1375 row.enabled = False
1376 row.label( text=F'{kv[0]}: {repr(kv[2])}' )
1377 else:
1378 _.layout.label( text=F"ERROR: NO CLASS DEFINITION" )
1379
1380 class CXR_LIGHT_PANEL(bpy.types.Panel):
1381 bl_label = "Source Settings"
1382 bl_idname = "LIGHT_PT_cxr"
1383 bl_space_type = 'PROPERTIES'
1384 bl_region_type = 'WINDOW'
1385 bl_context = "data"
1386
1387 def draw(self, context):
1388 layout = self.layout
1389 scene = context.scene
1390
1391 active_object = bpy.context.active_object
1392 if active_object == None: return
1393
1394 if active_object.type == 'LIGHT' or \
1395 active_object.type == 'LIGHT_PROBE':
1396
1397 properties = active_object.data.cxr_data
1398
1399 if active_object.type == 'LIGHT':
1400 layout.prop( properties, "realtime" )
1401 elif active_object.type == 'LIGHT_PROBE':
1402 layout.prop( properties, "size" )
1403
1404 class CXR_IMAGE_SETTINGS(bpy.types.PropertyGroup):
1405 export_res: bpy.props.IntVectorProperty(
1406 name="",
1407 description="Texture Export Resolution",
1408 default=(512,512),
1409 min=1,
1410 max=4096,
1411 size=2)
1412
1413 fmt: bpy.props.EnumProperty(
1414 name="Format",
1415 items = [
1416 ('DXT1', "DXT1", "BC1 compressed", '', 0),
1417 ('DXT5', "DXT5", "BC3 compressed", '', 1),
1418 ('RGB', "RGB", "Uncompressed", '', 2),
1419 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
1420 ],
1421 description="Image format",
1422 default=0)
1423
1424 last_hash: bpy.props.StringProperty( name="" )
1425 asset_id: bpy.props.IntProperty(name="intl_assetid",default=0)
1426
1427 mipmap: bpy.props.BoolProperty(name="MIP",default=True)
1428 lod: bpy.props.BoolProperty(name="LOD",default=True)
1429 clamp: bpy.props.BoolProperty(name="CLAMP",default=False)
1430
1431 class CXR_LIGHT_SETTINGS(bpy.types.PropertyGroup):
1432 realtime: bpy.props.BoolProperty(name="Realtime Light", default=True)
1433
1434 class CXR_CUBEMAP_SETTINGS(bpy.types.PropertyGroup):
1435 size: bpy.props.EnumProperty(
1436 name="Resolution",
1437 items = [
1438 ('1',"1x1",'','',0),
1439 ('2',"2x2",'','',1),
1440 ('3',"4x4",'','',2),
1441 ('4',"8x8",'','',3),
1442 ('5',"16x16",'','',4),
1443 ('6',"32x32",'','',5),
1444 ('7',"64x64",'','',6),
1445 ('8',"128x128",'','',7),
1446 ('9',"256x256",'','',8)
1447 ],
1448 description="Texture resolution",
1449 default=7)
1450
1451 class CXR_ENTITY_SETTINGS(bpy.types.PropertyGroup):
1452 entity: bpy.props.BoolProperty(name="")
1453
1454 enum_pointents = [('NONE',"None","")]
1455 enum_brushents = [('NONE',"None","")]
1456
1457 for classname in cxr_entities:
1458 entdef = cxr_entities[classname]
1459 if 'allow' in entdef:
1460 itm = [(classname, classname, "")]
1461 if 'EMPTY' in entdef['allow']: enum_pointents += itm
1462 else: enum_brushents += itm
1463
1464 classname: bpy.props.EnumProperty(items=enum_pointents, name="Class", \
1465 update=cxr_entity_changeclass, default='NONE' )
1466
1467 brushclass: bpy.props.EnumProperty(items=enum_brushents, name="Class", \
1468 update=cxr_entity_changeclass, default='NONE' )
1469
1470 class CXR_MODEL_SETTINGS(bpy.types.PropertyGroup):
1471 last_hash: bpy.props.StringProperty( name="" )
1472 asset_id: bpy.props.IntProperty(name="vmf_settings",default=0)
1473
1474 class CXR_SCENE_SETTINGS(bpy.types.PropertyGroup):
1475 project_name: bpy.props.StringProperty( name="Project Name" )
1476 subdir: bpy.props.StringProperty( name="Subdirectory" )
1477
1478 exe_studiomdl: bpy.props.StringProperty( name="studiomdl" )
1479 exe_vbsp: bpy.props.StringProperty( name="vbsp" )
1480 opt_vbsp: bpy.props.StringProperty( name="args" )
1481 exe_vvis: bpy.props.StringProperty( name="vvis" )
1482 opt_vvis: bpy.props.StringProperty( name="args" )
1483 exe_vrad: bpy.props.StringProperty( name="vrad" )
1484 opt_vrad: bpy.props.StringProperty( name="args" )
1485
1486 debug: bpy.props.BoolProperty(name="Debug",default=False)
1487 scale_factor: bpy.props.FloatProperty(name="VMF Scale factor",default=32.0,min=1.0)
1488 skybox_scale_factor: bpy.props.FloatProperty(name="Sky Scale factor",default=1.0,min=0.01)
1489 skybox_offset: bpy.props.FloatProperty(name="Sky offset",default=-4096.0)
1490 light_scale: bpy.props.FloatProperty(name="Light Scale",default=1.0/5.0)
1491 displacement_cardinal: bpy.props.BoolProperty(name="Cardinal displacements",default=True)
1492 include_names: bpy.props.BoolProperty(name="Append original file names",default=True)
1493 lightmap_scale: bpy.props.IntProperty(name="Global Lightmap Scale",default=12)
1494
1495 class CXR_DETECT_COMPILERS(bpy.types.Operator):
1496 bl_idname="convexer.detect_compilers"
1497 bl_label="Find compilers"
1498
1499 def execute(self,context):
1500 scene = context.scene
1501 settings = scene.cxr_data
1502 subdir = settings.subdir
1503
1504 for exename in ['studiomdl','vbsp','vvis','vrad']:
1505 searchpath = os.path.normpath(F'{subdir}/../bin/{exename}.exe')
1506 if os.path.exists(searchpath):
1507 settings[F'exe_{exename}'] = searchpath
1508
1509 return {'FINISHED'}
1510
1511 classes = [ CXR_RELOAD, CXR_DECOMPOSE_SOLID, CXR_INTERFACE, \
1512 CXR_WRITE_VMF, CXR_MATERIAL_PANEL, CXR_IMAGE_SETTINGS,\
1513 CXR_MODEL_SETTINGS, CXR_ENTITY_SETTINGS, CXR_CUBEMAP_SETTINGS,\
1514 CXR_LIGHT_SETTINGS, CXR_SCENE_SETTINGS, CXR_DETECT_COMPILERS,\
1515 CXR_ENTITY_PANEL, CXR_LIGHT_PANEL ]
1516
1517 def register():
1518 global debug_draw_handler, vmt_param_dynamic_class
1519
1520 for c in classes:
1521 bpy.utils.register_class(c)
1522
1523 # Build dynamic VMT properties class defined by cxr_shader_params
1524 annotations_dict = {}
1525
1526 def _dvmt_propogate(layer):
1527 nonlocal annotations_dict
1528
1529 for decl in layer:
1530 if isinstance(layer[decl],dict): # $property definition
1531 pdef = layer[decl]
1532
1533 prop = None
1534 if pdef['type'] == 'bool':
1535 prop = bpy.props.BoolProperty(\
1536 name = pdef['name'],\
1537 default = pdef['default'])
1538
1539 elif pdef['type'] == 'float':
1540 prop = bpy.props.FloatProperty(\
1541 name = pdef['name'],\
1542 default = pdef['default'])
1543
1544 elif pdef['type'] == 'vector':
1545 if 'subtype' in pdef:
1546 prop = bpy.props.FloatVectorProperty(\
1547 name = pdef['name'],\
1548 subtype = pdef['subtype'],\
1549 default = pdef['default'],\
1550 size = len(pdef['default']))
1551 else:
1552 prop = bpy.props.FloatVectorProperty(\
1553 name = pdef['name'],\
1554 default = pdef['default'],\
1555 size = len(pdef['default']))
1556
1557 elif pdef['type'] == 'string':
1558 prop = bpy.props.StringProperty(\
1559 name = pdef['name'],\
1560 default = pdef['default'])
1561
1562 elif pdef['type'] == 'enum':
1563 prop = bpy.props.EnumProperty(\
1564 name = pdef['name'],\
1565 items = pdef['items'],\
1566 default = pdef['default'])
1567
1568 if prop != None:
1569 annotations_dict[decl] = prop
1570
1571 # Recurse into sub-definitions
1572 _dvmt_propogate(pdef)
1573
1574 annotations_dict["shader"] = bpy.props.EnumProperty(\
1575 name = "Shader",\
1576 items = [( _,\
1577 cxr_shaders[_]["name"],\
1578 '') for _ in cxr_shaders],\
1579 default = next(iter(cxr_shaders)))
1580
1581 annotations_dict["asset_id"] = bpy.props.IntProperty(name="intl_assetid",default=0)
1582
1583 _dvmt_propogate( cxr_shader_params )
1584 vmt_param_dynamic_class = type(
1585 "CXR_VMT_DYNAMIC",
1586 (bpy.types.PropertyGroup,),{
1587 "__annotations__": annotations_dict
1588 },
1589 )
1590
1591 bpy.utils.register_class( vmt_param_dynamic_class )
1592
1593 # Pointer types
1594 bpy.types.Material.cxr_data = \
1595 bpy.props.PointerProperty(type=vmt_param_dynamic_class)
1596 bpy.types.Image.cxr_data = \
1597 bpy.props.PointerProperty(type=CXR_IMAGE_SETTINGS)
1598 bpy.types.Object.cxr_data = \
1599 bpy.props.PointerProperty(type=CXR_ENTITY_SETTINGS)
1600 bpy.types.Collection.cxr_data = \
1601 bpy.props.PointerProperty(type=CXR_MODEL_SETTINGS)
1602 bpy.types.Light.cxr_data = \
1603 bpy.props.PointerProperty(type=CXR_LIGHT_SETTINGS)
1604 bpy.types.LightProbe.cxr_data = \
1605 bpy.props.PointerProperty(type=CXR_CUBEMAP_SETTINGS)
1606 bpy.types.Scene.cxr_data = \
1607 bpy.props.PointerProperty(type=CXR_SCENE_SETTINGS)
1608
1609 # CXR Scene settings
1610
1611 # GPU / callbacks
1612 debug_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
1613 cxr_draw,(),'WINDOW','POST_VIEW')
1614
1615 bpy.app.handlers.load_post.append(cxr_on_load)
1616 bpy.app.handlers.depsgraph_update_post.append(cxr_dgraph_update)
1617
1618 def unregister():
1619 global debug_draw_handler, vmt_param_dynamic_class
1620
1621 bpy.utils.unregister_class( vmt_param_dynamic_class )
1622 for c in classes:
1623 bpy.utils.unregister_class(c)
1624
1625 bpy.app.handlers.depsgraph_update_post.remove(cxr_dgraph_update)
1626 bpy.app.handlers.load_post.remove(cxr_on_load)
1627
1628 bpy.types.SpaceView3D.draw_handler_remove(debug_draw_handler,'WINDOW')