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