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