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