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