bug fixes
[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.hide_render:
962 return
963
964 if collection.name.startswith('mdl_'):
965 _collect.heros += [(collection,transform)]
966 return
967
968 for obj in collection.objects:
969 if obj.hide_get(): continue
970
971 classname = cxr_classname( obj )
972
973 if classname != None:
974 _collect.entities += [( obj,transform,classname )]
975 elif obj.type == 'MESH':
976 _collect.geo += [(obj,transform)]
977
978 for c in collection.children:
979 _collect( c, transform )
980
981 _collect.a_models = set()
982 _collect.entities = []
983 _collect.geo = []
984 _collect.heros = []
985
986 transform_main = cxr_object_context( context.scene.cxr_data.scale_factor, 0.0 )
987 transform_sky = cxr_object_context( context.scene.cxr_data.skybox_scale_factor, \
988 context.scene.cxr_data.skybox_offset )
989
990 if 'main' in bpy.data.collections:
991 _collect( bpy.data.collections['main'], transform_main )
992
993 if 'skybox' in bpy.data.collections:
994 _collect( bpy.data.collections['skybox'], transform_sky )
995
996 # World geometry
997 for brush in _collect.geo:
998 baked = mesh_cxr_format( brush[0] )
999 libcxr_set_scale_factor( brush[1].scale )
1000 libcxr_set_offset( brush[1].offset_z )
1001 libcxr_convert_mesh_to_vmf(baked,m.fp)
1002
1003 m.edon()
1004
1005 # Entities
1006 for entity in _collect.entities:
1007 obj = entity[0]
1008 ctx = entity[1]
1009 cls = entity[2]
1010 m.node( 'entity' )
1011 m.kv( 'classname', cls )
1012
1013 kvs = cxr_entity_keyvalues( obj, ctx, cls )
1014
1015 for kv in kvs:
1016 if isinstance(kv[2], list):
1017 m.kv( kv[0], ' '.join([str(_) for _ in kv[2]]) )
1018 else: m.kv( kv[0], str(kv[2]) )
1019
1020 if obj.type == 'MESH':
1021 baked = mesh_cxr_format( obj )
1022 libcxr_set_scale_factor( ctx.scale )
1023 libcxr_set_offset( ctx.offset_z )
1024 libcxr_convert_mesh_to_vmf(baked,m.fp)
1025
1026 m.edon()
1027
1028 print( "[CONVEXER] Compile materials / textures" )
1029
1030 for mat in material_info.references:
1031 compile_material(mat)
1032
1033 print( "[CONVEXER] Compiling models" )
1034
1035 libcxr_batch_debug_lines()
1036 scene_redraw()
1037
1038 return {'FINISHED'}
1039
1040 class CXR_DECOMPOSE_SOLID(bpy.types.Operator):
1041 bl_idname="convexer.decompose_solid"
1042 bl_label="Decompose Solid"
1043
1044 def execute(_,context):
1045 libcxr_use()
1046
1047 # Prepare input data
1048 mesh_src = mesh_cxr_format(context.active_object)
1049
1050 libcxr_reset_debug_lines()
1051 libcxr_decompose( pointer(mesh_src) )
1052 libcxr_batch_debug_lines()
1053
1054 scene_redraw()
1055 return {'FINISHED'}
1056
1057 class CXR_INTERFACE(bpy.types.Panel):
1058 bl_label="Convexer"
1059 bl_idname="SCENE_PT_convexer"
1060 bl_space_type='PROPERTIES'
1061 bl_region_type='WINDOW'
1062 bl_context="scene"
1063
1064 def draw(_,context):
1065 _.layout.operator("convexer.reload")
1066 _.layout.operator("convexer.decompose_solid")
1067 _.layout.operator("convexer.write_vmf")
1068
1069 settings = context.scene.cxr_data
1070
1071 _.layout.prop(settings, "debug")
1072 _.layout.prop(settings, "scale_factor")
1073 _.layout.prop(settings, "lightmap_scale")
1074 _.layout.prop(settings, "light_scale" )
1075
1076 box = _.layout.box()
1077
1078 box.prop(settings, "project_name")
1079 box.prop(settings, "subdir")
1080
1081 box = _.layout.box()
1082 box.operator("convexer.detect_compilers")
1083 box.prop(settings, "exe_studiomdl")
1084 box.prop(settings, "exe_vbsp")
1085 box.prop(settings, "exe_vvis")
1086 box.prop(settings, "exe_vrad")
1087
1088 # COmpile image using NBVTF and hash it
1089 def compile_image(img,flags):
1090 if img==None:
1091 return None
1092
1093 name = asset_name(img)
1094 src_path = bpy.path.abspath(img.filepath)
1095
1096 dims = img.cxr_data.export_res
1097 fmt = {
1098 'RGBA': NBVTF_IMAGE_FORMAT_RGBA8888,
1099 'DXT1': NBVTF_IMAGE_FORMAT_DXT1,
1100 'DXT5': NBVTF_IMAGE_FORMAT_DXT5,
1101 'RGB': NBVTF_IMAGE_FORMAT_RGB888
1102 }[ img.cxr_data.fmt ]
1103
1104 mipmap = img.cxr_data.mipmap
1105 lod = img.cxr_data.lod
1106 clamp = img.cxr_data.clamp
1107
1108 userflag_hash = F"{mipmap}.{lod}.{clamp}"
1109 file_hash = F"{name}.{os.path.getmtime(src_path)}"
1110 comphash = F"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}"
1111
1112 if img.cxr_data.last_hash != comphash:
1113 print( F"Texture update: {img.filepath}" )
1114
1115 src = src_path.encode('utf-8')
1116 dst = (asset_full_path('materials',img)+'.vtf').encode('utf-8')
1117
1118 flags_full = flags
1119
1120 # texture setting flags
1121 if not lod: flags_full |= NBVTF_TEXTUREFLAGS_NOLOD
1122 if clamp:
1123 flags_full |= NBVTF_TEXTUREFLAGS_CLAMPS
1124 flags_full |= NBVTF_TEXTUREFLAGS_CLAMPT
1125
1126 if libnbvtf_convert(src,dims[0],dims[1],mipmap,fmt,flags_full,dst):
1127 img.cxr_data.last_hash = comphash
1128
1129 return name
1130
1131 def compile_material(mat):
1132 print( F"Compile {asset_full_path('materials',mat)}.vmt" )
1133
1134 info = material_info(mat)
1135 properties = mat.cxr_data
1136
1137 props = []
1138
1139 def _mlayer( layer ):
1140 nonlocal properties, props
1141
1142 for decl in layer:
1143 if isinstance(layer[decl],dict): # $property definition
1144 pdef = layer[decl]
1145 ptype = pdef['type']
1146
1147 subdefines = False
1148 default = None
1149 prop = None
1150
1151 if 'shaders' in pdef and properties.shader not in pdef['shaders']:
1152 continue
1153
1154 # Group expansion (does it have subdefinitions?)
1155 for ch in pdef:
1156 if isinstance(pdef[ch],dict):
1157 subdefines = True
1158 break
1159
1160 expandview = False
1161
1162 if ptype == 'ui':
1163 expandview = True
1164 else:
1165 if ptype == 'intrinsic':
1166 if decl in info:
1167 prop = info[decl]
1168 else:
1169 prop = getattr(properties,decl)
1170 default = pdef['default']
1171
1172 if not isinstance(prop,str) and \
1173 not isinstance(prop,bpy.types.Image) and \
1174 hasattr(prop,'__getitem__'):
1175 prop = tuple([p for p in prop])
1176
1177 if prop != default:
1178 # write prop
1179 props += [(decl,pdef,prop)]
1180
1181 if subdefines:
1182 expandview = True
1183
1184 if expandview: _mlayer(pdef)
1185
1186 _mlayer( cxr_shader_params )
1187
1188 with vdf_structure( F"{asset_full_path('materials',mat)}.vmt" ) as vmt:
1189 vmt.node( properties.shader )
1190 vmt.put( "// Convexer export\n" )
1191
1192 for pair in props:
1193 decl = pair[0]
1194 pdef = pair[1]
1195 prop = pair[2]
1196
1197 def _numeric(v):
1198 nonlocal pdef
1199
1200 if 'exponent' in pdef: return str(pow( v, pdef['exponent'] ))
1201 else: return str(v)
1202
1203 if isinstance(prop,bpy.types.Image):
1204 flags = 0
1205 if 'flags' in pdef:
1206 flags = pdef['flags']
1207 vmt.kv( decl,compile_image(prop,flags))
1208
1209 elif isinstance(prop,bool):
1210 vmt.kv( decl, '1' if prop else '0' )
1211 elif isinstance(prop,str):
1212 vmt.kv( decl, prop )
1213 elif isinstance(prop,float) or isinstance(prop,int):
1214 vmt.kv( decl, _numeric(prop) )
1215 elif isinstance(prop,tuple):
1216 vmt.kv( decl, F"[{' '.join([_numeric(_) for _ in prop])}]" )
1217 else:
1218 vmt.put( F"// (cxr) unkown shader value type'{type(prop)}'" )
1219
1220 vmt.edon()
1221
1222 class CXR_MATERIAL_PANEL(bpy.types.Panel):
1223 bl_label="VMT Properties"
1224 bl_idname="SCENE_PT_convexer_vmt"
1225 bl_space_type='PROPERTIES'
1226 bl_region_type='WINDOW'
1227 bl_context="material"
1228
1229 def draw(_,context):
1230 active_object = bpy.context.active_object
1231 if active_object == None: return
1232
1233 active_material = active_object.active_material
1234 if active_material == None: return
1235
1236 properties = active_material.cxr_data
1237 info = material_info( active_material )
1238
1239 _.layout.label(text=F"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1240 _.layout.prop( properties, "shader" )
1241
1242 for xk in info:
1243 _.layout.label(text=F"{xk}:={info[xk]}")
1244
1245 def _mtex( name, img, uiParent ):
1246 nonlocal properties
1247
1248 box = uiParent.box()
1249 box.label( text=F'{name} "{img.filepath}"' )
1250 def _p2( x ):
1251 if ((x & (x - 1)) == 0):
1252 return x
1253 closest = 0
1254 closest_diff = 10000000
1255 for i in range(16):
1256 dist = abs((1 << i)-x)
1257 if dist < closest_diff:
1258 closest_diff = dist
1259 closest = i
1260 real = 1 << closest
1261 if x > real:
1262 return 1 << (closest+1)
1263 if x < real:
1264 return 1 << (closest-1)
1265 return x
1266
1267 if img is not None:
1268 row = box.row()
1269 row.prop( img.cxr_data, "export_res" )
1270 row.prop( img.cxr_data, "fmt" )
1271
1272 row = box.row()
1273 row.prop( img.cxr_data, "mipmap" )
1274 row.prop( img.cxr_data, "lod" )
1275 row.prop( img.cxr_data, "clamp" )
1276
1277 img.cxr_data.export_res[0] = _p2( img.cxr_data.export_res[0] )
1278 img.cxr_data.export_res[1] = _p2( img.cxr_data.export_res[1] )
1279
1280 def _mview( layer, uiParent ):
1281 nonlocal properties
1282
1283 for decl in layer:
1284 if isinstance(layer[decl],dict): # $property definition
1285 pdef = layer[decl]
1286 ptype = pdef['type']
1287
1288 thisnode = uiParent
1289 expandview = True
1290 drawthis = True
1291
1292 if 'shaders' in pdef and properties.shader not in pdef['shaders']:
1293 continue
1294
1295 if ptype == 'intrinsic':
1296 if decl not in info:
1297 drawthis = False
1298
1299 if drawthis:
1300 for ch in pdef:
1301 if isinstance(pdef[ch],dict):
1302 if ptype == 'ui' or ptype == 'intrinsic':
1303 pass
1304 elif getattr(properties,decl) == pdef['default']:
1305 expandview = False
1306
1307 thisnode = uiParent.box()
1308 break
1309
1310 if ptype == 'ui':
1311 thisnode.label( text=decl )
1312 elif ptype == 'intrinsic':
1313 if isinstance(info[decl], bpy.types.Image):
1314 _mtex( decl, info[decl], thisnode )
1315 else:
1316 # hidden intrinsic value.
1317 # Means its a float array or something not an image
1318 thisnode.label( text=F"-- hidden intrinsic '{decl}' --" )
1319 else:
1320 thisnode.prop(properties,decl)
1321 if expandview: _mview(pdef,thisnode)
1322
1323 _mview( cxr_shader_params, _.layout )
1324
1325 def cxr_entity_changeclass(_,context):
1326 active_object = context.active_object
1327
1328 # Create ID properties
1329 entdef = None
1330 classname = active_object.cxr_data.classname
1331
1332 if classname in cxr_entities:
1333 entdef = cxr_entities[classname]
1334
1335 kvs = entdef['keyvalues']
1336 if callable(kvs): kvs = kvs(active_object)
1337
1338 for k in kvs:
1339 kv = kvs[k]
1340 key = F'cxrkv_{k}'
1341
1342 if callable(kv) or not isinstance(kv,dict): continue
1343
1344 if key not in active_object:
1345 active_object[key] = kv['default']
1346 id_prop = active_object.id_properties_ui(key)
1347 id_prop.update(default=kv['default'])
1348
1349 class CXR_ENTITY_PANEL(bpy.types.Panel):
1350 bl_label="Entity Config"
1351 bl_idname="SCENE_PT_convexer_entity"
1352 bl_space_type='PROPERTIES'
1353 bl_region_type='WINDOW'
1354 bl_context="object"
1355
1356 def draw(_,context):
1357 active_object = bpy.context.active_object
1358
1359 if active_object == None: return
1360
1361 default_context = cxr_object_context( bpy.context.scene.cxr_data.scale_factor, 0.0 )
1362 ecn = cxr_intrinsic_classname( active_object )
1363 classname = cxr_custom_class( active_object )
1364
1365 if ecn == None:
1366 if active_object.type == 'MESH': _.layout.prop( active_object.cxr_data, 'brushclass' )
1367 else: _.layout.prop( active_object.cxr_data, 'classname' )
1368
1369 if classname == 'NONE':
1370 return
1371 else:
1372 _.layout.label(text=F"<implementation defined ({ecn})>")
1373 _.layout.enabled=False
1374 classname = ecn
1375
1376 kvs = cxr_entity_keyvalues( active_object, default_context, classname )
1377 if kvs != None:
1378 for kv in kvs:
1379 if kv[1]:
1380 _.layout.prop( active_object, F'["cxrkv_{kv[0]}"]', text=kv[0])
1381 else:
1382 row = _.layout.row()
1383 row.enabled = False
1384 row.label( text=F'{kv[0]}: {repr(kv[2])}' )
1385 else:
1386 _.layout.label( text=F"ERROR: NO CLASS DEFINITION" )
1387
1388 class CXR_LIGHT_PANEL(bpy.types.Panel):
1389 bl_label = "Source Settings"
1390 bl_idname = "LIGHT_PT_cxr"
1391 bl_space_type = 'PROPERTIES'
1392 bl_region_type = 'WINDOW'
1393 bl_context = "data"
1394
1395 def draw(self, context):
1396 layout = self.layout
1397 scene = context.scene
1398
1399 active_object = bpy.context.active_object
1400 if active_object == None: return
1401
1402 if active_object.type == 'LIGHT' or \
1403 active_object.type == 'LIGHT_PROBE':
1404
1405 properties = active_object.data.cxr_data
1406
1407 if active_object.type == 'LIGHT':
1408 layout.prop( properties, "realtime" )
1409 elif active_object.type == 'LIGHT_PROBE':
1410 layout.prop( properties, "size" )
1411
1412 class CXR_IMAGE_SETTINGS(bpy.types.PropertyGroup):
1413 export_res: bpy.props.IntVectorProperty(
1414 name="",
1415 description="Texture Export Resolution",
1416 default=(512,512),
1417 min=1,
1418 max=4096,
1419 size=2)
1420
1421 fmt: bpy.props.EnumProperty(
1422 name="Format",
1423 items = [
1424 ('DXT1', "DXT1", "BC1 compressed", '', 0),
1425 ('DXT5', "DXT5", "BC3 compressed", '', 1),
1426 ('RGB', "RGB", "Uncompressed", '', 2),
1427 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
1428 ],
1429 description="Image format",
1430 default=0)
1431
1432 last_hash: bpy.props.StringProperty( name="" )
1433 asset_id: bpy.props.IntProperty(name="intl_assetid",default=0)
1434
1435 mipmap: bpy.props.BoolProperty(name="MIP",default=True)
1436 lod: bpy.props.BoolProperty(name="LOD",default=True)
1437 clamp: bpy.props.BoolProperty(name="CLAMP",default=False)
1438
1439 class CXR_LIGHT_SETTINGS(bpy.types.PropertyGroup):
1440 realtime: bpy.props.BoolProperty(name="Realtime Light", default=True)
1441
1442 class CXR_CUBEMAP_SETTINGS(bpy.types.PropertyGroup):
1443 size: bpy.props.EnumProperty(
1444 name="Resolution",
1445 items = [
1446 ('1',"1x1",'','',0),
1447 ('2',"2x2",'','',1),
1448 ('3',"4x4",'','',2),
1449 ('4',"8x8",'','',3),
1450 ('5',"16x16",'','',4),
1451 ('6',"32x32",'','',5),
1452 ('7',"64x64",'','',6),
1453 ('8',"128x128",'','',7),
1454 ('9',"256x256",'','',8)
1455 ],
1456 description="Texture resolution",
1457 default=7)
1458
1459 class CXR_ENTITY_SETTINGS(bpy.types.PropertyGroup):
1460 entity: bpy.props.BoolProperty(name="")
1461
1462 enum_pointents = [('NONE',"None","")]
1463 enum_brushents = [('NONE',"None","")]
1464
1465 for classname in cxr_entities:
1466 entdef = cxr_entities[classname]
1467 if 'allow' in entdef:
1468 itm = [(classname, classname, "")]
1469 if 'EMPTY' in entdef['allow']: enum_pointents += itm
1470 else: enum_brushents += itm
1471
1472 classname: bpy.props.EnumProperty(items=enum_pointents, name="Class", \
1473 update=cxr_entity_changeclass, default='NONE' )
1474
1475 brushclass: bpy.props.EnumProperty(items=enum_brushents, name="Class", \
1476 update=cxr_entity_changeclass, default='NONE' )
1477
1478 class CXR_MODEL_SETTINGS(bpy.types.PropertyGroup):
1479 last_hash: bpy.props.StringProperty( name="" )
1480 asset_id: bpy.props.IntProperty(name="vmf_settings",default=0)
1481
1482 class CXR_SCENE_SETTINGS(bpy.types.PropertyGroup):
1483 project_name: bpy.props.StringProperty( name="Project Name" )
1484 subdir: bpy.props.StringProperty( name="Subdirectory" )
1485
1486 exe_studiomdl: bpy.props.StringProperty( name="studiomdl" )
1487 exe_vbsp: bpy.props.StringProperty( name="vbsp" )
1488 opt_vbsp: bpy.props.StringProperty( name="args" )
1489 exe_vvis: bpy.props.StringProperty( name="vvis" )
1490 opt_vvis: bpy.props.StringProperty( name="args" )
1491 exe_vrad: bpy.props.StringProperty( name="vrad" )
1492 opt_vrad: bpy.props.StringProperty( name="args" )
1493
1494 debug: bpy.props.BoolProperty(name="Debug",default=False)
1495 scale_factor: bpy.props.FloatProperty(name="VMF Scale factor",default=32.0,min=1.0)
1496 skybox_scale_factor: bpy.props.FloatProperty(name="Sky Scale factor",default=1.0,min=0.01)
1497 skybox_offset: bpy.props.FloatProperty(name="Sky offset",default=-4096.0)
1498 light_scale: bpy.props.FloatProperty(name="Light Scale",default=1.0/5.0)
1499 displacement_cardinal: bpy.props.BoolProperty(name="Cardinal displacements",default=True)
1500 include_names: bpy.props.BoolProperty(name="Append original file names",default=True)
1501 lightmap_scale: bpy.props.IntProperty(name="Global Lightmap Scale",default=12)
1502
1503 class CXR_DETECT_COMPILERS(bpy.types.Operator):
1504 bl_idname="convexer.detect_compilers"
1505 bl_label="Find compilers"
1506
1507 def execute(self,context):
1508 scene = context.scene
1509 settings = scene.cxr_data
1510 subdir = settings.subdir
1511
1512 for exename in ['studiomdl','vbsp','vvis','vrad']:
1513 searchpath = os.path.normpath(F'{subdir}/../bin/{exename}.exe')
1514 if os.path.exists(searchpath):
1515 settings[F'exe_{exename}'] = searchpath
1516
1517 return {'FINISHED'}
1518
1519 classes = [ CXR_RELOAD, CXR_DECOMPOSE_SOLID, CXR_INTERFACE, \
1520 CXR_WRITE_VMF, CXR_MATERIAL_PANEL, CXR_IMAGE_SETTINGS,\
1521 CXR_MODEL_SETTINGS, CXR_ENTITY_SETTINGS, CXR_CUBEMAP_SETTINGS,\
1522 CXR_LIGHT_SETTINGS, CXR_SCENE_SETTINGS, CXR_DETECT_COMPILERS,\
1523 CXR_ENTITY_PANEL, CXR_LIGHT_PANEL ]
1524
1525 def register():
1526 global debug_draw_handler, vmt_param_dynamic_class
1527
1528 for c in classes:
1529 bpy.utils.register_class(c)
1530
1531 # Build dynamic VMT properties class defined by cxr_shader_params
1532 annotations_dict = {}
1533
1534 def _dvmt_propogate(layer):
1535 nonlocal annotations_dict
1536
1537 for decl in layer:
1538 if isinstance(layer[decl],dict): # $property definition
1539 pdef = layer[decl]
1540
1541 prop = None
1542 if pdef['type'] == 'bool':
1543 prop = bpy.props.BoolProperty(\
1544 name = pdef['name'],\
1545 default = pdef['default'])
1546
1547 elif pdef['type'] == 'float':
1548 prop = bpy.props.FloatProperty(\
1549 name = pdef['name'],\
1550 default = pdef['default'])
1551
1552 elif pdef['type'] == 'vector':
1553 if 'subtype' in pdef:
1554 prop = bpy.props.FloatVectorProperty(\
1555 name = pdef['name'],\
1556 subtype = pdef['subtype'],\
1557 default = pdef['default'],\
1558 size = len(pdef['default']))
1559 else:
1560 prop = bpy.props.FloatVectorProperty(\
1561 name = pdef['name'],\
1562 default = pdef['default'],\
1563 size = len(pdef['default']))
1564
1565 elif pdef['type'] == 'string':
1566 prop = bpy.props.StringProperty(\
1567 name = pdef['name'],\
1568 default = pdef['default'])
1569
1570 elif pdef['type'] == 'enum':
1571 prop = bpy.props.EnumProperty(\
1572 name = pdef['name'],\
1573 items = pdef['items'],\
1574 default = pdef['default'])
1575
1576 if prop != None:
1577 annotations_dict[decl] = prop
1578
1579 # Recurse into sub-definitions
1580 _dvmt_propogate(pdef)
1581
1582 annotations_dict["shader"] = bpy.props.EnumProperty(\
1583 name = "Shader",\
1584 items = [( _,\
1585 cxr_shaders[_]["name"],\
1586 '') for _ in cxr_shaders],\
1587 default = next(iter(cxr_shaders)))
1588
1589 annotations_dict["asset_id"] = bpy.props.IntProperty(name="intl_assetid",default=0)
1590
1591 _dvmt_propogate( cxr_shader_params )
1592 vmt_param_dynamic_class = type(
1593 "CXR_VMT_DYNAMIC",
1594 (bpy.types.PropertyGroup,),{
1595 "__annotations__": annotations_dict
1596 },
1597 )
1598
1599 bpy.utils.register_class( vmt_param_dynamic_class )
1600
1601 # Pointer types
1602 bpy.types.Material.cxr_data = \
1603 bpy.props.PointerProperty(type=vmt_param_dynamic_class)
1604 bpy.types.Image.cxr_data = \
1605 bpy.props.PointerProperty(type=CXR_IMAGE_SETTINGS)
1606 bpy.types.Object.cxr_data = \
1607 bpy.props.PointerProperty(type=CXR_ENTITY_SETTINGS)
1608 bpy.types.Collection.cxr_data = \
1609 bpy.props.PointerProperty(type=CXR_MODEL_SETTINGS)
1610 bpy.types.Light.cxr_data = \
1611 bpy.props.PointerProperty(type=CXR_LIGHT_SETTINGS)
1612 bpy.types.LightProbe.cxr_data = \
1613 bpy.props.PointerProperty(type=CXR_CUBEMAP_SETTINGS)
1614 bpy.types.Scene.cxr_data = \
1615 bpy.props.PointerProperty(type=CXR_SCENE_SETTINGS)
1616
1617 # CXR Scene settings
1618
1619 # GPU / callbacks
1620 debug_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
1621 cxr_draw,(),'WINDOW','POST_VIEW')
1622
1623 bpy.app.handlers.load_post.append(cxr_on_load)
1624 bpy.app.handlers.depsgraph_update_post.append(cxr_dgraph_update)
1625
1626 def unregister():
1627 global debug_draw_handler, vmt_param_dynamic_class
1628
1629 bpy.utils.unregister_class( vmt_param_dynamic_class )
1630 for c in classes:
1631 bpy.utils.unregister_class(c)
1632
1633 bpy.app.handlers.depsgraph_update_post.remove(cxr_dgraph_update)
1634 bpy.app.handlers.load_post.remove(cxr_on_load)
1635
1636 bpy.types.SpaceView3D.draw_handler_remove(debug_draw_handler,'WINDOW')