the asumptions were of course, incorrect
[convexer.git] / __init__.py
1 # CONVEXER v0.95
2 #
3 # A GNU/Linux-first Source1 Hammer replacement
4 # built with Blender, for mapmakers
5 #
6 # Copyright (C) 2022 Harry Godden (hgn)
7 #
8 # LICENSE: GPLv3.0, please see COPYING and LICENSE for more information
9 #
10
11 bl_info = {
12 "name":"Convexer",
13 "author": "Harry Godden (hgn)",
14 "version": (0,1),
15 "blender":(3,1,0),
16 "location":"Export",
17 "descriptin":"",
18 "warning":"",
19 "wiki_url":"",
20 "category":"Import/Export",
21 }
22
23 print( "Convexer reload" )
24
25 #from mathutils import *
26 import bpy, gpu, math, os, time, mathutils, blf, subprocess, shutil, hashlib
27 from ctypes import *
28 from gpu_extras.batch import batch_for_shader
29 from bpy.app.handlers import persistent
30
31 # Setup platform dependent variables
32
33 exec(open(F'{os.path.dirname(__file__)}/platform.py').read())
34 if CXR_GNU_LINUX==1:
35 CXR_SHARED_EXT=".so"
36 CXR_EXE_EXT=""
37 else:
38 CXR_SHARED_EXT=".dll"
39 CXR_EXE_EXT=".exe"
40
41 # GPU and viewport drawing
42 # ------------------------------------------------------------------------------
43
44 # Handlers
45 cxr_view_draw_handler = None
46 cxr_ui_draw_handler = None
47
48 # Batches
49 cxr_view_lines = None
50 cxr_view_mesh = None
51 cxr_jobs_batch = None
52 cxr_jobs_inf = []
53 cxr_error_inf = None
54 cxr_test_mdl = None
55
56 cxr_asset_lib = \
57 {
58 "models": {},
59 "materials": {},
60 "textures": {}
61 }
62
63 # Shaders
64 cxr_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
65
66 cxr_ui_shader = gpu.types.GPUShader("""
67 uniform mat4 ModelViewProjectionMatrix;
68 uniform float scale;
69
70 in vec2 aPos;
71 in vec4 aColour;
72
73 out vec4 colour;
74
75 void main()
76 {
77 gl_Position = ModelViewProjectionMatrix * vec4(aPos.x*scale,aPos.y, 0.0, 1.0);
78 colour = aColour;
79 }
80 ""","""
81 in vec4 colour;
82 out vec4 FragColor;
83
84 void main()
85 {
86 FragColor = colour;
87 }
88 """)
89
90 cxr_mdl_shader = gpu.types.GPUShader("""
91 uniform mat4 modelMatrix;
92 uniform mat4 viewProjectionMatrix;
93
94 in vec3 aPos;
95 in vec3 aNormal;
96 in vec2 aUv;
97
98 out vec3 lPos;
99 out vec3 lNormal;
100 out vec2 lUv;
101
102 void main()
103 {
104 vec4 pWorldPos = modelMatrix * vec4(aPos, 1.0);
105 vec3 worldPos = pWorldPos.xyz;
106
107 gl_Position = viewProjectionMatrix * pWorldPos;
108 lNormal = normalize(mat3(transpose(inverse(modelMatrix))) * aNormal);
109 lPos = worldPos;
110 lUv = aUv;
111 }
112 ""","""
113
114 uniform vec4 colour;
115 uniform vec3 testLightDir;
116 uniform sampler2D uBasetexture;
117
118 in vec3 lNormal;
119 in vec3 lPos;
120 in vec2 lUv;
121
122 out vec4 FragColor;
123
124 float SoftenCosineTerm( float flDot )
125 {
126 return ( flDot + ( flDot * flDot ) ) * 0.5;
127 }
128
129 vec3 DiffuseTerm( vec3 worldNormal, vec3 lightDir )
130 {
131 float fResult = 0.0;
132 float NDotL = dot( worldNormal, lightDir );
133
134 fResult = clamp( NDotL, 0.0, 1.0 );
135 fResult = SoftenCosineTerm( fResult );
136
137 vec3 fOut = vec3( fResult, fResult, fResult );
138 return fOut;
139 }
140
141 vec3 PixelShaderDoLightingLinear( vec3 worldPos, vec3 worldNormal )
142 {
143 vec3 linearColor = vec3(0.0,0.0,0.0);
144 linearColor += DiffuseTerm( worldNormal, testLightDir );
145
146 return linearColor;
147 }
148
149 vec3 LinearToGamma( vec3 f3linear )
150 {
151 return pow( f3linear, vec3(1.0 / 2.2) );
152 }
153
154 vec3 GammaToLinear( vec3 f3gamma )
155 {
156 return pow( f3gamma, vec3(2.2) );
157 }
158
159 void main()
160 {
161 vec3 tangentSpaceNormal = vec3( 0.0, 0.0, 1.0 );
162 vec4 normalTexel = vec4(1.0,1.0,1.0,1.0);
163 vec3 colorInput = GammaToLinear( texture( uBasetexture, lUv ).rgb );
164
165 vec4 baseColor = vec4( colorInput * colour.rgb, 1.0 );
166
167 //normalTexel = tex2D( BumpmapSampler, i.detailOrBumpTexCoord );
168 //tangentSpaceNormal = 2.0 * normalTexel - 1.0;
169
170 vec3 diffuseLighting = vec3( 1.0, 1.0, 1.0 );
171
172 vec3 staticLightingColor = vec3( 0.0, 0.0, 0.0 );
173 diffuseLighting = PixelShaderDoLightingLinear( lPos, lNormal );
174
175 // multiply by .5 since we want a 50% (in gamma space) reflective surface)
176 diffuseLighting *= pow( 0.5, 2.2 );
177
178 vec3 result = diffuseLighting * baseColor.xyz;
179
180 FragColor = vec4( LinearToGamma(result), 1.0 );
181 }
182 """)
183
184 # Render functions
185 #
186 def cxr_ui(_,context):
187 global cxr_jobs_batch, cxr_ui_shader, cxr_jobs_inf, cxr_error_inf
188
189 w = gpu.state.viewport_get()[2]
190 cxr_ui_shader.bind()
191 cxr_ui_shader.uniform_float( "scale", w )
192
193 if cxr_error_inf != None:
194 err_begin = 50
195
196 if isinstance(cxr_error_inf[1],list):
197 err_begin += 20*(len(cxr_error_inf[1])-1)
198
199 blf.position(0,2,err_begin,0)
200 blf.size(0,50,48)
201 blf.color(0, 1.0,0.2,0.2,0.9)
202 blf.draw(0,cxr_error_inf[0])
203
204 blf.size(0,50,24)
205 blf.color(0, 1.0,1.0,1.0,1.0)
206
207 if isinstance(cxr_error_inf[1],list):
208 for i,inf in enumerate(cxr_error_inf[1]):
209 blf.position(0,2,err_begin-30-i*20,0)
210 blf.draw(0,inf)
211 else:
212 blf.position(0,2,err_begin-30,0)
213 blf.draw(0,cxr_error_inf[1])
214
215 elif cxr_jobs_batch != None:
216 gpu.state.blend_set('ALPHA')
217 cxr_jobs_batch.draw(cxr_ui_shader)
218
219 blf.position(0,2,50,0)
220 blf.size(0,50,48)
221 blf.color(0,1.0,1.0,1.0,1.0)
222 blf.draw(0,"Compiling")
223
224 for ji in cxr_jobs_inf:
225 blf.position(0,ji[0]*w,35,0)
226 blf.size(0,50,20)
227 blf.draw(0,ji[1])
228
229 py = 80
230 blf.size(0,50,16)
231 for ln in reversed(CXR_COMPILER_CHAIN.LOG[-25:]):
232 blf.position(0,2,py,0)
233 blf.draw(0,ln[:-1])
234 py += 16
235
236 # Something is off with TIMER,
237 # this forces the viewport to redraw before we can continue with our
238 # compilation stuff.
239
240 CXR_COMPILER_CHAIN.WAIT_REDRAW = False
241
242 def cxr_draw():
243 global cxr_view_shader, cxr_view_mesh, cxr_view_lines, cxr_mdl_shader,\
244 cxr_mdl_mesh, cxr_test_mdl
245
246 cxr_view_shader.bind()
247
248 gpu.state.depth_mask_set(False)
249 gpu.state.line_width_set(1.5)
250 gpu.state.face_culling_set('BACK')
251 gpu.state.depth_test_set('NONE')
252 gpu.state.blend_set('ALPHA')
253
254 if cxr_view_lines != None:
255 cxr_view_lines.draw( cxr_view_shader )
256
257 if cxr_view_mesh != None:
258 gpu.state.depth_test_set('LESS_EQUAL')
259 gpu.state.blend_set('ADDITIVE')
260
261 cxr_view_mesh.draw( cxr_view_shader )
262
263 # Models
264 gpu.state.depth_mask_set(True)
265 gpu.state.depth_test_set('LESS_EQUAL')
266 gpu.state.face_culling_set('FRONT')
267 gpu.state.blend_set('NONE')
268
269 cxr_mdl_shader.bind()
270 cxr_mdl_shader.uniform_float("viewProjectionMatrix", \
271 bpy.context.region_data.perspective_matrix)
272
273 if cxr_test_mdl != None:
274 cxr_mdl_shader.uniform_float('colour',(1.0,1.0,1.0,1.0))
275
276 #temp light dir
277 testmdl = bpy.context.scene.objects['target']
278 light = bpy.context.scene.objects['point']
279 relative = light.location - testmdl.location
280 relative.normalize()
281 cxr_mdl_shader.uniform_float("modelMatrix", testmdl.matrix_world)
282 cxr_mdl_shader.uniform_float("testLightDir", relative)
283
284 for part in cxr_test_mdl:
285 cxr_mdl_shader.uniform_sampler("uBasetexture", part[0]['basetexture'])
286 part[1].draw( cxr_mdl_shader )
287
288 def cxr_jobs_update_graph(jobs):
289 global cxr_jobs_batch, cxr_ui_shader, cxr_jobs_inf
290
291 cxr_jobs_inf = []
292
293 total_width = 0
294 verts = []
295 colours = []
296 indices = []
297
298 for sys in jobs:
299 total_width += sys['w']
300
301 sf = 1.0/total_width
302 cur = 0.0
303 ci = 0
304
305 for sys in jobs:
306 w = sys['w']
307 h = 30.0
308 colour = sys['colour']
309 colourwait = (colour[0],colour[1],colour[2],0.4)
310 colourrun = (colour[0]*1.5,colour[1]*1.5,colour[2]*1.5,0.5)
311 colourdone = (colour[0],colour[1],colour[2],1.0)
312
313 jobs = sys['jobs']
314 sfsub = (1.0/(len(jobs)))*w
315 i = 0
316
317 for j in jobs:
318 if j == None: colour = colourdone
319 else: colour = colourwait
320
321 px = (cur + (i)*sfsub) * sf
322 px1 = (cur + (i+1.0)*sfsub) * sf
323 i += 1
324
325 verts += [(px,0), (px, h), (px1, 0.0), (px1,h)]
326 colours += [colour,colour,colour,colour]
327 indices += [(ci+0,ci+2,ci+3),(ci+0,ci+3,ci+1)]
328 ci += 4
329
330 cxr_jobs_inf += [((sf*cur), sys['title'])]
331 cur += w
332
333 cxr_jobs_batch = batch_for_shader(
334 cxr_ui_shader, 'TRIS',
335 { "aPos": verts, "aColour": colours },
336 indices = indices
337 )
338
339 # view_layer.update() doesnt seem to work,
340 # tag_redraw() seems to have broken
341 # therefore, change a property
342 def scene_redraw():
343 ob = bpy.context.scene.objects[0]
344 ob.hide_render = ob.hide_render
345
346 # the 'real' way to refresh the scene
347 for area in bpy.context.window.screen.areas:
348 if area.type == 'view_3d':
349 area.tag_redraw()
350
351 # Shared libraries
352 # ------------------------------------------------------------------------------
353
354 if CXR_GNU_LINUX==1:
355 # dlclose for reloading modules manually
356 libc_dlclose = None
357 libc_dlclose = cdll.LoadLibrary(None).dlclose
358 libc_dlclose.argtypes = [c_void_p]
359
360 # wrapper for ctypes binding
361 class extern():
362 def __init__(_,name,argtypes,restype):
363 _.name = name
364 _.argtypes = argtypes
365 _.restype = restype
366 _.call = None
367
368 def loadfrom(_,so):
369 _.call = getattr(so,_.name)
370 _.call.argtypes = _.argtypes
371
372 if _.restype != None:
373 _.call.restype = _.restype
374
375 # libcxr (convexer)
376 # ------------------------------------------------------------------------------
377
378 libcxr = None
379
380 # Structure definitions
381 #
382 class cxr_edge(Structure):
383 _fields_ = [("i0",c_int32),
384 ("i1",c_int32),
385 ("freestyle",c_int32),
386 ("sharp",c_int32)]
387
388 class cxr_static_loop(Structure):
389 _fields_ = [("index",c_int32),
390 ("edge_index",c_int32),
391 ("uv",c_double * 2),
392 ("alpha",c_double)]
393
394 class cxr_polygon(Structure):
395 _fields_ = [("loop_start",c_int32),
396 ("loop_total",c_int32),
397 ("normal",c_double * 3),
398 ("center",c_double * 3),
399 ("material_id",c_int32)]
400
401 class cxr_material(Structure):
402 _fields_ = [("res",c_int32 * 2),
403 ("name",c_char_p)]
404
405 class cxr_static_mesh(Structure):
406 _fields_ = [("vertices",POINTER(c_double * 3)),
407 ("edges",POINTER(cxr_edge)),
408 ("loops",POINTER(cxr_static_loop)),
409 ("polys",POINTER(cxr_polygon)),
410 ("materials",POINTER(cxr_material)),
411
412 ("poly_count",c_int32),
413 ("vertex_count",c_int32),
414 ("edge_count",c_int32),
415 ("loop_count",c_int32),
416 ("material_count",c_int32)]
417
418 class cxr_tri_mesh(Structure):
419 _fields_ = [("vertices",POINTER(c_double *3)),
420 ("normals",POINTER(c_double *3)),
421 ("uvs",POINTER(c_double *2)),
422 ("colours",POINTER(c_double *4)),
423 ("indices",POINTER(c_int32)),
424 ("indices_count",c_int32),
425 ("vertex_count",c_int32)]
426
427 class cxr_visgroup(Structure):
428 _fields_ = [("name",c_char_p)]
429
430 class cxr_vmf_context(Structure):
431 _fields_ = [("mapversion",c_int32),
432 ("skyname",c_char_p),
433 ("detailvbsp",c_char_p),
434 ("detailmaterial",c_char_p),
435 ("visgroups",POINTER(cxr_visgroup)),
436 ("visgroup_count",c_int32),
437 ("scale",c_double),
438 ("offset",c_double *3),
439 ("lightmap_scale",c_int32),
440 ("visgroupid",c_int32),
441 ("brush_count",c_int32),
442 ("entity_count",c_int32),
443 ("face_count",c_int32)]
444
445 # Valve wrapper types
446 class fs_locator(Structure):
447 _fields_ = [("vpk_entry",c_void_p),
448 ("path",c_char_p*1024)]
449
450 class valve_material(Structure):
451 _fields_ = [("basetexture",c_char_p),
452 ("bumpmap",c_char_p)]
453
454 class valve_model_batch(Structure):
455 _fields_ = [("material",c_uint32),
456 ("ibstart",c_uint32),
457 ("ibcount",c_uint32)]
458
459 class valve_model(Structure):
460 _fields_ = [("vertex_data",POINTER(c_float)),
461 ("indices",POINTER(c_uint32)),
462 ("indices_count",c_uint32),
463 ("vertex_count",c_uint32),
464 ("part_count",c_uint32),
465 ("material_count",c_uint32),
466 ("materials",POINTER(c_char_p)),
467 ("parts",POINTER(valve_model_batch)),
468 ("studiohdr",c_void_p),
469 ("vtxhdr",c_void_p),
470 ("vvdhdr",c_void_p)]
471
472 # Convert blenders mesh format into CXR's static format (they are very similar)
473 #
474 def mesh_cxr_format(obj):
475 orig_state = None
476
477 if bpy.context.active_object != None:
478 orig_state = obj.mode
479 if orig_state != 'OBJECT':
480 bpy.ops.object.mode_set(mode='OBJECT')
481
482 dgraph = bpy.context.evaluated_depsgraph_get()
483 data = obj.evaluated_get(dgraph).data
484
485 _,mtx_rot,_ = obj.matrix_world.decompose()
486
487 mesh = cxr_static_mesh()
488
489 vertex_data = ((c_double*3)*len(data.vertices))()
490 for i, vert in enumerate(data.vertices):
491 v = obj.matrix_world @ vert.co
492 vertex_data[i][0] = c_double(v[0])
493 vertex_data[i][1] = c_double(v[1])
494 vertex_data[i][2] = c_double(v[2])
495
496 loop_data = (cxr_static_loop*len(data.loops))()
497 polygon_data = (cxr_polygon*len(data.polygons))()
498
499 for i, poly in enumerate(data.polygons):
500 loop_start = poly.loop_start
501 loop_end = poly.loop_start + poly.loop_total
502 for loop_index in range(loop_start, loop_end):
503 loop = data.loops[loop_index]
504 loop_data[loop_index].index = loop.vertex_index
505 loop_data[loop_index].edge_index = loop.edge_index
506
507 if data.uv_layers:
508 uv = data.uv_layers.active.data[loop_index].uv
509 loop_data[loop_index].uv[0] = c_double(uv[0])
510 loop_data[loop_index].uv[1] = c_double(uv[1])
511 else:
512 loop_data[loop_index].uv[0] = c_double(0.0)
513 loop_data[loop_index].uv[1] = c_double(0.0)
514
515 if data.vertex_colors:
516 alpha = data.vertex_colors.active.data[loop_index].color[0]
517 else:
518 alpha = 0.0
519
520 loop_data[loop_index].alpha = alpha
521
522 center = obj.matrix_world @ poly.center
523 normal = mtx_rot @ poly.normal
524
525 polygon_data[i].loop_start = poly.loop_start
526 polygon_data[i].loop_total = poly.loop_total
527 polygon_data[i].normal[0] = normal[0]
528 polygon_data[i].normal[1] = normal[1]
529 polygon_data[i].normal[2] = normal[2]
530 polygon_data[i].center[0] = center[0]
531 polygon_data[i].center[1] = center[1]
532 polygon_data[i].center[2] = center[2]
533 polygon_data[i].material_id = poly.material_index
534
535 edge_data = (cxr_edge*len(data.edges))()
536
537 for i, edge in enumerate(data.edges):
538 edge_data[i].i0 = edge.vertices[0]
539 edge_data[i].i1 = edge.vertices[1]
540 edge_data[i].freestyle = edge.use_freestyle_mark
541 edge_data[i].sharp = edge.use_edge_sharp
542
543 material_data = (cxr_material*len(obj.material_slots))()
544
545 for i, ms in enumerate(obj.material_slots):
546 inf = material_info(ms.material)
547 material_data[i].res[0] = inf['res'][0]
548 material_data[i].res[1] = inf['res'][1]
549 material_data[i].name = inf['name'].encode('utf-8')
550
551 mesh.edges = cast(edge_data, POINTER(cxr_edge))
552 mesh.vertices = cast(vertex_data, POINTER(c_double*3))
553 mesh.loops = cast(loop_data,POINTER(cxr_static_loop))
554 mesh.polys = cast(polygon_data, POINTER(cxr_polygon))
555 mesh.materials = cast(material_data, POINTER(cxr_material))
556
557 mesh.poly_count = len(data.polygons)
558 mesh.vertex_count = len(data.vertices)
559 mesh.edge_count = len(data.edges)
560 mesh.loop_count = len(data.loops)
561 mesh.material_count = len(obj.material_slots)
562
563 if orig_state != None:
564 bpy.ops.object.mode_set(mode=orig_state)
565
566 return mesh
567
568 # Callback ctypes indirection things.. not really sure.
569 c_libcxr_log_callback = None
570 c_libcxr_line_callback = None
571
572 # Public API
573 # -------------------------------------------------------------
574 libcxr_decompose = extern( "cxr_decompose",
575 [POINTER(cxr_static_mesh), POINTER(c_int32)],
576 c_void_p
577 )
578 libcxr_free_world = extern( "cxr_free_world",
579 [c_void_p],
580 None
581 )
582 libcxr_write_test_data = extern( "cxr_write_test_data",
583 [POINTER(cxr_static_mesh)],
584 None
585 )
586 libcxr_world_preview = extern( "cxr_world_preview",
587 [c_void_p],
588 POINTER(cxr_tri_mesh)
589 )
590 libcxr_free_tri_mesh = extern( "cxr_free_tri_mesh",
591 [c_void_p],
592 None
593 )
594 libcxr_begin_vmf = extern( "cxr_begin_vmf",
595 [POINTER(cxr_vmf_context), c_void_p],
596 None
597 )
598 libcxr_vmf_begin_entities = extern( "cxr_vmf_begin_entities",
599 [POINTER(cxr_vmf_context), c_void_p],
600 None
601 )
602 libcxr_push_world_vmf = extern("cxr_push_world_vmf",
603 [c_void_p,POINTER(cxr_vmf_context),c_void_p],
604 None
605 )
606 libcxr_end_vmf = extern( "cxr_end_vmf",
607 [POINTER(cxr_vmf_context),c_void_p],
608 None
609 )
610
611 # VDF + with open wrapper
612 libcxr_vdf_open = extern( "cxr_vdf_open", [c_char_p], c_void_p )
613 libcxr_vdf_close = extern( "cxr_vdf_close", [c_void_p], None )
614 libcxr_vdf_put = extern( "cxr_vdf_put", [c_void_p,c_char_p], None )
615 libcxr_vdf_node = extern( "cxr_vdf_node", [c_void_p,c_char_p], None )
616 libcxr_vdf_edon = extern( "cxr_vdf_edon", [c_void_p], None )
617 libcxr_vdf_kv = extern( "cxr_vdf_kv", [c_void_p,c_char_p,c_char_p], None )
618
619 class vdf_structure():
620 def __init__(_,path):
621 _.path = path
622 def __enter__(_):
623 _.fp = libcxr_vdf_open.call( _.path.encode('utf-8') )
624 if _.fp == None:
625 print( F"Could not open file {_.path}" )
626 return None
627 return _
628 def __exit__(_,type,value,traceback):
629 if _.fp != None:
630 libcxr_vdf_close.call(_.fp)
631 def put(_,s):
632 libcxr_vdf_put.call(_.fp, s.encode('utf-8') )
633 def node(_,name):
634 libcxr_vdf_node.call(_.fp, name.encode('utf-8') )
635 def edon(_):
636 libcxr_vdf_edon.call(_.fp)
637 def kv(_,k,v):
638 libcxr_vdf_kv.call(_.fp, k.encode('utf-8'), v.encode('utf-8'))
639
640 # Other
641 libcxr_lightpatch_bsp = extern( "cxr_lightpatch_bsp", [c_char_p], None )
642
643 # Binary file formats and FS
644 libcxr_fs_set_gameinfo = extern( "cxr_fs_set_gameinfo", [c_char_p], c_int32 )
645 libcxr_fs_exit = extern( "cxr_fs_exit", [], None )
646 libcxr_fs_get = extern( "cxr_fs_get", [c_char_p, c_int32], c_void_p )
647 libcxr_fs_free = extern( "cxr_fs_free", [c_void_p], None )
648 libcxr_fs_find = extern( "cxr_fs_find", [c_char_p, POINTER(fs_locator)],\
649 c_int32 )
650
651 libcxr_valve_load_model = extern( "valve_load_model", [c_char_p], \
652 POINTER(valve_model) )
653 libcxr_valve_free_model = extern( "valve_free_model", [POINTER(valve_model)],\
654 None )
655
656 libcxr_valve_load_material = extern( "valve_load_material", [c_char_p], \
657 POINTER(valve_material) )
658 libcxr_valve_free_material = extern( "valve_free_material", \
659 [POINTER(valve_material)], None )
660
661 libcxr_funcs = [ libcxr_decompose, libcxr_free_world, libcxr_begin_vmf, \
662 libcxr_vmf_begin_entities, libcxr_push_world_vmf, \
663 libcxr_end_vmf, libcxr_vdf_open, libcxr_vdf_close, \
664 libcxr_vdf_put, libcxr_vdf_node, libcxr_vdf_edon, \
665 libcxr_vdf_kv, libcxr_lightpatch_bsp, libcxr_write_test_data,\
666 libcxr_world_preview, libcxr_free_tri_mesh, \
667 libcxr_fs_set_gameinfo, libcxr_fs_exit, libcxr_fs_get, \
668 libcxr_fs_find, libcxr_fs_free, \
669 libcxr_valve_load_model, libcxr_valve_free_model,\
670 libcxr_valve_load_material, libcxr_valve_free_material ]
671
672 # Callbacks
673 def libcxr_log_callback(logStr):
674 print( F"{logStr.decode('utf-8')}",end='' )
675
676 cxr_line_positions = None
677 cxr_line_colours = None
678
679 def cxr_reset_lines():
680 global cxr_line_positions, cxr_line_colours
681
682 cxr_line_positions = []
683 cxr_line_colours = []
684
685 def cxr_batch_lines():
686 global cxr_line_positions, cxr_line_colours, cxr_view_shader, cxr_view_lines
687
688 cxr_view_lines = batch_for_shader(\
689 cxr_view_shader, 'LINES',\
690 { "pos": cxr_line_positions, "color": cxr_line_colours })
691
692 def libcxr_line_callback( p0,p1,colour ):
693 global cxr_line_colours, cxr_line_positions
694
695 cxr_line_positions += [(p0[0],p0[1],p0[2])]
696 cxr_line_positions += [(p1[0],p1[1],p1[2])]
697 cxr_line_colours += [(colour[0],colour[1],colour[2],colour[3])]
698 cxr_line_colours += [(colour[0],colour[1],colour[2],colour[3])]
699
700 def cxr_reset_all():
701 global cxr_jobs_inf, cxr_jobs_batch, cxr_error_inf, cxr_view_mesh, \
702 cxr_asset_lib
703 cxr_jobs_inf = None
704 cxr_jobs_batch = None
705 cxr_error_inf = None
706
707 cxr_reset_lines()
708 cxr_batch_lines()
709 cxr_view_mesh = None
710
711 cxr_asset_lib['models'] = {}
712 cxr_asset_lib['materials'] = {}
713 cxr_asset_lib['textures'] = {}
714
715 scene_redraw()
716
717 # libnbvtf
718 # ------------------------------------------------------------------------------
719
720 libnbvtf = None
721
722 # Constants
723 NBVTF_IMAGE_FORMAT_ABGR8888 = 1
724 NBVTF_IMAGE_FORMAT_BGR888 = 3
725 NBVTF_IMAGE_FORMAT_DXT1 = 13
726 NBVTF_IMAGE_FORMAT_DXT5 = 15
727 NBVTF_TEXTUREFLAGS_CLAMPS = 0x00000004
728 NBVTF_TEXTUREFLAGS_CLAMPT = 0x00000008
729 NBVTF_TEXTUREFLAGS_NORMAL = 0x00000080
730 NBVTF_TEXTUREFLAGS_NOMIP = 0x00000100
731 NBVTF_TEXTUREFLAGS_NOLOD = 0x00000200
732
733 libnbvtf_convert = extern( "nbvtf_convert", \
734 [c_char_p,c_int32,c_int32,c_int32,c_int32,c_int32,c_uint32,c_char_p], \
735 c_int32 )
736
737 libnbvtf_read = extern( "nbvtf_read", \
738 [c_void_p,POINTER(c_int32),POINTER(c_int32), c_int32], \
739 POINTER(c_uint8) )
740
741 libnbvtf_free = extern( "nbvtf_free", [POINTER(c_uint8)], None )
742
743 libnbvtf_init = extern( "nbvtf_init", [], None )
744 libnbvtf_funcs = [ libnbvtf_convert, libnbvtf_init, libnbvtf_read, \
745 libnbvtf_free ]
746
747 # Loading
748 # --------------------------
749
750 def shared_reload():
751 global libcxr, libnbvtf, libcxr_funcs, libnbvtf_funcs
752
753 # Unload libraries if existing
754 def _reload( lib, path ):
755 if CXR_GNU_LINUX==1:
756 if lib != None:
757 _handle = lib._handle
758 for i in range(10): libc_dlclose( _handle )
759 lib = None
760 del lib
761
762 libpath = F'{os.path.dirname(__file__)}/{path}{CXR_SHARED_EXT}'
763 return cdll.LoadLibrary( libpath )
764
765 libnbvtf = _reload( libnbvtf, "libnbvtf" )
766 libcxr = _reload( libcxr, "libcxr" )
767
768 for fd in libnbvtf_funcs:
769 fd.loadfrom( libnbvtf )
770 libnbvtf_init.call()
771
772 for fd in libcxr_funcs:
773 fd.loadfrom( libcxr )
774
775 # Callbacks
776 global c_libcxr_log_callback, c_libcxr_line_callback
777
778 LOG_FUNCTION_TYPE = CFUNCTYPE(None,c_char_p)
779 c_libcxr_log_callback = LOG_FUNCTION_TYPE(libcxr_log_callback)
780
781 LINE_FUNCTION_TYPE = CFUNCTYPE(None,\
782 POINTER(c_double), POINTER(c_double), POINTER(c_double))
783 c_libcxr_line_callback = LINE_FUNCTION_TYPE(libcxr_line_callback)
784
785 libcxr.cxr_set_log_function(cast(c_libcxr_log_callback,c_void_p))
786 libcxr.cxr_set_line_function(cast(c_libcxr_line_callback,c_void_p))
787
788 build_time = c_char_p.in_dll(libcxr,'cxr_build_time')
789 print( F"libcxr build time: {build_time.value}" )
790
791 shared_reload()
792
793 # Configuration
794 # ------------------------------------------------------------------------------
795
796 # Standard entity functions, think of like base.fgd
797 #
798 def cxr_get_origin(context):
799 return context['object'].location * context['transform']['scale'] + \
800 mathutils.Vector(context['transform']['offset'])
801
802 def cxr_get_angles(context):
803 obj = context['object']
804 euler = [ a*57.295779513 for a in obj.rotation_euler ]
805 angle = [0,0,0]
806 angle[0] = euler[1]
807 angle[1] = euler[2]
808 angle[2] = euler[0]
809 return angle
810
811 def cxr_baseclass(classes, other):
812 base = other.copy()
813 for x in classes:
814 base.update(x.copy())
815 return base
816
817 def ent_soundscape(context):
818 obj = context['object']
819 kvs = cxr_baseclass([ent_origin],\
820 {
821 "radius": obj.scale.x * bpy.context.scene.cxr_data.scale_factor,
822 "soundscape": {"type":"string","default":""}
823 })
824
825 return kvs
826
827 # EEVEE Light component converter -> Source 1
828 #
829 def ent_lights(context):
830 obj = context['object']
831 kvs = cxr_baseclass([ent_origin],\
832 {
833 "_distance": (0.0 if obj.data.cxr_data.realtime else -1.0),
834 "_lightHDR": '-1 -1 -1 1',
835 "_lightscaleHDR": 1
836 })
837
838 light_base = [(pow(obj.data.color[i],1.0/2.2)*255.0) for i in range(3)] +\
839 [obj.data.energy * bpy.context.scene.cxr_data.light_scale]
840
841 if obj.data.type == 'SPOT' or obj.data.type == 'SUN':
842 # Blenders directional lights are -z forward
843 # Source is +x, however, it seems to use a completely different system.
844 # Since we dont care about roll for spotlights, we just take the
845 # pitch and yaw via trig
846
847 _,mtx_rot,_ = obj.matrix_world.decompose()
848 fwd = mtx_rot @ mathutils.Vector((0,0,-1))
849 dir_pitch = math.asin(fwd[2]) * 57.295779513
850 dir_yaw = math.atan2(fwd[1],fwd[0]) * 57.295779513
851
852 if obj.data.type == 'SPOT':
853 kvs['_light'] = [ int(x) for x in light_base ]
854 kvs['_cone'] = obj.data.spot_size*(57.295779513/2.0)
855 kvs['_inner_cone'] = (1.0-obj.data.spot_blend)*kvs['_cone']
856
857 kvs['pitch'] = dir_pitch
858 kvs['angles'] = [ 0, dir_yaw, 0 ]
859 kvs['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
860 # Really bad...
861 #
862 # Blender's default has a much more 'nice'
863 # look.
864 kvs['_linear_attn'] = 1.0
865
866 elif obj.data.type == 'POINT':
867 kvs['_light'] = [ int(x) for x in light_base]
868 kvs['_quadratic_attn'] = 1.0
869 kvs['_linear_attn'] = 1.0
870
871 elif obj.data.type == 'SUN':
872 light_base[3] *= 300.0 * 5
873 kvs['_light'] = [ int(x) for x in light_base ]
874
875 ambient = bpy.context.scene.world.color
876 kvs['_ambient'] = [int(pow(ambient[i],1.0/2.2)*255.0) for i in range(3)] +\
877 [80 * 5]
878 kvs['_ambientHDR'] = [-1,-1,-1,1]
879 kvs['_AmbientScaleHDR'] = 1
880 kvs['pitch'] = dir_pitch
881 kvs['angles'] = [ dir_pitch, dir_yaw, 0.0 ]
882 kvs['SunSpreadAngle'] = 0
883
884 return kvs
885
886 def ent_prop(context):
887 if isinstance( context['object'], bpy.types.Collection ):
888 kvs = {}
889 target = context['object']
890 pos = mathutils.Vector(context['origin'])
891 pos += mathutils.Vector(context['transform']['offset'])
892
893 kvs['origin'] = [pos[1],-pos[0],pos[2]]
894 kvs['angles'] = [0,180,0]
895 kvs['uniformscale'] = 1.0
896 else:
897 kvs = cxr_baseclass([ent_origin],{})
898 target = context['object'].instance_collection
899
900 obj = context['object']
901 euler = [ a*57.295779513 for a in obj.rotation_euler ]
902 angle = [0,0,0]
903 angle[0] = euler[1]
904 angle[1] = euler[2] + 180.0 # Dunno...
905 angle[2] = euler[0]
906
907 kvs['angles'] = angle
908 kvs['uniformscale'] = obj.scale[0]
909
910 if target.cxr_data.shadow_caster:
911 kvs['enablelightbounce'] = 1
912 kvs['disableshadows'] = 0
913 else:
914 kvs['enablelightbounce'] = 0
915 kvs['disableshadows'] = 1
916
917 kvs['fademindist'] = -1
918 kvs['fadescale'] = 1
919 kvs['model'] = F"{asset_path('models',target)}.mdl".lower()
920 kvs['renderamt'] = 255
921 kvs['rendercolor'] = [255, 255, 255]
922 kvs['skin'] = 0
923 kvs['solid'] = 6
924
925 return kvs
926
927 def ent_sky_camera(context):
928 settings = bpy.context.scene.cxr_data
929 scale = settings.scale_factor / settings.skybox_scale_factor
930
931 kvs = {
932 "origin": [_ for _ in context['transform']['offset']],
933 "angles": [ 0, 0, 0 ],
934 "fogcolor": [255, 255, 255],
935 "fogcolor2": [255, 255, 255],
936 "fogdir": [1,0,0],
937 "fogend": 2000.0,
938 "fogmaxdensity": 1,
939 "fogstart": 500.0,
940 "HDRColorScale": 1.0,
941 "scale": scale
942 }
943 return kvs
944
945 def ent_cubemap(context):
946 obj = context['object']
947 return cxr_baseclass([ent_origin], {"cubemapsize": obj.data.cxr_data.size})
948
949 ent_origin = { "origin": cxr_get_origin }
950 ent_angles = { "angles": cxr_get_angles }
951 ent_transform = cxr_baseclass( [ent_origin], ent_angles )
952
953 #include the user config
954 exec(open(F'{os.path.dirname(__file__)}/config.py').read())
955
956 # Blender state callbacks
957 # ------------------------------------------------------------------------------
958
959 @persistent
960 def cxr_on_load(dummy):
961 global cxr_view_lines, cxr_view_mesh
962
963 cxr_view_lines = None
964 cxr_view_mesh = None
965
966 @persistent
967 def cxr_dgraph_update(scene,dgraph):
968 return
969 print( F"Hallo {time.time()}" )
970
971 # Convexer compilation functions
972 # ------------------------------------------------------------------------------
973
974 # Asset path management
975
976 def asset_uid(asset):
977 if isinstance(asset,str):
978 return asset
979
980 # Create a unique ID string
981 base = "bopshei"
982 v = asset.cxr_data.asset_id
983 name = ""
984
985 if v == 0:
986 name = "a"
987 else:
988 dig = []
989
990 while v:
991 dig.append( int( v % len(base) ) )
992 v //= len(base)
993
994 for d in dig[::-1]:
995 name += base[d]
996
997 if bpy.context.scene.cxr_data.include_names:
998 name += asset.name.replace('.','_')
999
1000 return name
1001
1002 # -> <project_name>/<asset_name>
1003 def asset_name(asset):
1004 return F"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
1005
1006 # -> <subdir>/<project_name>/<asset_name>
1007 def asset_path(subdir, asset):
1008 return F"{subdir}/{asset_name(asset_uid(asset))}"
1009
1010 # -> <csgo>/<subdir>/<project_name>/<asset_name>
1011 def asset_full_path(sdir,asset):
1012 return F"{bpy.context.scene.cxr_data.subdir}/"+\
1013 F"{asset_path(sdir,asset_uid(asset))}"
1014
1015 # Decomposes mesh, and sets global error information if failed.
1016 # - returns None on fail
1017 # - returns world on success
1018 def cxr_decompose_globalerr( mesh_src ):
1019 global cxr_error_inf
1020
1021 err = c_int32(0)
1022 world = libcxr_decompose.call( mesh_src, pointer(err) )
1023
1024 if not world:
1025 cxr_view_mesh = None
1026 cxr_batch_lines()
1027
1028 cxr_error_inf = [\
1029 ("No Error", "There is no error?"),\
1030 ("Bad input", "Non manifold geometry is present in the input mesh"),\
1031 ("Bad result","An invalid manifold was generated, try to simplify"),\
1032 ("Bad result","Make sure there is a clear starting point"),\
1033 ("Bad result","Implicit vertex was invalid, try to simplify"),\
1034 ("Bad input","Non coplanar vertices are in the source mesh"),\
1035 ("Bad input","Non convex polygon is in the source mesh"),\
1036 ("Bad result","Undefined failure"),\
1037 ("Invalid Input", "Undefined failure"),\
1038 ][err.value]
1039
1040 scene_redraw()
1041
1042 return world
1043
1044 # Entity functions / infos
1045 # ------------------------
1046
1047 def cxr_collection_purpose(collection):
1048 if collection.name.startswith('.'): return None
1049 if collection.hide_render: return None
1050 if collection.name.startswith('mdl_'): return 'model'
1051 return 'group'
1052
1053 def cxr_object_purpose(obj):
1054 objpurpose = None
1055 group = None
1056
1057 def _search(collection):
1058 nonlocal objpurpose, group, obj
1059
1060 purpose = cxr_collection_purpose( collection )
1061 if purpose == None: return
1062 if purpose == 'model':
1063 for o in collection.objects:
1064 if o == obj:
1065 if o.type != 'EMPTY':
1066 objpurpose = 'model'
1067 group = collection
1068 return
1069 return
1070 for o in collection.objects:
1071 if o == obj:
1072 classname = cxr_classname(o)
1073 if classname != None:
1074 objpurpose = 'entity'
1075 if o.type == 'MESH':
1076 objpurpose = 'brush_entity'
1077 group = collection
1078 else:
1079 if o.type == 'MESH':
1080 objpurpose = 'brush'
1081 group = collection
1082 return
1083 for c in collection.children:
1084 _search(c)
1085
1086 if 'main' in bpy.data.collections:
1087 _search( bpy.data.collections['main'] )
1088
1089 if objpurpose == None and 'skybox' in bpy.data.collections:
1090 _search( bpy.data.collections['skybox'] )
1091
1092 return (group,objpurpose)
1093
1094 def cxr_intrinsic_classname(obj):
1095 if obj.type == 'LIGHT':
1096 return {
1097 'SPOT': "light_spot",
1098 'POINT': "light",
1099 'SUN': "light_environment" }[ obj.data.type ]
1100
1101 elif obj.type == 'LIGHT_PROBE':
1102 return "env_cubemap"
1103 elif obj.type == 'EMPTY':
1104 if obj.is_instancer:
1105 return "prop_static"
1106
1107 return None
1108
1109 def cxr_custom_class(obj):
1110 if obj.type == 'MESH': custom_class = obj.cxr_data.brushclass
1111 else: custom_class = obj.cxr_data.classname
1112
1113 return custom_class
1114
1115 def cxr_classname(obj):
1116 intr = cxr_intrinsic_classname(obj)
1117 if intr != None: return intr
1118
1119 custom_class = cxr_custom_class(obj)
1120 if custom_class != 'NONE':
1121 return custom_class
1122
1123 return None
1124
1125 # Returns array of:
1126 # intinsic: (k, False, value)
1127 # property: (k, True, value or default)
1128 #
1129 # Error: None
1130 #
1131 def cxr_entity_keyvalues(context):
1132 classname = context['classname']
1133 obj = context['object']
1134 if classname not in cxr_entities: return None
1135
1136 result = []
1137
1138 entdef = cxr_entities[classname]
1139 kvs = entdef['keyvalues']
1140
1141 if callable(kvs): kvs = kvs(context)
1142
1143 for k in kvs:
1144 kv = kvs[k]
1145 value = kv
1146 isprop = False
1147
1148 if isinstance(kv,dict):
1149 isprop = True
1150 value = obj[ F"cxrkv_{k}" ]
1151 else:
1152 if callable(kv):
1153 value = kv(context)
1154
1155 if isinstance(value,mathutils.Vector):
1156 value = [_ for _ in value]
1157
1158 result += [(k, isprop, value)]
1159
1160 return result
1161
1162 # Extract material information from shader graph data
1163 #
1164 def material_info(mat):
1165 info = {}
1166 info['res'] = (512,512)
1167 info['name'] = 'tools/toolsnodraw'
1168
1169 if mat == None or mat.use_nodes == False:
1170 return info
1171
1172 # Builtin shader
1173 if mat.cxr_data.shader == 'Builtin':
1174 info['name'] = mat.name
1175 return info
1176
1177 # Custom materials
1178 info['name'] = asset_name(mat)
1179
1180 # Using the cxr_graph_mapping as a reference, go through the shader
1181 # graph and gather all $props from it.
1182 #
1183 def _graph_read( node_def, node=None, depth=0 ):
1184 nonlocal mat
1185 nonlocal info
1186
1187 def _variant_apply( val ):
1188 nonlocal mat
1189
1190 if isinstance( val, list ):
1191 for shader_variant in val:
1192 if shader_variant[0] == mat.cxr_data.shader:
1193 return shader_variant[1]
1194 return val[0][1]
1195 else:
1196 return val
1197
1198 # Find rootnodes
1199 if node == None:
1200 _graph_read.extracted = []
1201
1202 for node_idname in node_def:
1203 for n in mat.node_tree.nodes:
1204 if n.name == node_idname:
1205 node_def = node_def[node_idname]
1206 node = n
1207 break
1208
1209 for link in node_def:
1210 link_def = _variant_apply( node_def[link] )
1211
1212 if isinstance( link_def, dict ):
1213 node_link = node.inputs[link]
1214
1215 if node_link.is_linked:
1216
1217 # look for definitions for the connected node type
1218 from_node = node_link.links[0].from_node
1219
1220 node_name = from_node.name.split('.')[0]
1221 if node_name in link_def:
1222 from_node_def = link_def[ node_name ]
1223
1224 _graph_read( from_node_def, from_node, depth+1 )
1225
1226 # No definition! :(
1227 # TODO: Make a warning for this?
1228
1229 else:
1230 if "default" in link_def:
1231 prop = _variant_apply( link_def['default'] )
1232 info[prop] = node_link.default_value
1233 else:
1234 prop = _variant_apply( link_def )
1235 info[prop] = getattr( node, link )
1236
1237 _graph_read(cxr_graph_mapping)
1238
1239 if "$basetexture" in info:
1240 export_res = info['$basetexture'].cxr_data.export_res
1241 info['res'] = (export_res[0], export_res[1])
1242
1243 return info
1244
1245 def vec3_min( a, b ):
1246 return mathutils.Vector((min(a[0],b[0]),min(a[1],b[1]),min(a[2],b[2])))
1247 def vec3_max( a, b ):
1248 return mathutils.Vector((max(a[0],b[0]),max(a[1],b[1]),max(a[2],b[2])))
1249
1250 def cxr_collection_center(collection, transform):
1251 BIG=999999999
1252 bounds_min = mathutils.Vector((BIG,BIG,BIG))
1253 bounds_max = mathutils.Vector((-BIG,-BIG,-BIG))
1254
1255 for obj in collection.objects:
1256 if obj.type == 'MESH':
1257 corners = [ mathutils.Vector(c) for c in obj.bound_box ]
1258
1259 for corner in [ obj.matrix_world@c for c in corners ]:
1260 bounds_min = vec3_min( bounds_min, corner )
1261 bounds_max = vec3_max( bounds_max, corner )
1262
1263 center = (bounds_min + bounds_max) / 2.0
1264
1265 origin = mathutils.Vector((-center[1],center[0],center[2]))
1266 origin *= transform['scale']
1267
1268 return origin
1269
1270 # Prepares Scene into dictionary format
1271 #
1272 def cxr_scene_collect():
1273 context = bpy.context
1274
1275 # Make sure all of our asset types have a unique ID
1276 def _uid_prepare(objtype):
1277 used_ids = [0]
1278 to_generate = []
1279 id_max = 0
1280 for o in objtype:
1281 vs = o.cxr_data
1282 if vs.asset_id in used_ids:
1283 to_generate+=[vs]
1284 else:
1285 id_max = max(id_max,vs.asset_id)
1286 used_ids+=[vs.asset_id]
1287 for vs in to_generate:
1288 id_max += 1
1289 vs.asset_id = id_max
1290 _uid_prepare(bpy.data.materials)
1291 _uid_prepare(bpy.data.images)
1292 _uid_prepare(bpy.data.collections)
1293
1294 sceneinfo = {
1295 "entities": [], # Everything with a classname
1296 "geo": [], # All meshes without a classname
1297 "heros": [] # Collections prefixed with mdl_
1298 }
1299
1300 def _collect(collection,transform):
1301 nonlocal sceneinfo
1302
1303 purpose = cxr_collection_purpose( collection )
1304 if purpose == None: return
1305 if purpose == 'model':
1306 sceneinfo['entities'] += [{
1307 "object": collection,
1308 "classname": "prop_static",
1309 "transform": transform,
1310 "origin": cxr_collection_center( collection, transform )
1311 }]
1312
1313 sceneinfo['heros'] += [{
1314 "collection": collection,
1315 "transform": transform,
1316 "origin": cxr_collection_center( collection, transform )
1317 }]
1318 return
1319
1320 for obj in collection.objects:
1321 if obj.hide_get(): continue
1322
1323 classname = cxr_classname( obj )
1324
1325 if classname != None:
1326 sceneinfo['entities'] += [{
1327 "object": obj,
1328 "classname": classname,
1329 "transform": transform
1330 }]
1331 elif obj.type == 'MESH':
1332 sceneinfo['geo'] += [{
1333 "object": obj,
1334 "transform": transform
1335 }]
1336
1337 for c in collection.children:
1338 _collect( c, transform )
1339
1340 transform_main = {
1341 "scale": context.scene.cxr_data.scale_factor,
1342 "offset": (0,0,0)
1343 }
1344
1345 transform_sky = {
1346 "scale": context.scene.cxr_data.skybox_scale_factor,
1347 "offset": (0,0,context.scene.cxr_data.skybox_offset )
1348 }
1349
1350 if 'main' in bpy.data.collections:
1351 _collect( bpy.data.collections['main'], transform_main )
1352
1353 if 'skybox' in bpy.data.collections:
1354 _collect( bpy.data.collections['skybox'], transform_sky )
1355
1356 sceneinfo['entities'] += [{
1357 "object": None,
1358 "transform": transform_sky,
1359 "classname": "sky_camera"
1360 }]
1361
1362 return sceneinfo
1363
1364 # Write VMF out to file (JOB HANDLER)
1365 #
1366 def cxr_export_vmf(sceneinfo, output_vmf):
1367 cxr_reset_lines()
1368
1369 with vdf_structure(output_vmf) as m:
1370 print( F"Write: {output_vmf}" )
1371
1372 vmfinfo = cxr_vmf_context()
1373 vmfinfo.mapversion = 4
1374
1375 #TODO: These need to be in options...
1376 vmfinfo.skyname = bpy.context.scene.cxr_data.skyname.encode('utf-8')
1377 vmfinfo.detailvbsp = b"detail.vbsp"
1378 vmfinfo.detailmaterial = b"detail/detailsprites"
1379 vmfinfo.lightmap_scale = 12
1380
1381 vmfinfo.brush_count = 0
1382 vmfinfo.entity_count = 0
1383 vmfinfo.face_count = 0
1384
1385 visgroups = (cxr_visgroup*len(cxr_visgroups))()
1386 for i, vg in enumerate(cxr_visgroups):
1387 visgroups[i].name = vg.encode('utf-8')
1388 vmfinfo.visgroups = cast(visgroups, POINTER(cxr_visgroup))
1389 vmfinfo.visgroup_count = len(cxr_visgroups)
1390
1391 libcxr_begin_vmf.call( pointer(vmfinfo), m.fp )
1392
1393 def _buildsolid( cmd ):
1394 nonlocal m
1395
1396 print( F"{vmfinfo.brush_count} :: {cmd['object'].name}" )
1397
1398 baked = mesh_cxr_format( cmd['object'] )
1399 world = cxr_decompose_globalerr( baked )
1400
1401 if world == None:
1402 return False
1403
1404 vmfinfo.scale = cmd['transform']['scale']
1405
1406 offset = cmd['transform']['offset']
1407 vmfinfo.offset[0] = offset[0]
1408 vmfinfo.offset[1] = offset[1]
1409 vmfinfo.offset[2] = offset[2]
1410
1411 if cmd['object'].cxr_data.lightmap_override > 0:
1412 vmfinfo.lightmap_scale = cmd['object'].cxr_data.lightmap_override
1413 else:
1414 vmfinfo.lightmap_scale = bpy.context.scene.cxr_data.lightmap_scale
1415
1416 libcxr_push_world_vmf.call( world, pointer(vmfinfo), m.fp )
1417 libcxr_free_world.call( world )
1418
1419 return True
1420
1421 # World geometry
1422 for brush in sceneinfo['geo']:
1423 vmfinfo.visgroupid = int(brush['object'].cxr_data.visgroup)
1424 if not _buildsolid( brush ):
1425 cxr_batch_lines()
1426 scene_redraw()
1427 return False
1428 vmfinfo.visgroupid = 0
1429
1430 libcxr_vmf_begin_entities.call(pointer(vmfinfo), m.fp)
1431
1432 # Entities
1433 for ent in sceneinfo['entities']:
1434 obj = ent['object']
1435 ctx = ent['transform']
1436 cls = ent['classname']
1437
1438 m.node( 'entity' )
1439 m.kv( 'classname', cls )
1440
1441 kvs = cxr_entity_keyvalues( ent )
1442
1443 for kv in kvs:
1444 if isinstance(kv[2], list):
1445 m.kv( kv[0], ' '.join([str(_) for _ in kv[2]]) )
1446 else: m.kv( kv[0], str(kv[2]) )
1447
1448 if obj == None:
1449 pass
1450 elif not isinstance( obj, bpy.types.Collection ):
1451 if obj.type == 'MESH':
1452 vmfinfo.visgroupid = int(obj.cxr_data.visgroup)
1453 if not _buildsolid( ent ):
1454 cxr_batch_lines()
1455 scene_redraw()
1456 return False
1457
1458 if obj != None:
1459 m.node( 'editor' )
1460 m.kv( 'visgroupid', str(obj.cxr_data.visgroup) )
1461 m.kv( 'visgroupshown', '1' )
1462 m.kv( 'visgroupautoshown', '1' )
1463 m.edon()
1464
1465 m.edon()
1466 vmfinfo.visgroupid = 0
1467
1468 print( "Done" )
1469 return True
1470
1471 # COmpile image using NBVTF and hash it (JOB HANDLER)
1472 #
1473 def compile_image(img):
1474 if img==None:
1475 return None
1476
1477 name = asset_name(img)
1478 src_path = bpy.path.abspath(img.filepath)
1479
1480 dims = img.cxr_data.export_res
1481 fmt = {
1482 'RGBA': NBVTF_IMAGE_FORMAT_ABGR8888,
1483 'DXT1': NBVTF_IMAGE_FORMAT_DXT1,
1484 'DXT5': NBVTF_IMAGE_FORMAT_DXT5,
1485 'RGB': NBVTF_IMAGE_FORMAT_BGR888
1486 }[ img.cxr_data.fmt ]
1487
1488 mipmap = img.cxr_data.mipmap
1489 lod = img.cxr_data.lod
1490 clamp = img.cxr_data.clamp
1491 flags = img.cxr_data.flags
1492
1493 q=bpy.context.scene.cxr_data.image_quality
1494
1495 userflag_hash = F"{mipmap}.{lod}.{clamp}.{flags}"
1496 file_hash = F"{name}.{os.path.getmtime(src_path)}"
1497 comphash = F"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1498
1499 if img.cxr_data.last_hash != comphash:
1500 print( F"Texture update: {img.filepath}" )
1501
1502 src = src_path.encode('utf-8')
1503 dst = (asset_full_path('materials',img)+'.vtf').encode('utf-8')
1504
1505 flags_full = flags
1506
1507 # texture setting flags
1508 if not lod: flags_full |= NBVTF_TEXTUREFLAGS_NOLOD
1509 if clamp:
1510 flags_full |= NBVTF_TEXTUREFLAGS_CLAMPS
1511 flags_full |= NBVTF_TEXTUREFLAGS_CLAMPT
1512
1513 if libnbvtf_convert.call(src,dims[0],dims[1],mipmap,fmt,q,flags_full,dst):
1514 img.cxr_data.last_hash = comphash
1515
1516 return name
1517
1518 #
1519 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1520 # job handler.
1521 #
1522 def compile_material(mat):
1523 info = material_info(mat)
1524 properties = mat.cxr_data
1525
1526 print( F"Compile {asset_full_path('materials',mat)}.vmt" )
1527 if properties.shader == 'Builtin':
1528 return []
1529
1530 props = []
1531
1532 # Walk the property tree
1533 def _mlayer( layer ):
1534 nonlocal properties, props
1535
1536 for decl in layer:
1537 if isinstance(layer[decl],dict): # $property definition
1538 pdef = layer[decl]
1539 ptype = pdef['type']
1540
1541 subdefines = False
1542 default = None
1543 prop = None
1544
1545 if 'shaders' in pdef and properties.shader not in pdef['shaders']:
1546 continue
1547
1548 # Group expansion (does it have subdefinitions?)
1549 for ch in pdef:
1550 if isinstance(pdef[ch],dict):
1551 subdefines = True
1552 break
1553
1554 expandview = False
1555
1556 if ptype == 'ui':
1557 expandview = True
1558 else:
1559 if ptype == 'intrinsic':
1560 if decl in info:
1561 prop = info[decl]
1562 else:
1563 prop = getattr(properties,decl)
1564 default = pdef['default']
1565
1566 if not isinstance(prop,str) and \
1567 not isinstance(prop,bpy.types.Image) and \
1568 hasattr(prop,'__getitem__'):
1569 prop = tuple([p for p in prop])
1570
1571 if prop != default:
1572 # write prop
1573 props += [(decl,pdef,prop)]
1574
1575 if subdefines:
1576 expandview = True
1577
1578 if expandview: _mlayer(pdef)
1579
1580 _mlayer( cxr_shader_params )
1581
1582 # Write the vmt
1583 with vdf_structure( F"{asset_full_path('materials',mat)}.vmt" ) as vmt:
1584 vmt.node( properties.shader )
1585 vmt.put( "// Convexer export\n" )
1586
1587 for pair in props:
1588 decl = pair[0]
1589 pdef = pair[1]
1590 prop = pair[2]
1591
1592 def _numeric(v):
1593 nonlocal pdef
1594 if 'exponent' in pdef: return str(pow( v, pdef['exponent'] ))
1595 else: return str(v)
1596
1597 if isinstance(prop,bpy.types.Image):
1598 vmt.kv( decl, asset_name(prop))
1599 elif isinstance(prop,bool):
1600 vmt.kv( decl, '1' if prop else '0' )
1601 elif isinstance(prop,str):
1602 vmt.kv( decl, prop )
1603 elif isinstance(prop,float) or isinstance(prop,int):
1604 vmt.kv( decl, _numeric(prop) )
1605 elif isinstance(prop,tuple):
1606 vmt.kv( decl, F"[{' '.join([_numeric(_) for _ in prop])}]" )
1607 else:
1608 vmt.put( F"// (cxr) unkown shader value type'{type(prop)}'" )
1609
1610 vmt.edon()
1611 return props
1612
1613 def cxr_modelsrc_vphys( mdl ):
1614 for obj in mdl.objects:
1615 if obj.name == F"{mdl.name}_phy":
1616 return obj
1617 return None
1618
1619 def cxr_export_modelsrc( mdl, origin, asset_dir, project_name, transform ):
1620 dgraph = bpy.context.evaluated_depsgraph_get()
1621
1622 # Compute hash value
1623 chash = asset_uid(mdl)+str(origin)+str(transform)
1624
1625 #for obj in mdl.objects:
1626 # if obj.type != 'MESH':
1627 # continue
1628
1629 # ev = obj.evaluated_get(dgraph).data
1630 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1631 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1632
1633 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1634 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1635
1636 # if ev.uv_layers.active != None:
1637 # uv_layer = ev.uv_layers.active.data
1638 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1639 # else:
1640 # srcuv=['none']
1641
1642 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1643 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1644 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1645 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1646 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1647 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1648
1649 #if chash != mdl.cxr_data.last_hash:
1650 # mdl.cxr_data.last_hash = chash
1651 # print( F"Compile: {mdl.name}" )
1652 #else:
1653 # return True
1654
1655 bpy.ops.object.select_all(action='DESELECT')
1656
1657 # Get viewlayer
1658 def _get_layer(col,name):
1659 for c in col.children:
1660 if c.name == name:
1661 return c
1662 sub = _get_layer(c,name)
1663 if sub != None:
1664 return sub
1665 return None
1666 layer = _get_layer(bpy.context.view_layer.layer_collection,mdl.name)
1667
1668 prev_state = layer.hide_viewport
1669 layer.hide_viewport=False
1670
1671 # Collect materials to be compiled, and temp rename for export
1672 mat_dict = {}
1673
1674 vphys = None
1675 for obj in mdl.objects:
1676 if obj.name == F"{mdl.name}_phy":
1677 vphys = obj
1678 continue
1679
1680 obj.select_set(state=True)
1681 for ms in obj.material_slots:
1682 if ms.material != None:
1683 if ms.material not in mat_dict:
1684 mat_dict[ms.material] = ms.material.name
1685 ms.material.name = asset_uid(ms.material)
1686 ms.material.use_nodes = False
1687
1688 uid=asset_uid(mdl)
1689 bpy.ops.export_scene.fbx( filepath=F'{asset_dir}/{uid}_ref.fbx',\
1690 check_existing=False,
1691 use_selection=True,
1692 apply_unit_scale=False,
1693 bake_space_transform=False
1694 )
1695
1696 bpy.ops.object.select_all(action='DESELECT')
1697
1698 if vphys != None:
1699 vphys.select_set(state=True)
1700 bpy.ops.export_scene.fbx( filepath=F'{asset_dir}/{uid}_phy.fbx',\
1701 check_existing=False,
1702 use_selection=True,
1703 apply_unit_scale=False,
1704 bake_space_transform=False
1705 )
1706 bpy.ops.object.select_all(action='DESELECT')
1707
1708 # Fix material names back to original
1709 for mat in mat_dict:
1710 mat.name = mat_dict[mat]
1711 mat.use_nodes = True
1712
1713 layer.hide_viewport=prev_state
1714
1715 # Write out QC file
1716 with open(F'{asset_dir}/{uid}.qc','w') as o:
1717 o.write(F'$modelname "{project_name}/{uid}"\n')
1718 #o.write(F'$scale .32\n')
1719 o.write(F'$scale {transform["scale"]/100.0}\n')
1720 o.write(F'$body _ "{uid}_ref.fbx"\n')
1721 o.write(F'$staticprop\n')
1722 o.write(F'$origin {origin[0]:.6f} {origin[1]:.6f} {origin[2]:.6f}\n')
1723
1724 if mdl.cxr_data.preserve_order:
1725 o.write(F"$preservetriangleorder\n")
1726
1727 if mdl.cxr_data.texture_shadows:
1728 o.write(F"$casttextureshadows\n")
1729
1730 o.write(F"$surfaceprop {mdl.cxr_data.surfaceprop}\n")
1731
1732 if vphys != None:
1733 o.write(F'$collisionmodel "{uid}_phy.fbx"\n')
1734 o.write("{\n")
1735 o.write(" $concave\n")
1736 o.write("}\n")
1737
1738 o.write(F'$cdmaterials {project_name}\n')
1739 o.write(F'$sequence idle {uid}_ref.fbx\n')
1740
1741 return True
1742 #
1743 # Copy bsp file (and also lightpatch it)
1744 #
1745 def cxr_patchmap( src, dst ):
1746 libcxr_lightpatch_bsp.call( src.encode('utf-8') )
1747 shutil.copyfile( src, dst )
1748 return True
1749
1750 # Convexer operators
1751 # ------------------------------------------------------------------------------
1752
1753 # Force reload of shared libraries
1754 #
1755 class CXR_RELOAD(bpy.types.Operator):
1756 bl_idname="convexer.reload"
1757 bl_label="Reload"
1758 def execute(_,context):
1759 shared_reload()
1760 return {'FINISHED'}
1761
1762 # Reset all debugging/ui information
1763 #
1764 class CXR_RESET(bpy.types.Operator):
1765 bl_idname="convexer.reset"
1766 bl_label="Reset Convexer"
1767 def execute(_,context):
1768 cxr_reset_all()
1769 return {'FINISHED'}
1770
1771 # Used for exporting data to use with ASAN builds
1772 #
1773 class CXR_DEV_OPERATOR(bpy.types.Operator):
1774 bl_idname="convexer.dev_test"
1775 bl_label="Export development data"
1776
1777 def execute(_,context):
1778 # Prepare input data
1779 mesh_src = mesh_cxr_format(context.active_object)
1780 libcxr_write_test_data.call( pointer(mesh_src) )
1781 return {'FINISHED'}
1782
1783 class CXR_INIT_FS_OPERATOR(bpy.types.Operator):
1784 bl_idname="convexer.fs_init"
1785 bl_label="Initialize filesystem"
1786
1787 def execute(_,context):
1788 gameinfo = F'{bpy.context.scene.cxr_data.subdir}/gameinfo.txt'
1789
1790 if libcxr_fs_set_gameinfo.call( gameinfo.encode('utf-8') ) == 1:
1791 print( "File system ready" )
1792 else:
1793 print( "File system failed to initialize" )
1794
1795 return {'FINISHED'}
1796
1797 def cxr_load_texture( path, is_normal ):
1798 global cxr_asset_lib
1799
1800 if path in cxr_asset_lib['textures']:
1801 return cxr_asset_lib['textures'][path]
1802
1803 print( F"cxr_load_texture( '{path}' )" )
1804
1805 pvtf = libcxr_fs_get.call( path.encode('utf-8'), 0 )
1806
1807 if not pvtf:
1808 print( "vtf failed to load" )
1809 cxr_asset_lib['textures'][path] = None
1810 return None
1811
1812 x = c_int32(0)
1813 y = c_int32(0)
1814
1815 img_data = libnbvtf_read.call( pvtf, pointer(x), pointer(y), \
1816 c_int32(is_normal) )
1817
1818 x = x.value
1819 y = y.value
1820
1821 if not img_data:
1822 print( "vtf failed to decode" )
1823 libcxr_fs_free.call( pvtf )
1824 cxr_asset_lib['textures'][path] = None
1825 return None
1826
1827 img_buf = gpu.types.Buffer('FLOAT', [x*y*4], [_/255.0 for _ in img_data[:x*y*4]])
1828
1829 tex = cxr_asset_lib['textures'][path] = \
1830 gpu.types.GPUTexture( size=(x,y), layers=0, is_cubemap=False,\
1831 format='RGBA8', data=img_buf )
1832
1833 libnbvtf_free.call( img_data )
1834 libcxr_fs_free.call( pvtf )
1835 return tex
1836
1837 def cxr_load_material( path ):
1838 global cxr_asset_lib
1839
1840 if path in cxr_asset_lib['materials']:
1841 return cxr_asset_lib['materials'][path]
1842
1843 print( F"cxr_load_material( '{path}' )" )
1844
1845 pvmt = libcxr_valve_load_material.call( path.encode( 'utf-8') )
1846
1847 if not pvmt:
1848 cxr_asset_lib['materials'][path] = None
1849 return None
1850
1851 vmt = pvmt[0]
1852 mat = cxr_asset_lib['materials'][path] = {}
1853
1854 if vmt.basetexture:
1855 mat['basetexture'] = cxr_load_texture( vmt.basetexture.decode('utf-8'), 0)
1856
1857 if vmt.bumpmap:
1858 mat['bumpmap'] = cxr_load_texture( vmt.bumpmap.decode('utf-8'), 1)
1859
1860 libcxr_valve_free_material.call( pvmt )
1861
1862 return mat
1863
1864 def cxr_load_model_full( path ):
1865 global cxr_asset_lib, cxr_mdl_shader
1866
1867 if path in cxr_asset_lib['models']:
1868 return cxr_asset_lib['models'][path]
1869
1870 pmdl = libcxr_valve_load_model.call( path.encode( 'utf-8' ) )
1871
1872 print( F"cxr_load_model_full( '{path}' )" )
1873
1874 if not pmdl:
1875 print( "Failed to load model" )
1876 cxr_asset_lib['models'][path] = None
1877 return None
1878
1879 mdl = pmdl[0]
1880
1881 # Convert our lovely interleaved vertex stream into, whatever this is.
1882 positions = [ (mdl.vertex_data[i*8+0], \
1883 mdl.vertex_data[i*8+1], \
1884 mdl.vertex_data[i*8+2]) for i in range(mdl.vertex_count) ]
1885
1886 normals = [ (mdl.vertex_data[i*8+3], \
1887 mdl.vertex_data[i*8+4], \
1888 mdl.vertex_data[i*8+5]) for i in range(mdl.vertex_count) ]
1889
1890 uvs = [ (mdl.vertex_data[i*8+6], \
1891 mdl.vertex_data[i*8+7]) for i in range(mdl.vertex_count) ]
1892
1893 fmt = gpu.types.GPUVertFormat()
1894 fmt.attr_add(id="aPos", comp_type='F32', len=3, fetch_mode='FLOAT')
1895 fmt.attr_add(id="aNormal", comp_type='F32', len=3, fetch_mode='FLOAT')
1896 fmt.attr_add(id="aUv", comp_type='F32', len=2, fetch_mode='FLOAT')
1897
1898 vbo = gpu.types.GPUVertBuf(len=mdl.vertex_count, format=fmt)
1899 vbo.attr_fill(id="aPos", data=positions )
1900 vbo.attr_fill(id="aNormal", data=normals )
1901 vbo.attr_fill(id="aUv", data=uvs )
1902
1903 batches = cxr_asset_lib['models'][path] = []
1904
1905 for p in range(mdl.part_count):
1906 part = mdl.parts[p]
1907 indices = mdl.indices[part.ibstart:part.ibstart+part.ibcount]
1908 indices = [ (indices[i*3+0],indices[i*3+1],indices[i*3+2]) \
1909 for i in range(part.ibcount//3) ]
1910
1911 ibo = gpu.types.GPUIndexBuf( type='TRIS', seq=indices )
1912
1913 batch = gpu.types.GPUBatch( type='TRIS', buf=vbo, elem=ibo )
1914 batch.program_set( cxr_mdl_shader )
1915
1916 mat_str = cast( mdl.materials[ part.material ], c_char_p )
1917 batches += [( cxr_load_material( mat_str.value.decode('utf-8') ), batch )]
1918
1919 libcxr_valve_free_model.call( pmdl )
1920
1921 return batches
1922
1923 class CXR_LOAD_MODEL_OPERATOR(bpy.types.Operator):
1924 bl_idname="convexer.model_load"
1925 bl_label="Load model"
1926
1927 def execute(_,context):
1928 global cxr_test_mdl, cxr_mdl_shader, cxr_asset_lib
1929
1930 cxr_test_mdl = cxr_load_model_full( bpy.context.scene.cxr_data.dev_mdl )
1931
1932 scene_redraw()
1933 return {'FINISHED'}
1934
1935 # UI: Preview how the brushes will looks in 3D view
1936 #
1937 class CXR_PREVIEW_OPERATOR(bpy.types.Operator):
1938 bl_idname="convexer.preview"
1939 bl_label="Preview Brushes"
1940
1941 RUNNING = False
1942
1943 def execute(_,context):
1944 global cxr_view_mesh
1945 global cxr_view_shader, cxr_view_mesh, cxr_error_inf
1946
1947 cxr_reset_all()
1948
1949 static = _.__class__
1950
1951 mesh_src = mesh_cxr_format(context.active_object)
1952 world = cxr_decompose_globalerr( mesh_src )
1953
1954 if world == None:
1955 return {'FINISHED'}
1956
1957 # Generate preview using cxr
1958 #
1959 ptrpreview = libcxr_world_preview.call( world )
1960 preview = ptrpreview[0]
1961
1962 vertices = preview.vertices[:preview.vertex_count]
1963 vertices = [(_[0],_[1],_[2]) for _ in vertices]
1964
1965 colours = preview.colours[:preview.vertex_count]
1966 colours = [(_[0],_[1],_[2],_[3]) for _ in colours]
1967
1968 indices = preview.indices[:preview.indices_count]
1969 indices = [ (indices[i*3+0],indices[i*3+1],indices[i*3+2]) \
1970 for i in range(int(preview.indices_count/3)) ]
1971
1972 cxr_view_mesh = batch_for_shader(
1973 cxr_view_shader, 'TRIS',
1974 { "pos": vertices, "color": colours },
1975 indices = indices,
1976 )
1977
1978 libcxr_free_tri_mesh.call( ptrpreview )
1979 libcxr_free_world.call( world )
1980 cxr_batch_lines()
1981 scene_redraw()
1982
1983 return {'FINISHED'}
1984
1985 # Search for VMF compiler executables in subdirectory
1986 #
1987 class CXR_DETECT_COMPILERS(bpy.types.Operator):
1988 bl_idname="convexer.detect_compilers"
1989 bl_label="Find compilers"
1990
1991 def execute(self,context):
1992 scene = context.scene
1993 settings = scene.cxr_data
1994 subdir = settings.subdir
1995
1996 for exename in ['studiomdl','vbsp','vvis','vrad']:
1997 searchpath = os.path.normpath(F'{subdir}/../bin/{exename}.exe')
1998 if os.path.exists(searchpath):
1999 settings[F'exe_{exename}'] = searchpath
2000
2001 return {'FINISHED'}
2002
2003 def cxr_compiler_path( compiler ):
2004 settings = bpy.context.scene.cxr_data
2005 subdir = settings.subdir
2006 path = os.path.normpath(F'{subdir}/../bin/{compiler}.exe')
2007
2008 if os.path.exists( path ): return path
2009 else: return None
2010
2011 # Compatibility layer
2012 #
2013 def cxr_temp_file( fn ):
2014 if CXR_GNU_LINUX == 1:
2015 return F"/tmp/fn"
2016 else:
2017 filepath = bpy.data.filepath
2018 directory = os.path.dirname(filepath)
2019 return F"{directory}/{fn}"
2020
2021 def cxr_winepath( path ):
2022 if CXR_GNU_LINUX == 1:
2023 return 'z:'+path.replace('/','\\')
2024 else:
2025 return path
2026
2027 # Main compile function
2028 #
2029 class CXR_COMPILER_CHAIN(bpy.types.Operator):
2030 bl_idname="convexer.chain"
2031 bl_label="Compile Chain"
2032
2033 # 'static'
2034 USER_EXIT = False
2035 SUBPROC = None
2036 TIMER = None
2037 TIMER_LAST = 0.0
2038 WAIT_REDRAW = False
2039 FILE = None
2040 LOG = []
2041
2042 JOBINFO = None
2043 JOBID = 0
2044 JOBSYS = None
2045
2046 def cancel(_,context):
2047 #global cxr_jobs_batch
2048 static = _.__class__
2049 wm = context.window_manager
2050
2051 if static.SUBPROC != None:
2052 static.SUBPROC.terminate()
2053 static.SUBPROC = None
2054
2055 if static.TIMER != None:
2056 wm.event_timer_remove( static.TIMER )
2057 static.TIMER = None
2058
2059 static.FILE.close()
2060
2061 #cxr_jobs_batch = None
2062 scene_redraw()
2063 return {'FINISHED'}
2064
2065 def modal(_,context,ev):
2066 static = _.__class__
2067
2068 if ev.type == 'TIMER':
2069 global cxr_jobs_batch, cxr_error_inf
2070
2071 if static.WAIT_REDRAW:
2072 scene_redraw()
2073 return {'PASS_THROUGH'}
2074 static.WAIT_REDRAW = True
2075
2076 if static.USER_EXIT:
2077 print( "Chain USER_EXIT" )
2078 return _.cancel(context)
2079
2080 if static.SUBPROC != None:
2081 # Deal with async modes
2082 status = static.SUBPROC.poll()
2083
2084 # Cannot redirect STDOUT through here without causing
2085 # undefined behaviour due to the Blender Python specification.
2086 #
2087 # Have to write it out to a file and read it back in.
2088 #
2089
2090 with open(cxr_temp_file("convexer_compile_log.txt"),"r") as log:
2091 static.LOG = log.readlines()
2092 if status == None:
2093 return {'PASS_THROUGH'}
2094 else:
2095 #for l in static.SUBPROC.stdout:
2096 # print( F'-> {l.decode("utf-8")}',end='' )
2097 static.SUBPROC = None
2098
2099 if status != 0:
2100 print(F'Compiler () error: {status}')
2101
2102 jobn = static.JOBSYS['jobs'][static.JOBID]
2103 cxr_error_inf = ( F"{static.JOBSYS['title']} error {status}", jobn )
2104
2105 return _.cancel(context)
2106
2107 static.JOBSYS['jobs'][static.JOBID] = None
2108 cxr_jobs_update_graph( static.JOBINFO )
2109 scene_redraw()
2110 return {'PASS_THROUGH'}
2111
2112 # Compile syncronous thing
2113 for sys in static.JOBINFO:
2114 for i,target in enumerate(sys['jobs']):
2115 if target != None:
2116
2117 if callable(sys['exec']):
2118 print( F"Run (sync): {static.JOBID} @{time.time()}" )
2119
2120 if not sys['exec'](*target):
2121 print( "Job failed" )
2122 return _.cancel(context)
2123
2124 sys['jobs'][i] = None
2125 static.JOBID += 1
2126 else:
2127 # Run external executable (wine)
2128 static.SUBPROC = subprocess.Popen( target,
2129 stdout=static.FILE,\
2130 stderr=subprocess.PIPE,\
2131 cwd=sys['cwd'])
2132 static.JOBSYS = sys
2133 static.JOBID = i
2134
2135 cxr_jobs_update_graph( static.JOBINFO )
2136 scene_redraw()
2137 return {'PASS_THROUGH'}
2138
2139 # All completed
2140 print( "All jobs completed!" )
2141 #cxr_jobs_batch = None
2142 #scene_redraw()
2143 return _.cancel(context)
2144
2145 return {'PASS_THROUGH'}
2146
2147 def invoke(_,context,event):
2148 global cxr_error_inf
2149
2150 static = _.__class__
2151 wm = context.window_manager
2152
2153 if static.TIMER != None:
2154 print("Chain exiting...")
2155 static.USER_EXIT=True
2156 return {'RUNNING_MODAL'}
2157
2158 print("Launching compiler toolchain")
2159 cxr_reset_all()
2160
2161 # Run static compilation units now (collect, vmt..)
2162 filepath = bpy.data.filepath
2163 directory = os.path.dirname(filepath)
2164 settings = bpy.context.scene.cxr_data
2165
2166 asset_dir = F"{directory}/modelsrc"
2167 material_dir = F"{settings.subdir}/materials/{settings.project_name}"
2168 model_dir = F"{settings.subdir}/models/{settings.project_name}"
2169 output_vmf = F"{directory}/{settings.project_name}.vmf"
2170
2171 bsp_local = F"{directory}/{settings.project_name}.bsp"
2172 bsp_remote = F"{settings.subdir}/maps/{settings.project_name}.bsp"
2173 bsp_packed = F"{settings.subdir}/maps/{settings.project_name}_pack.bsp"
2174 packlist = F"{directory}/{settings.project_name}_assets.txt"
2175
2176 os.makedirs( asset_dir, exist_ok=True )
2177 os.makedirs( material_dir, exist_ok=True )
2178 os.makedirs( model_dir, exist_ok=True )
2179
2180 static.FILE = open(cxr_temp_file("convexer_compile_log.txt"),"w")
2181 static.LOG = []
2182
2183 sceneinfo = cxr_scene_collect()
2184 image_jobs = []
2185 qc_jobs = []
2186
2187 # Collect materials
2188 a_materials = set()
2189 for brush in sceneinfo['geo']:
2190 for ms in brush['object'].material_slots:
2191 a_materials.add( ms.material )
2192 if ms.material.cxr_data.shader == 'VertexLitGeneric':
2193 errmat = ms.material.name
2194 errnam = brush['object'].name
2195
2196 cxr_error_inf = ( "Shader error", \
2197 F"Vertex shader ({errmat}) used on model ({errnam})" )
2198
2199 print( F"Vertex shader {errmat} used on {errnam}")
2200 scene_redraw()
2201 return {'CANCELLED'}
2202
2203 a_models = set()
2204 model_jobs = []
2205 for ent in sceneinfo['entities']:
2206 if ent['object'] == None: continue
2207
2208 if ent['classname'] == 'prop_static':
2209 obj = ent['object']
2210 if isinstance(obj,bpy.types.Collection):
2211 target = obj
2212 a_models.add( target )
2213 model_jobs += [(target, ent['origin'], asset_dir, \
2214 settings.project_name, ent['transform'])]
2215 else:
2216 target = obj.instance_collection
2217 if target in a_models:
2218 continue
2219 a_models.add( target )
2220
2221 # TODO: Should take into account collection instancing offset
2222 model_jobs += [(target, [0,0,0], asset_dir, \
2223 settings.project_name, ent['transform'])]
2224
2225 elif ent['object'].type == 'MESH':
2226 for ms in ent['object'].material_slots:
2227 a_materials.add( ms.material )
2228
2229 for mdl in a_models:
2230 uid = asset_uid(mdl)
2231 qc_jobs += [F'{uid}.qc']
2232
2233 for obj in mdl.objects:
2234 for ms in obj.material_slots:
2235 a_materials.add( ms.material )
2236 if ms.material.cxr_data.shader == 'LightMappedGeneric' or \
2237 ms.material.cxr_data.shader == 'WorldVertexTransition':
2238
2239 errmat = ms.material.name
2240 errnam = obj.name
2241
2242 cxr_error_inf = ( "Shader error", \
2243 F"Lightmapped shader ({errmat}) used on model ({errnam})" )
2244
2245 print( F"Lightmapped shader {errmat} used on {errnam}")
2246 scene_redraw()
2247 return {'CANCELLED'}
2248
2249 # Collect images
2250 for mat in a_materials:
2251 for pair in compile_material(mat):
2252 decl = pair[0]
2253 pdef = pair[1]
2254 prop = pair[2]
2255
2256 if isinstance(prop,bpy.types.Image):
2257 flags = 0
2258 if 'flags' in pdef: flags = pdef['flags']
2259 if prop not in image_jobs:
2260 image_jobs += [(prop,)]
2261 prop.cxr_data.flags = flags
2262
2263 # Create packlist
2264 with open( packlist, "w" ) as fp:
2265
2266 for mat in a_materials:
2267 if mat.cxr_data.shader == 'Builtin': continue
2268 fp.write(F"{asset_path('materials',mat)}.vmt\n")
2269 fp.write(F"{cxr_winepath(asset_full_path('materials',mat))}.vmt\n")
2270
2271 for img_job in image_jobs:
2272 img = img_job[0]
2273 fp.write(F"{asset_path('materials',img)}.vtf\n")
2274 fp.write(F"{cxr_winepath(asset_full_path('materials',img))}.vtf\n")
2275
2276 for mdl in a_models:
2277 local = asset_path('models',mdl)
2278 winep = cxr_winepath(asset_full_path('models',mdl))
2279
2280 fp.write(F"{local}.vvd\n")
2281 fp.write(F"{winep}.vvd\n")
2282 fp.write(F"{local}.dx90.vtx\n")
2283 fp.write(F"{winep}.dx90.vtx\n")
2284 fp.write(F"{local}.mdl\n")
2285 fp.write(F"{winep}.mdl\n")
2286 fp.write(F"{local}.vvd\n")
2287 fp.write(F"{winep}.vvd\n")
2288
2289 if cxr_modelsrc_vphys(mdl):
2290 fp.write(F"{local}.phy\n")
2291 fp.write(F"{winep}.phy\n")
2292
2293 # Convexer jobs
2294 static.JOBID = 0
2295 static.JOBINFO = []
2296
2297 if settings.comp_vmf:
2298 static.JOBINFO += [{
2299 "title": "Convexer",
2300 "w": 20,
2301 "colour": (0.863, 0.078, 0.235,1.0),
2302 "exec": cxr_export_vmf,
2303 "jobs": [(sceneinfo,output_vmf)]
2304 }]
2305
2306 if settings.comp_textures:
2307 if len(image_jobs) > 0:
2308 static.JOBINFO += [{
2309 "title": "Textures",
2310 "w": 40,
2311 "colour": (1.000, 0.271, 0.000,1.0),
2312 "exec": compile_image,
2313 "jobs": image_jobs
2314 }]
2315
2316 game = cxr_winepath( settings.subdir )
2317 args = [ \
2318 '-game', game, settings.project_name
2319 ]
2320
2321 # FBX stage
2322 if settings.comp_models:
2323 if len(model_jobs) > 0:
2324 static.JOBINFO += [{
2325 "title": "Batches",
2326 "w": 25,
2327 "colour": (1.000, 0.647, 0.000,1.0),
2328 "exec": cxr_export_modelsrc,
2329 "jobs": model_jobs
2330 }]
2331
2332 if len(qc_jobs) > 0:
2333 static.JOBINFO += [{
2334 "title": "StudioMDL",
2335 "w": 20,
2336 "colour": (1.000, 0.843, 0.000, 1.0),
2337 "exec": "studiomdl",
2338 "jobs": [[settings[F'exe_studiomdl']] + [\
2339 '-nop4', '-game', game, qc] for qc in qc_jobs],
2340 "cwd": asset_dir
2341 }]
2342
2343 # VBSP stage
2344 if settings.comp_compile:
2345 if not settings.opt_vbsp.startswith( 'disable' ):
2346 vbsp_opt = settings.opt_vbsp.split()
2347 static.JOBINFO += [{
2348 "title": "VBSP",
2349 "w": 25,
2350 "colour": (0.678, 1.000, 0.184,1.0),
2351 "exec": "vbsp",
2352 "jobs": [[settings[F'exe_vbsp']] + vbsp_opt + args],
2353 "cwd": directory
2354 }]
2355
2356 if not settings.opt_vvis.startswith( 'disable' ):
2357 vvis_opt = settings.opt_vvis.split()
2358 static.JOBINFO += [{
2359 "title": "VVIS",
2360 "w": 25,
2361 "colour": (0.000, 1.000, 0.498,1.0),
2362 "exec": "vvis",
2363 "jobs": [[settings[F'exe_vvis']] + vvis_opt + args ],
2364 "cwd": directory
2365 }]
2366
2367 if not settings.opt_vrad.startswith( 'disable' ):
2368 vrad_opt = settings.opt_vrad.split()
2369 static.JOBINFO += [{
2370 "title": "VRAD",
2371 "w": 25,
2372 "colour": (0.125, 0.698, 0.667,1.0),
2373 "exec": "vrad",
2374 "jobs": [[settings[F'exe_vrad']] + vrad_opt + args ],
2375 "cwd": directory
2376 }]
2377
2378 static.JOBINFO += [{
2379 "title": "CXR",
2380 "w": 5,
2381 "colour": (0.118, 0.565, 1.000,1.0),
2382 "exec": cxr_patchmap,
2383 "jobs": [(bsp_local,bsp_remote)]
2384 }]
2385
2386 if settings.comp_pack:
2387 static.JOBINFO += [{
2388 "title": "Pack",
2389 "w": 5,
2390 "colour": (0.541, 0.169, 0.886,1.0),
2391 "exec": "bspzip",
2392 "jobs": [[cxr_compiler_path("bspzip"), '-addlist', \
2393 cxr_winepath(bsp_remote),
2394 cxr_winepath(packlist),
2395 cxr_winepath(bsp_packed) ]],
2396 "cwd": directory
2397 }]
2398
2399 if len(static.JOBINFO) == 0:
2400 return {'CANCELLED'}
2401
2402 static.USER_EXIT=False
2403 static.TIMER=wm.event_timer_add(0.1,window=context.window)
2404 wm.modal_handler_add(_)
2405
2406 cxr_jobs_update_graph( static.JOBINFO )
2407 scene_redraw()
2408 return {'RUNNING_MODAL'}
2409
2410 class CXR_RESET_HASHES(bpy.types.Operator):
2411 bl_idname="convexer.hash_reset"
2412 bl_label="Reset asset hashes"
2413
2414 def execute(_,context):
2415 for c in bpy.data.collections:
2416 c.cxr_data.last_hash = F"<RESET>{time.time()}"
2417 c.cxr_data.asset_id=0
2418
2419 for t in bpy.data.images:
2420 t.cxr_data.last_hash = F"<RESET>{time.time()}"
2421 t.cxr_data.asset_id=0
2422
2423 return {'FINISHED'}
2424
2425 class CXR_COMPILE_MATERIAL(bpy.types.Operator):
2426 bl_idname="convexer.matcomp"
2427 bl_label="Recompile Material"
2428
2429 def execute(_,context):
2430 active_obj = bpy.context.active_object
2431 active_mat = active_obj.active_material
2432
2433 #TODO: reduce code dupe (L1663)
2434 for pair in compile_material(active_mat):
2435 decl = pair[0]
2436 pdef = pair[1]
2437 prop = pair[2]
2438
2439 if isinstance(prop,bpy.types.Image):
2440 flags = 0
2441 if 'flags' in pdef: flags = pdef['flags']
2442 prop.cxr_data.flags = flags
2443
2444 compile_image( prop )
2445
2446 settings = bpy.context.scene.cxr_data
2447 with open(F'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o:
2448 o.write(F'mat_reloadmaterial {asset_name(active_mat)}')
2449
2450 # TODO: Move this
2451 with open(F'{settings.subdir}/cfg/convexer.cfg','w') as o:
2452 o.write('sv_cheats 1\n')
2453 o.write('mp_warmup_pausetimer 1\n')
2454 o.write('bot_kick\n')
2455 o.write('alias cxr_reload "exec convexer_mat_update"\n')
2456
2457 return {'FINISHED'}
2458
2459 # Convexer panels
2460 # ------------------------------------------------------------------------------
2461
2462 # Helper buttons for 3d toolbox view
2463 #
2464 class CXR_VIEW3D( bpy.types.Panel ):
2465 bl_idname = "VIEW3D_PT_convexer"
2466 bl_label = "Convexer"
2467 bl_space_type = 'VIEW_3D'
2468 bl_region_type = 'UI'
2469 bl_category = "Convexer"
2470
2471 def draw(_, context):
2472 layout = _.layout
2473
2474 active_object = context.object
2475 if active_object == None: return
2476
2477 purpose = cxr_object_purpose( active_object )
2478
2479 if purpose[0] == None or purpose[1] == None:
2480 usage_str = "No purpose"
2481 else:
2482 if purpose[1] == 'model':
2483 usage_str = F'mesh in {asset_name( purpose[0] )}.mdl'
2484 else:
2485 usage_str = F'{purpose[1]} in {purpose[0].name}'
2486
2487 layout.label(text=F"Currently editing:")
2488 box = layout.box()
2489 box.label(text=usage_str)
2490
2491 if purpose[1] == 'brush' or purpose[1] == 'brush_entity':
2492 row = layout.row()
2493 row.scale_y = 2
2494 row.operator("convexer.preview")
2495
2496 row = layout.row()
2497 row.scale_y = 2
2498 row.operator("convexer.reset")
2499
2500 layout.prop( bpy.context.scene.cxr_data, "dev_mdl" )
2501 layout.operator( "convexer.model_load" )
2502
2503 # Main scene properties interface, where all the settings go
2504 #
2505 class CXR_INTERFACE(bpy.types.Panel):
2506 bl_label="Convexer"
2507 bl_idname="SCENE_PT_convexer"
2508 bl_space_type='PROPERTIES'
2509 bl_region_type='WINDOW'
2510 bl_context="scene"
2511
2512 def draw(_,context):
2513 if CXR_GNU_LINUX==1:
2514 _.layout.operator("convexer.reload")
2515 _.layout.operator("convexer.dev_test")
2516 _.layout.operator("convexer.fs_init")
2517
2518 _.layout.operator("convexer.hash_reset")
2519 settings = context.scene.cxr_data
2520
2521 _.layout.prop(settings, "scale_factor")
2522 _.layout.prop(settings, "skybox_scale_factor")
2523 _.layout.prop(settings, "skyname" )
2524 _.layout.prop(settings, "lightmap_scale")
2525 _.layout.prop(settings, "light_scale" )
2526 _.layout.prop(settings, "image_quality" )
2527
2528 box = _.layout.box()
2529
2530 box.prop(settings, "project_name")
2531 box.prop(settings, "subdir")
2532
2533 box = _.layout.box()
2534 box.operator("convexer.detect_compilers")
2535 box.prop(settings, "exe_studiomdl")
2536 box.prop(settings, "exe_vbsp")
2537 box.prop(settings, "opt_vbsp")
2538
2539 box.prop(settings, "exe_vvis")
2540 box.prop(settings, "opt_vvis")
2541
2542 box.prop(settings, "exe_vrad")
2543 box.prop(settings, "opt_vrad")
2544
2545 box = box.box()
2546 row = box.row()
2547 row.prop(settings,"comp_vmf")
2548 row.prop(settings,"comp_textures")
2549 row.prop(settings,"comp_models")
2550 row.prop(settings,"comp_compile")
2551 row.prop(settings,"comp_pack")
2552
2553 text = "Compile" if CXR_COMPILER_CHAIN.TIMER == None else "Cancel"
2554 row = box.row()
2555 row.scale_y = 3
2556 row.operator("convexer.chain", text=text)
2557
2558 row = box.row()
2559 row.scale_y = 2
2560 row.operator("convexer.reset")
2561 if CXR_COMPILER_CHAIN.TIMER != None:
2562 row.enabled = False
2563
2564 class CXR_MATERIAL_PANEL(bpy.types.Panel):
2565 bl_label="VMT Properties"
2566 bl_idname="SCENE_PT_convexer_vmt"
2567 bl_space_type='PROPERTIES'
2568 bl_region_type='WINDOW'
2569 bl_context="material"
2570
2571 def draw(_,context):
2572 active_object = bpy.context.active_object
2573 if active_object == None: return
2574
2575 active_material = active_object.active_material
2576 if active_material == None: return
2577
2578 properties = active_material.cxr_data
2579 info = material_info( active_material )
2580
2581 _.layout.label(text=F"{info['name']} @{info['res'][0]}x{info['res'][1]}")
2582 row = _.layout.row()
2583 row.prop( properties, "shader" )
2584 row.operator( "convexer.matcomp" )
2585
2586 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
2587
2588 def _mtex( name, img, uiParent ):
2589 nonlocal properties
2590
2591 box = uiParent.box()
2592 box.label( text=F'{name} "{img.filepath}"' )
2593 def _p2( x ):
2594 if ((x & (x - 1)) == 0):
2595 return x
2596 closest = 0
2597 closest_diff = 10000000
2598 for i in range(16):
2599 dist = abs((1 << i)-x)
2600 if dist < closest_diff:
2601 closest_diff = dist
2602 closest = i
2603 real = 1 << closest
2604 if x > real:
2605 return 1 << (closest+1)
2606 if x < real:
2607 return 1 << (closest-1)
2608 return x
2609
2610 if img is not None:
2611 row = box.row()
2612 row.prop( img.cxr_data, "export_res" )
2613 row.prop( img.cxr_data, "fmt" )
2614
2615 row = box.row()
2616 row.prop( img.cxr_data, "mipmap" )
2617 row.prop( img.cxr_data, "lod" )
2618 row.prop( img.cxr_data, "clamp" )
2619
2620 img.cxr_data.export_res[0] = _p2( img.cxr_data.export_res[0] )
2621 img.cxr_data.export_res[1] = _p2( img.cxr_data.export_res[1] )
2622
2623 def _mview( layer, uiParent ):
2624 nonlocal properties
2625
2626 for decl in layer:
2627 if isinstance(layer[decl],dict): # $property definition
2628 pdef = layer[decl]
2629 ptype = pdef['type']
2630
2631 thisnode = uiParent
2632 expandview = True
2633 drawthis = True
2634
2635 if ('shaders' in pdef) and \
2636 (properties.shader not in pdef['shaders']):
2637 continue
2638
2639 if ptype == 'intrinsic':
2640 if decl not in info:
2641 drawthis = False
2642
2643 if drawthis:
2644 for ch in pdef:
2645 if isinstance(pdef[ch],dict):
2646 if ptype == 'ui' or ptype == 'intrinsic':
2647 pass
2648 elif getattr(properties,decl) == pdef['default']:
2649 expandview = False
2650
2651 thisnode = uiParent.box()
2652 break
2653
2654 if ptype == 'ui':
2655 thisnode.label( text=decl )
2656 elif ptype == 'intrinsic':
2657 if isinstance(info[decl], bpy.types.Image):
2658 _mtex( decl, info[decl], thisnode )
2659 else:
2660 # hidden intrinsic value.
2661 # Means its a float array or something not an image
2662 thisnode.label(text=F"-- hidden intrinsic '{decl}' --")
2663 else:
2664 thisnode.prop(properties,decl)
2665 if expandview: _mview(pdef,thisnode)
2666
2667 _mview( cxr_shader_params, _.layout )
2668
2669 def cxr_entity_changeclass(_,context):
2670 active_object = context.active_object
2671
2672 # Create ID properties
2673 entdef = None
2674 classname = cxr_custom_class(active_object)
2675
2676 if classname in cxr_entities:
2677 entdef = cxr_entities[classname]
2678
2679 kvs = entdef['keyvalues']
2680 if callable(kvs): kvs = kvs( {'object': active_object} )
2681
2682 for k in kvs:
2683 kv = kvs[k]
2684 key = F'cxrkv_{k}'
2685
2686 if callable(kv) or not isinstance(kv,dict): continue
2687
2688 if key not in active_object:
2689 active_object[key] = kv['default']
2690 id_prop = active_object.id_properties_ui(key)
2691 id_prop.update(default=kv['default'])
2692
2693 class CXR_ENTITY_PANEL(bpy.types.Panel):
2694 bl_label="Entity Config"
2695 bl_idname="SCENE_PT_convexer_entity"
2696 bl_space_type='PROPERTIES'
2697 bl_region_type='WINDOW'
2698 bl_context="object"
2699
2700 def draw(_,context):
2701 active_object = bpy.context.active_object
2702
2703 if active_object == None: return
2704
2705 default_context = {
2706 "scale": bpy.context.scene.cxr_data.scale_factor,
2707 "offset": (0,0,0)
2708 }
2709
2710 ecn = cxr_intrinsic_classname( active_object )
2711 classname = cxr_custom_class( active_object )
2712
2713 if ecn == None:
2714 if active_object.type == 'MESH':
2715 _.layout.prop( active_object.cxr_data, 'brushclass' )
2716 else: _.layout.prop( active_object.cxr_data, 'classname' )
2717
2718 _.layout.prop( active_object.cxr_data, 'visgroup' )
2719 _.layout.prop( active_object.cxr_data, 'lightmap_override' )
2720
2721 if classname == 'NONE':
2722 return
2723 else:
2724 _.layout.label(text=F"<implementation defined ({ecn})>")
2725 _.layout.enabled=False
2726 classname = ecn
2727
2728 kvs = cxr_entity_keyvalues( {
2729 "object": active_object,
2730 "transform": default_context,
2731 "classname": classname
2732 })
2733
2734 if kvs != None:
2735 for kv in kvs:
2736 if kv[1]:
2737 _.layout.prop( active_object, F'["cxrkv_{kv[0]}"]', text=kv[0])
2738 else:
2739 row = _.layout.row()
2740 row.enabled = False
2741 row.label( text=F'{kv[0]}: {repr(kv[2])}' )
2742 else:
2743 _.layout.label( text=F"ERROR: NO CLASS DEFINITION" )
2744
2745 class CXR_LIGHT_PANEL(bpy.types.Panel):
2746 bl_label = "Source Settings"
2747 bl_idname = "LIGHT_PT_cxr"
2748 bl_space_type = 'PROPERTIES'
2749 bl_region_type = 'WINDOW'
2750 bl_context = "data"
2751
2752 def draw(self, context):
2753 layout = self.layout
2754 scene = context.scene
2755
2756 active_object = bpy.context.active_object
2757 if active_object == None: return
2758
2759 if active_object.type == 'LIGHT' or \
2760 active_object.type == 'LIGHT_PROBE':
2761
2762 properties = active_object.data.cxr_data
2763
2764 if active_object.type == 'LIGHT':
2765 layout.prop( properties, "realtime" )
2766 elif active_object.type == 'LIGHT_PROBE':
2767 layout.prop( properties, "size" )
2768
2769 class CXR_COLLECTION_PANEL(bpy.types.Panel):
2770 bl_label = "Source Settings"
2771 bl_idname = "COL_PT_cxr"
2772 bl_space_type = 'PROPERTIES'
2773 bl_region_type = 'WINDOW'
2774 bl_context = "collection"
2775
2776 def draw(self, context):
2777 layout = self.layout
2778 scene = context.scene
2779
2780 active_collection = bpy.context.collection
2781
2782 if active_collection != None:
2783 layout.prop( active_collection.cxr_data, "shadow_caster" )
2784 layout.prop( active_collection.cxr_data, "texture_shadows" )
2785 layout.prop( active_collection.cxr_data, "preserve_order" )
2786 layout.prop( active_collection.cxr_data, "surfaceprop" )
2787 layout.prop( active_collection.cxr_data, "visgroup" )
2788
2789 # Settings groups
2790 # ------------------------------------------------------------------------------
2791
2792 class CXR_IMAGE_SETTINGS(bpy.types.PropertyGroup):
2793 export_res: bpy.props.IntVectorProperty(
2794 name="",
2795 description="Texture Export Resolution",
2796 default=(512,512),
2797 min=1,
2798 max=4096,
2799 size=2)
2800
2801 fmt: bpy.props.EnumProperty(
2802 name="Format",
2803 items = [
2804 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2805 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2806 ('RGB', "RGB", "Uncompressed", '', 2),
2807 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2808 ],
2809 description="Image format",
2810 default=0)
2811
2812 last_hash: bpy.props.StringProperty( name="" )
2813 asset_id: bpy.props.IntProperty(name="intl_assetid",default=0)
2814
2815 mipmap: bpy.props.BoolProperty(name="MIP",default=True)
2816 lod: bpy.props.BoolProperty(name="LOD",default=True)
2817 clamp: bpy.props.BoolProperty(name="CLAMP",default=False)
2818 flags: bpy.props.IntProperty(name="flags",default=0)
2819
2820 class CXR_LIGHT_SETTINGS(bpy.types.PropertyGroup):
2821 realtime: bpy.props.BoolProperty(name="Realtime Light", default=True)
2822
2823 class CXR_CUBEMAP_SETTINGS(bpy.types.PropertyGroup):
2824 size: bpy.props.EnumProperty(
2825 name="Resolution",
2826 items = [
2827 ('1',"1x1",'','',0),
2828 ('2',"2x2",'','',1),
2829 ('3',"4x4",'','',2),
2830 ('4',"8x8",'','',3),
2831 ('5',"16x16",'','',4),
2832 ('6',"32x32",'','',5),
2833 ('7',"64x64",'','',6),
2834 ('8',"128x128",'','',7),
2835 ('9',"256x256",'','',8)
2836 ],
2837 description="Texture resolution",
2838 default=7)
2839
2840 class CXR_ENTITY_SETTINGS(bpy.types.PropertyGroup):
2841 entity: bpy.props.BoolProperty(name="")
2842
2843 enum_pointents = [('NONE',"None","")]
2844 enum_brushents = [('NONE',"None","")]
2845
2846 for classname in cxr_entities:
2847 entdef = cxr_entities[classname]
2848 if 'allow' in entdef:
2849 itm = [(classname, classname, "")]
2850 if 'EMPTY' in entdef['allow']: enum_pointents += itm
2851 else: enum_brushents += itm
2852
2853 classname: bpy.props.EnumProperty(items=enum_pointents, name="Class", \
2854 update=cxr_entity_changeclass, default='NONE' )
2855
2856 brushclass: bpy.props.EnumProperty(items=enum_brushents, name="Class", \
2857 update=cxr_entity_changeclass, default='NONE' )
2858
2859 enum_classes = [('0',"None","")]
2860 for i, vg in enumerate(cxr_visgroups):
2861 enum_classes += [(str(i+1),vg,"")]
2862 visgroup: bpy.props.EnumProperty(name="visgroup",items=enum_classes,default=0)
2863 lightmap_override: bpy.props.IntProperty(name="Lightmap Override",default=0)
2864
2865 class CXR_MODEL_SETTINGS(bpy.types.PropertyGroup):
2866 last_hash: bpy.props.StringProperty( name="" )
2867 asset_id: bpy.props.IntProperty(name="vmf_settings",default=0)
2868 shadow_caster: bpy.props.BoolProperty( name="Shadow caster", default=True )
2869 texture_shadows: bpy.props.BoolProperty( name="Texture Shadows", default=False )
2870 preserve_order: bpy.props.BoolProperty( name="Preserve Order", default=False )
2871 surfaceprop: bpy.props.StringProperty( name="Suface prop",default="default" )
2872
2873 enum_classes = [('0',"None","")]
2874 for i, vg in enumerate(cxr_visgroups):
2875 enum_classes += [(str(i+1),vg,"")]
2876 visgroup: bpy.props.EnumProperty(name="visgroup",items=enum_classes,default=0)
2877
2878 class CXR_SCENE_SETTINGS(bpy.types.PropertyGroup):
2879 project_name: bpy.props.StringProperty( name="Project Name" )
2880 subdir: bpy.props.StringProperty( name="../csgo/ folder" )
2881
2882 exe_studiomdl: bpy.props.StringProperty( name="studiomdl" )
2883 exe_vbsp: bpy.props.StringProperty( name="vbsp" )
2884 opt_vbsp: bpy.props.StringProperty( name="args" )
2885 exe_vvis: bpy.props.StringProperty( name="vvis" )
2886 opt_vvis: bpy.props.StringProperty( name="args" )
2887 exe_vrad: bpy.props.StringProperty( name="vrad" )
2888 opt_vrad: bpy.props.StringProperty( name="args", \
2889 default="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2890
2891 scale_factor: bpy.props.FloatProperty( name="VMF Scale factor", \
2892 default=32.0,min=1.0)
2893 skybox_scale_factor: bpy.props.FloatProperty( name="Sky Scale factor", \
2894 default=1.0,min=0.01)
2895 skyname: bpy.props.StringProperty(name="Skyname",default="sky_csgo_night02b")
2896 skybox_offset: bpy.props.FloatProperty(name="Sky offset",default=-4096.0)
2897 light_scale: bpy.props.FloatProperty(name="Light Scale",default=1.0/5.0)
2898 include_names: bpy.props.BoolProperty(name="Append original file names",\
2899 default=True)
2900 lightmap_scale: bpy.props.IntProperty(name="Global Lightmap Scale",\
2901 default=12)
2902 image_quality: bpy.props.IntProperty(name="Texture Quality (0-18)",\
2903 default=8, min=0, max=18 )
2904
2905 comp_vmf: bpy.props.BoolProperty(name="VMF",default=True)
2906 comp_models: bpy.props.BoolProperty(name="Models",default=True)
2907 comp_textures: bpy.props.BoolProperty(name="Textures",default=True)
2908 comp_compile: bpy.props.BoolProperty(name="Compile",default=True)
2909 comp_pack: bpy.props.BoolProperty(name="Pack",default=False)
2910
2911 dev_mdl: bpy.props.StringProperty(name="Model",default="")
2912
2913 classes = [ CXR_RELOAD, CXR_DEV_OPERATOR, CXR_INTERFACE, \
2914 CXR_MATERIAL_PANEL, CXR_IMAGE_SETTINGS,\
2915 CXR_MODEL_SETTINGS, CXR_ENTITY_SETTINGS, CXR_CUBEMAP_SETTINGS,\
2916 CXR_LIGHT_SETTINGS, CXR_SCENE_SETTINGS, CXR_DETECT_COMPILERS,\
2917 CXR_ENTITY_PANEL, CXR_LIGHT_PANEL, CXR_PREVIEW_OPERATOR,\
2918 CXR_VIEW3D, CXR_COMPILER_CHAIN, CXR_RESET_HASHES,\
2919 CXR_COMPILE_MATERIAL, CXR_COLLECTION_PANEL, CXR_RESET, \
2920 CXR_INIT_FS_OPERATOR, CXR_LOAD_MODEL_OPERATOR ]
2921
2922 vmt_param_dynamic_class = None
2923
2924 def register():
2925 global cxr_view_draw_handler, vmt_param_dynamic_class, cxr_ui_handler
2926
2927 for c in classes:
2928 bpy.utils.register_class(c)
2929
2930 # Build dynamic VMT properties class defined by cxr_shader_params
2931 annotations_dict = {}
2932
2933 def _dvmt_propogate(layer):
2934 nonlocal annotations_dict
2935
2936 for decl in layer:
2937 if isinstance(layer[decl],dict): # $property definition
2938 pdef = layer[decl]
2939
2940 prop = None
2941 if pdef['type'] == 'bool':
2942 prop = bpy.props.BoolProperty(\
2943 name = pdef['name'],\
2944 default = pdef['default'])
2945
2946 elif pdef['type'] == 'float':
2947 prop = bpy.props.FloatProperty(\
2948 name = pdef['name'],\
2949 default = pdef['default'])
2950
2951 elif pdef['type'] == 'vector':
2952 if 'subtype' in pdef:
2953 prop = bpy.props.FloatVectorProperty(\
2954 name = pdef['name'],\
2955 subtype = pdef['subtype'],\
2956 default = pdef['default'],\
2957 size = len(pdef['default']))
2958 else:
2959 prop = bpy.props.FloatVectorProperty(\
2960 name = pdef['name'],\
2961 default = pdef['default'],\
2962 size = len(pdef['default']))
2963
2964 elif pdef['type'] == 'string':
2965 prop = bpy.props.StringProperty(\
2966 name = pdef['name'],\
2967 default = pdef['default'])
2968
2969 elif pdef['type'] == 'enum':
2970 prop = bpy.props.EnumProperty(\
2971 name = pdef['name'],\
2972 items = pdef['items'],\
2973 default = pdef['default'])
2974
2975 if prop != None:
2976 annotations_dict[decl] = prop
2977
2978 # Recurse into sub-definitions
2979 _dvmt_propogate(pdef)
2980
2981 annotations_dict["shader"] = bpy.props.EnumProperty(\
2982 name = "Shader",\
2983 items = [( _,\
2984 cxr_shaders[_]["name"],\
2985 '') for _ in cxr_shaders],\
2986 default = next(iter(cxr_shaders)))
2987
2988 annotations_dict["asset_id"] = bpy.props.IntProperty(name="intl_assetid",\
2989 default=0)
2990
2991 _dvmt_propogate( cxr_shader_params )
2992 vmt_param_dynamic_class = type(
2993 "CXR_VMT_DYNAMIC",
2994 (bpy.types.PropertyGroup,),{
2995 "__annotations__": annotations_dict
2996 },
2997 )
2998
2999 bpy.utils.register_class( vmt_param_dynamic_class )
3000
3001 # Pointer types
3002 bpy.types.Material.cxr_data = \
3003 bpy.props.PointerProperty(type=vmt_param_dynamic_class)
3004 bpy.types.Image.cxr_data = \
3005 bpy.props.PointerProperty(type=CXR_IMAGE_SETTINGS)
3006 bpy.types.Object.cxr_data = \
3007 bpy.props.PointerProperty(type=CXR_ENTITY_SETTINGS)
3008 bpy.types.Collection.cxr_data = \
3009 bpy.props.PointerProperty(type=CXR_MODEL_SETTINGS)
3010 bpy.types.Light.cxr_data = \
3011 bpy.props.PointerProperty(type=CXR_LIGHT_SETTINGS)
3012 bpy.types.LightProbe.cxr_data = \
3013 bpy.props.PointerProperty(type=CXR_CUBEMAP_SETTINGS)
3014 bpy.types.Scene.cxr_data = \
3015 bpy.props.PointerProperty(type=CXR_SCENE_SETTINGS)
3016
3017 # CXR Scene settings
3018
3019 # GPU / callbacks
3020 cxr_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
3021 cxr_draw,(),'WINDOW','POST_VIEW')
3022
3023 cxr_ui_handler = bpy.types.SpaceView3D.draw_handler_add(\
3024 cxr_ui,(None,None),'WINDOW','POST_PIXEL')
3025
3026 bpy.app.handlers.load_post.append(cxr_on_load)
3027 bpy.app.handlers.depsgraph_update_post.append(cxr_dgraph_update)
3028
3029 def unregister():
3030 global cxr_view_draw_handler, vmt_param_dynamic_class, cxr_ui_handler
3031
3032 bpy.utils.unregister_class( vmt_param_dynamic_class )
3033 for c in classes:
3034 bpy.utils.unregister_class(c)
3035
3036 bpy.app.handlers.depsgraph_update_post.remove(cxr_dgraph_update)
3037 bpy.app.handlers.load_post.remove(cxr_on_load)
3038
3039 bpy.types.SpaceView3D.draw_handler_remove(cxr_view_draw_handler,'WINDOW')
3040 bpy.types.SpaceView3D.draw_handler_remove(cxr_ui_handler,'WINDOW')