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