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