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