print( "Convexer reload" )
#from mathutils import *
-import bpy, gpu, math, os, time, mathutils
+import bpy, gpu, math, os, time, mathutils, blf, subprocess, shutil, hashlib
from ctypes import *
from gpu_extras.batch import batch_for_shader
from bpy.app.handlers import persistent
-# Globals and tweaks
-vmt_param_dynamic_class = None
+# GPU and viewport drawing
+# ------------------------------------------------------------------------------
-# libcxr interface (TODO: We can probably automate this)
-# ======================================================
-libcxr = None
-libc_dlclose = None
-libc_dlclose = cdll.LoadLibrary(None).dlclose
-libc_dlclose.argtypes = [c_void_p]
+# Handlers
+cxr_view_draw_handler = None
+cxr_ui_draw_handler = None
-c_libcxr_log_callback = None
-c_libcxr_line_callback = None
+# Batches
+cxr_view_lines = None
+cxr_view_mesh = None
+cxr_jobs_batch = None
+cxr_jobs_inf = []
-libcxr_write_test_data = None
-libcxr_context_reset = None
-libcxr_set_offset = None
-libcxr_set_scale_factor = None
-
-# Vdf writing interface
-libcxr_vdf_open = None
-libcxr_vdf_close = None
-libcxr_vdf_put = None
-libcxr_vdf_node = None
-libcxr_vdf_edon = None
-libcxr_vdf_kv = None
-
-# libnbvtf interface
-# ==================
-libnbvtf = None
-libnbvtf_convert = None
+# Shaders
+cxr_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
+cxr_ui_shader = gpu.types.GPUShader("""
+uniform mat4 ModelViewProjectionMatrix;
+uniform float scale;
-# NBVTF constants
-# ===============
+in vec2 aPos;
+in vec4 aColour;
-NBVTF_IMAGE_FORMAT_RGBA8888 = 0
-NBVTF_IMAGE_FORMAT_RGB888 = 2
-NBVTF_IMAGE_FORMAT_DXT1 = 13
-NBVTF_IMAGE_FORMAT_DXT5 = 15
-NBVTF_TEXTUREFLAGS_CLAMPS = 0x00000004
-NBVTF_TEXTUREFLAGS_CLAMPT = 0x00000008
-NBVTF_TEXTUREFLAGS_NORMAL = 0x00000080
-NBVTF_TEXTUREFLAGS_NOMIP = 0x00000100
-NBVTF_TEXTUREFLAGS_NOLOD = 0x00000200
+out vec4 colour;
-# Wrapper for vdf functions to allow: with o = vdf_structure ...
-class vdf_structure():
- def __init__(_,path):
- _.path = path
- def __enter__(_):
- _.fp = libcxr_vdf_open( _.path.encode('utf-8') )
- if _.fp == None:
- print( F"Could not open file {_.path}" )
- return None
- return _
- def __exit__(_,type,value,traceback):
- if _.fp != None:
- libcxr_vdf_close(_.fp)
+void main()
+{
+ gl_Position = ModelViewProjectionMatrix * vec4(aPos.x*scale,aPos.y, 0.0, 1.0);
+ colour = aColour;
+}
+""","""
+in vec4 colour;
+out vec4 FragColor;
- def put(_,s):
- libcxr_vdf_put(_.fp, s.encode('utf-8') )
- def node(_,name):
- libcxr_vdf_node(_.fp, name.encode('utf-8') )
- def edon(_):
- libcxr_vdf_edon(_.fp)
- def kv(_,k,v):
- libcxr_vdf_kv(_.fp, k.encode('utf-8'), v.encode('utf-8'))
+void main()
+{
+ FragColor = colour;
+}
+""")
-class cxr_object_context():
- def __init__(_,scale,offset_z):
- _.scale=scale
- _.offset_z=offset_z
+# Render functions
+#
+def cxr_ui(_,context):
+ global cxr_jobs_batch, cxr_ui_shader, cxr_jobs_inf
+
+ w = gpu.state.viewport_get()[2]
+ cxr_ui_shader.bind()
+ cxr_ui_shader.uniform_float( "scale", w )
+
+ if cxr_jobs_batch != None:
+ gpu.state.blend_set('ALPHA')
+ cxr_jobs_batch.draw(cxr_ui_shader)
+
+ blf.position(0,2,50,0)
+ blf.size(0,50,48)
+ blf.color(0,1.0,1.0,1.0,1.0)
+ blf.draw(0,"Compiling")
+
+ for ji in cxr_jobs_inf:
+ blf.position(0,ji[0]*w,35,0)
+ blf.size(0,50,20)
+ blf.draw(0,ji[1])
+
+ py = 80
+ blf.size(0,50,16)
+ for ln in reversed(CXR_COMPILER_CHAIN.LOG[-25:]):
+ blf.position(0,2,py,0)
+ blf.draw(0,ln[:-1])
+ py += 16
-libcxr_convert_mesh_to_vmf = None
+ #if CXR_PREVIEW_OPERATOR.LASTERR != None:
+ # blf.position(0,2,80,0)
+ # blf.size(0,50,48)
+ # blf.color(0,1.0,0.2,0.2,0.9)
+ # blf.draw(0,"Invalid geometry")
-debug_gpu_lines = None
-debug_gpu_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
-debug_draw_handler = None
+ # Something is off with TIMER,
+ # this forces the viewport to redraw before we can continue with our
+ # compilation stuff.
-class cxr_settings(Structure):
- _fields_ = [("debug",c_int32),
- ("lightmap_scale",c_int32),
- ("light_scale",c_double)]
+ CXR_COMPILER_CHAIN.WAIT_REDRAW = False
+
+def cxr_draw():
+ global cxr_view_shader, cxr_view_mesh, cxr_view_lines
+
+ cxr_view_shader.bind()
+
+ gpu.state.depth_mask_set(False)
+ gpu.state.line_width_set(1.5)
+ gpu.state.face_culling_set('BACK')
+ gpu.state.depth_test_set('NONE')
+ gpu.state.blend_set('ALPHA')
+
+ if cxr_view_lines != None:
+ cxr_view_lines.draw( cxr_view_shader )
+
+ gpu.state.depth_test_set('LESS_EQUAL')
+ gpu.state.blend_set('ADDITIVE')
+ if cxr_view_mesh != None:
+ cxr_view_mesh.draw( cxr_view_shader )
+
+def cxr_jobs_update_graph(jobs):
+ global cxr_jobs_batch, cxr_ui_shader, cxr_jobs_inf
-class cxr_input_loop(Structure):
+ cxr_jobs_inf = []
+
+ total_width = 0
+ verts = []
+ colours = []
+ indices = []
+
+ for sys in jobs:
+ total_width += sys['w']
+
+ sf = 1.0/total_width
+ cur = 0.0
+ ci = 0
+
+ for sys in jobs:
+ w = sys['w']
+ h = 30.0
+ colour = sys['colour']
+ colourwait = (colour[0],colour[1],colour[2],0.4)
+ colourrun = (colour[0]*1.5,colour[1]*1.5,colour[2]*1.5,0.5)
+ colourdone = (colour[0],colour[1],colour[2],1.0)
+
+ jobs = sys['jobs']
+ sfsub = (1.0/(len(jobs)))*w
+ i = 0
+
+ for j in jobs:
+ if j == None: colour = colourdone
+ else: colour = colourwait
+
+ px = (cur + (i)*sfsub) * sf
+ px1 = (cur + (i+1.0)*sfsub) * sf
+ i += 1
+
+ verts += [(px,0), (px, h), (px1, 0.0), (px1,h)]
+ colours += [colour,colour,colour,colour]
+ indices += [(ci+0,ci+2,ci+3),(ci+0,ci+3,ci+1)]
+ ci += 4
+
+ cxr_jobs_inf += [((sf*cur), sys['title'])]
+ cur += w
+
+ cxr_jobs_batch = batch_for_shader(
+ cxr_ui_shader, 'TRIS',
+ { "aPos": verts, "aColour": colours },
+ indices = indices
+ )
+
+# view_layer.update() doesnt seem to work,
+# tag_redraw() seems to have broken
+# therefore, change a property
+def scene_redraw():
+ ob = bpy.context.scene.objects[0]
+ ob.hide_render = ob.hide_render
+
+ # the 'real' way to refresh the scene
+ for area in bpy.context.window.screen.areas:
+ if area.type == 'view_3d':
+ area.tag_redraw()
+
+# Shared libraries
+# ------------------------------------------------------------------------------
+
+# dlclose for reloading modules manually
+libc_dlclose = None
+libc_dlclose = cdll.LoadLibrary(None).dlclose
+libc_dlclose.argtypes = [c_void_p]
+
+# wrapper for ctypes binding
+class extern():
+ def __init__(_,name,argtypes,restype):
+ _.name = name
+ _.argtypes = argtypes
+ _.restype = restype
+ _.call = None
+
+ def loadfrom(_,so):
+ _.call = getattr(so,_.name)
+ _.call.argtypes = _.argtypes
+
+ if _.restype != None:
+ _.call.restype = _.restype
+
+# libcxr (convexer)
+# ------------------------------------------------------------------------------
+
+libcxr = None
+
+# Structure definitions
+#
+class cxr_edge(Structure):
+ _fields_ = [("i0",c_int32),
+ ("i1",c_int32),
+ ("freestyle",c_int32),
+ ("sharp",c_int32)]
+
+class cxr_static_loop(Structure):
_fields_ = [("index",c_int32),
("edge_index",c_int32),
("uv",c_double * 2)]
("center",c_double * 3),
("material_id",c_int32)]
-class cxr_edge(Structure):
- _fields_ = [("i0",c_int32),
- ("i1",c_int32),
- ("freestyle",c_int32)]
-
class cxr_material(Structure):
_fields_ = [("res",c_int32 * 2),
- ("vmt_path",c_char_p)]
+ ("name",c_char_p)]
-class cxr_input_mesh(Structure):
+class cxr_static_mesh(Structure):
_fields_ = [("vertices",POINTER(c_double * 3)),
("edges",POINTER(cxr_edge)),
- ("loops",POINTER(cxr_input_loop)),
+ ("loops",POINTER(cxr_static_loop)),
("polys",POINTER(cxr_polygon)),
("materials",POINTER(cxr_material)),
("loop_count",c_int32),
("material_count",c_int32)]
-class cxr_output_mesh(Structure):
- _fields_ = []
+class cxr_tri_mesh(Structure):
+ _fields_ = [("vertices",POINTER(c_double *3)),
+ ("colours",POINTER(c_double *4)),
+ ("indices",POINTER(c_int32)),
+ ("indices_count",c_int32),
+ ("vertex_count",c_int32)]
+
+class cxr_vmf_context(Structure):
+ _fields_ = [("mapversion",c_int32),
+ ("skyname",c_char_p),
+ ("detailvbsp",c_char_p),
+ ("detailmaterial",c_char_p),
+ ("scale",c_double),
+ ("offset",c_double *3),
+ ("lightmap_scale",c_int32),
+ ("brush_count",c_int32),
+ ("entity_count",c_int32),
+ ("face_count",c_int32)]
-def libcxr_log_callback(logStr):
- print( F"{logStr.decode('utf-8')}",end='' )
+# Convert blenders mesh format into CXR's static format (they are very similar)
+#
+def mesh_cxr_format(obj):
+ orig_state = None
-debug_lines_positions = None
-debug_lines_colours = None
+ if bpy.context.active_object != None:
+ orig_state = obj.mode
+ if orig_state != 'OBJECT':
+ bpy.ops.object.mode_set(mode='OBJECT')
-def libcxr_reset_debug_lines():
- global debug_lines_positions
- global debug_lines_colours
+ dgraph = bpy.context.evaluated_depsgraph_get()
+ data = obj.evaluated_get(dgraph).data
+
+ _,mtx_rot,_ = obj.matrix_world.decompose()
- debug_lines_positions = []
- debug_lines_colours = []
+ mesh = cxr_static_mesh()
-def libcxr_batch_debug_lines():
- global debug_lines_positions
- global debug_lines_colours
- global debug_gpu_lines
- global debug_gpu_shader
+ vertex_data = ((c_double*3)*len(data.vertices))()
+ for i, vert in enumerate(data.vertices):
+ v = obj.matrix_world @ vert.co
+ vertex_data[i][0] = c_double(v[0])
+ vertex_data[i][1] = c_double(v[1])
+ vertex_data[i][2] = c_double(v[2])
- debug_gpu_lines = batch_for_shader(\
- debug_gpu_shader, 'LINES',\
- { "pos": debug_lines_positions, "color": debug_lines_colours })
+ loop_data = (cxr_static_loop*len(data.loops))()
+ polygon_data = (cxr_polygon*len(data.polygons))()
-@persistent
-def cxr_on_load(dummy):
- libcxr_reset_debug_lines()
- libcxr_batch_debug_lines()
+ for i, poly in enumerate(data.polygons):
+ loop_start = poly.loop_start
+ loop_end = poly.loop_start + poly.loop_total
+ for loop_index in range(loop_start, loop_end):
+ loop = data.loops[loop_index]
+ loop_data[loop_index].index = loop.vertex_index
+ loop_data[loop_index].edge_index = loop.edge_index
-@persistent
-def cxr_dgraph_update(scene,dgraph):
- return
- print( F"Hallo {time.time()}" )
+ if data.uv_layers:
+ uv = data.uv_layers.active.data[loop_index].uv
+ loop_data[loop_index].uv[0] = c_double(uv[0])
+ loop_data[loop_index].uv[1] = c_double(uv[1])
+ else:
+ loop_data[loop_index].uv[0] = c_double(0.0)
+ loop_data[loop_index].uv[1] = c_double(0.0)
+ center = obj.matrix_world @ poly.center
+ normal = mtx_rot @ poly.normal
-def libcxr_line_callback(p0,p1,colour):
- global debug_lines_positions
- global debug_lines_colours
- debug_lines_positions += [(p0[0],p0[1],p0[2])]
- debug_lines_positions += [(p1[0],p1[1],p1[2])]
- debug_lines_colours += [(colour[0],colour[1],colour[2],colour[3])]
- debug_lines_colours += [(colour[0],colour[1],colour[2],colour[3])]
+ polygon_data[i].loop_start = poly.loop_start
+ polygon_data[i].loop_total = poly.loop_total
+ polygon_data[i].normal[0] = normal[0]
+ polygon_data[i].normal[1] = normal[1]
+ polygon_data[i].normal[2] = normal[2]
+ polygon_data[i].center[0] = center[0]
+ polygon_data[i].center[1] = center[1]
+ polygon_data[i].center[2] = center[2]
+ polygon_data[i].material_id = poly.material_index
-def cxr_draw():
- global debug_gpu_lines
- global debug_gpu_shader
-
- gpu.state.blend_set('ALPHA')
- if debug_gpu_lines != None:
- debug_gpu_lines.draw(debug_gpu_shader)
+ edge_data = (cxr_edge*len(data.edges))()
-class CXR_RELOAD(bpy.types.Operator):
- bl_idname="convexer.reload"
- bl_label="Reload convexer"
+ for i, edge in enumerate(data.edges):
+ edge_data[i].i0 = edge.vertices[0]
+ edge_data[i].i1 = edge.vertices[1]
+ edge_data[i].freestyle = edge.use_freestyle_mark
+ edge_data[i].sharp = edge.use_edge_sharp
- def execute(_,context):
- global libcxr, libnbvtf, libnbvtf_convert
-
- # Load vtf library
- libnbvtf = cdll.LoadLibrary( os.path.dirname(__file__)+'/libnbvtf.so')
- libnbvtf_convert = libnbvtf.nbvtf_convert
- libnbvtf_convert.argtypes = [\
- c_char_p, \
- c_int32, \
- c_int32, \
- c_int32, \
- c_int32, \
- c_uint32, \
- c_char_p ]
- libnbvtf_convert.restype = c_int32
-
- if libcxr != None:
- _handle = libcxr._handle
-
- # TODO: Find a propper way to do this
- for i in range(10): libc_dlclose( _handle )
- del libcxr
+ material_data = (cxr_material*len(obj.material_slots))()
- libcxr = None
- libcxr = cdll.LoadLibrary( os.path.dirname(__file__)+'/libcxr.so')
-
- build_time = c_char_p.in_dll(libcxr,'cxr_build_time')
- print( F"libcxr build time: {build_time.value}" )
+ for i, ms in enumerate(obj.material_slots):
+ inf = material_info(ms.material)
+ material_data[i].res[0] = inf['res'][0]
+ material_data[i].res[1] = inf['res'][1]
+ material_data[i].name = inf['name'].encode('utf-8')
+
+ mesh.edges = cast(edge_data, POINTER(cxr_edge))
+ mesh.vertices = cast(vertex_data, POINTER(c_double*3))
+ mesh.loops = cast(loop_data,POINTER(cxr_static_loop))
+ mesh.polys = cast(polygon_data, POINTER(cxr_polygon))
+ mesh.materials = cast(material_data, POINTER(cxr_material))
- # Public API
- global libcxr_write_test_data, libcxr_convert_mesh_to_vmf
+ mesh.poly_count = len(data.polygons)
+ mesh.vertex_count = len(data.vertices)
+ mesh.edge_count = len(data.edges)
+ mesh.loop_count = len(data.loops)
+ mesh.material_count = len(obj.material_slots)
- libcxr_write_test_data = libcxr.cxr_write_test_data
- libcxr_write_test_data.argtypes = [\
- POINTER(cxr_input_mesh)
- ]
- libcxr_write_test_data.restype = c_int32
+ if orig_state != None:
+ bpy.ops.object.mode_set(mode=orig_state)
- libcxr_convert_mesh_to_vmf = libcxr.cxr_convert_mesh_to_vmf
- libcxr_convert_mesh_to_vmf.argtypes = [\
- POINTER(cxr_input_mesh),\
- c_void_p,\
- ]
- libcxr_convert_mesh_to_vmf.restype = c_int32
-
- global libcxr_context_reset, libcxr_set_offset,\
- libcxr_set_scale_factor
- libcxr_context_reset = libcxr.cxr_context_reset
-
- libcxr_set_offset = libcxr.cxr_set_offset
- libcxr_set_offset.argtypes = [ c_double ]
-
- libcxr_set_scale_factor = libcxr.cxr_set_scale_factor
- libcxr_set_scale_factor.argtypes = [ c_double ]
-
- # VDF
- global libcxr_vdf_open, \
- libcxr_vdf_close, \
- libcxr_vdf_put, \
- libcxr_vdf_node, \
- libcxr_vdf_edon, \
- libcxr_vdf_kv
-
- libcxr_vdf_open = libcxr.cxr_vdf_open
- libcxr_vdf_open.argtypes = [ c_char_p ]
- libcxr_vdf_open.restype = c_void_p
+ return mesh
- libcxr_vdf_close = libcxr.cxr_vdf_close
- libcxr_vdf_close.argtypes = [ c_void_p ]
+# Callback ctypes indirection things.. not really sure.
+c_libcxr_log_callback = None
+c_libcxr_line_callback = None
- libcxr_vdf_put = libcxr.cxr_vdf_put
- libcxr_vdf_put.argtypes = [ c_void_p, c_char_p ]
+# Public API
+# -------------------------------------------------------------
+libcxr_decompose = extern( "cxr_decompose",
+ [POINTER(cxr_static_mesh), POINTER(c_int32)],
+ c_void_p
+)
+libcxr_free_world = extern( "cxr_free_world",
+ [c_void_p],
+ None
+)
+libcxr_write_test_data = extern( "cxr_write_test_data",
+ [POINTER(cxr_static_mesh)],
+ None
+)
+libcxr_world_preview = extern( "cxr_world_preview",
+ [c_void_p],
+ POINTER(cxr_tri_mesh)
+)
+libcxr_free_tri_mesh = extern( "cxr_free_tri_mesh",
+ [c_void_p],
+ None
+)
+libcxr_begin_vmf = extern( "cxr_begin_vmf",
+ [POINTER(cxr_vmf_context), c_void_p],
+ None
+)
+libcxr_vmf_begin_entities = extern( "cxr_vmf_begin_entities",
+ [POINTER(cxr_vmf_context), c_void_p],
+ None
+)
+libcxr_push_world_vmf = extern("cxr_push_world_vmf",
+ [c_void_p,POINTER(cxr_vmf_context),c_void_p],
+ None
+)
+libcxr_end_vmf = extern( "cxr_end_vmf",
+ [POINTER(cxr_vmf_context),c_void_p],
+ None
+)
+
+# VDF + with open wrapper
+libcxr_vdf_open = extern( "cxr_vdf_open", [c_char_p], c_void_p )
+libcxr_vdf_close = extern( "cxr_vdf_close", [c_void_p], None )
+libcxr_vdf_put = extern( "cxr_vdf_put", [c_void_p,c_char_p], None )
+libcxr_vdf_node = extern( "cxr_vdf_node", [c_void_p,c_char_p], None )
+libcxr_vdf_edon = extern( "cxr_vdf_edon", [c_void_p], None )
+libcxr_vdf_kv = extern( "cxr_vdf_kv", [c_void_p,c_char_p,c_char_p], None )
- libcxr_vdf_node = libcxr.cxr_vdf_node
- libcxr_vdf_node.argtypes = [ c_void_p, c_char_p ]
+class vdf_structure():
+ def __init__(_,path):
+ _.path = path
+ def __enter__(_):
+ _.fp = libcxr_vdf_open.call( _.path.encode('utf-8') )
+ if _.fp == None:
+ print( F"Could not open file {_.path}" )
+ return None
+ return _
+ def __exit__(_,type,value,traceback):
+ if _.fp != None:
+ libcxr_vdf_close.call(_.fp)
+ def put(_,s):
+ libcxr_vdf_put.call(_.fp, s.encode('utf-8') )
+ def node(_,name):
+ libcxr_vdf_node.call(_.fp, name.encode('utf-8') )
+ def edon(_):
+ libcxr_vdf_edon.call(_.fp)
+ def kv(_,k,v):
+ libcxr_vdf_kv.call(_.fp, k.encode('utf-8'), v.encode('utf-8'))
- libcxr_vdf_edon = libcxr.cxr_vdf_edon
- libcxr_vdf_edon.argtypes = [ c_void_p ]
+# Other
+libcxr_lightpatch_bsp = extern( "cxr_lightpatch_bsp", [c_char_p], None )
- libcxr_vdf_kv = libcxr.cxr_vdf_kv
- libcxr_vdf_kv.argtypes = [ c_void_p, c_char_p, c_char_p ]
+libcxr_funcs = [ libcxr_decompose, libcxr_free_world, libcxr_begin_vmf, \
+ libcxr_vmf_begin_entities, libcxr_push_world_vmf, \
+ libcxr_end_vmf, libcxr_vdf_open, libcxr_vdf_close, \
+ libcxr_vdf_put, libcxr_vdf_node, libcxr_vdf_edon,
+ libcxr_vdf_kv, libcxr_lightpatch_bsp, libcxr_write_test_data,\
+ libcxr_world_preview, libcxr_free_tri_mesh ]
- # Callbacks
- global c_libcxr_log_callback
- global c_libcxr_line_callback
+# Callbacks
- LOG_FUNCTION_TYPE = CFUNCTYPE(None,c_char_p)
- c_libcxr_log_callback = LOG_FUNCTION_TYPE(libcxr_log_callback)
- libcxr.cxr_set_log_function(cast(c_libcxr_log_callback,c_void_p))
+def libcxr_log_callback(logStr):
+ print( F"{logStr.decode('utf-8')}",end='' )
- LINE_FUNCTION_TYPE = CFUNCTYPE(None,\
- POINTER(c_double),POINTER(c_double),POINTER(c_double))
- c_libcxr_line_callback = LINE_FUNCTION_TYPE(libcxr_line_callback)
- libcxr.cxr_set_line_function(cast(c_libcxr_line_callback,c_void_p))
+cxr_line_positions = None
+cxr_line_colours = None
- return {'FINISHED'}
+def cxr_reset_lines():
+ global cxr_line_positions, cxr_line_colours
-def libcxr_use():
- global libcxr
+ cxr_line_positions = []
+ cxr_line_colours = []
- if libcxr == None:
- bpy.ops.convexer.reload()
-
- def _bool_int(b):
- return 1 if b else 0
-
- scene = bpy.context.scene
- cxr = scene.cxr_data
+def cxr_batch_lines():
+ global cxr_line_positions, cxr_line_colours, cxr_view_shader, cxr_view_lines
- settings=cxr_settings()
- settings.debug=_bool_int(cxr.debug)
+ cxr_view_lines = batch_for_shader(\
+ cxr_view_shader, 'LINES',\
+ { "pos": cxr_line_positions, "color": cxr_line_colours })
- settings.lightmap_scale=cxr.lightmap_scale
- settings.light_scale=cxr.light_scale
+def libcxr_line_callback( p0,p1,colour ):
+ global cxr_line_colours, cxr_line_positions
- libcxr.cxr_settings_update(pointer(settings))
+ cxr_line_positions += [(p0[0],p0[1],p0[2])]
+ cxr_line_positions += [(p1[0],p1[1],p1[2])]
+ cxr_line_colours += [(colour[0],colour[1],colour[2],colour[3])]
+ cxr_line_colours += [(colour[0],colour[1],colour[2],colour[3])]
-def to_aeiou( v ):
- ret = ""
- if v == 0:
- return "z"
- dig = []
- while v:
- dig.append( int( v % 5 ) )
- v //= 5
- for d in dig[::-1]:
- ret += [ 'a','e','i','o','u' ][d]
- return ret
+# libnbvtf
+# ------------------------------------------------------------------------------
-def asset_uid(asset):
- if isinstance(asset,str):
- return asset
- name = to_aeiou(asset.cxr_data.asset_id)
- if bpy.context.scene.cxr_data.include_names:
- name += asset.name.replace('.','_')
- return name
+libnbvtf = None
-# -> <project_name>/<asset_name>
-def asset_name(asset):
- return F"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
+# Constants
+NBVTF_IMAGE_FORMAT_ABGR8888 = 1
+NBVTF_IMAGE_FORMAT_BGR888 = 3
+NBVTF_IMAGE_FORMAT_DXT1 = 13
+NBVTF_IMAGE_FORMAT_DXT5 = 15
+NBVTF_TEXTUREFLAGS_CLAMPS = 0x00000004
+NBVTF_TEXTUREFLAGS_CLAMPT = 0x00000008
+NBVTF_TEXTUREFLAGS_NORMAL = 0x00000080
+NBVTF_TEXTUREFLAGS_NOMIP = 0x00000100
+NBVTF_TEXTUREFLAGS_NOLOD = 0x00000200
-# -> <subdir>/<project_name>/<asset_name>
-def asset_path(subdir, asset):
- return F"{subdir}/{asset_name(asset_uid(asset))}"
+libnbvtf_convert = extern( "nbvtf_convert", \
+ [c_char_p,c_int32,c_int32,c_int32,c_int32,c_int32,c_uint32,c_char_p], \
+ c_int32 )
-# -> <csgo>/<subdir>/<project_name>/<asset_name>
-def asset_full_path(sdir,asset):
- return F"{bpy.context.scene.cxr_data.subdir}/"+\
- F"{asset_path(sdir,asset_uid(asset))}"
+libnbvtf_init = extern( "nbvtf_init", [], None )
+libnbvtf_funcs = [ libnbvtf_convert, libnbvtf_init ]
-# view_layer.update() doesnt seem to work,
-# tag_redraw() seems to have broken
-# therefore, change a property
-def scene_redraw():
- ob = bpy.context.scene.objects[0]
- ob.hide_render = ob.hide_render
-
- # the 'real' way to refresh the scene
- #for area in bpy.context.window.screen.areas:
- # if area.type == 'view_3d':
- # area.tag_redraw()
+# Loading
+# --------------------------
-# The default shader is the first entry
-#
-cxr_shaders = {
- "LightMappedGeneric":
- {
- "name": "Light Mapped",
- "id": 0
- },
- "VertexLitGeneric":
- {
- "name": "Vertex Lit",
- "id": 1
- },
- "UnlitGeneric":
- {
- "name": "Unlit",
- "id": 2
- },
- "Builtin":
- {
- "name": "Builtin",
- "id": 3
- }
-}
+def shared_reload():
+ global libcxr, libnbvtf, libcxr_funcs, libnbvtf_funcs
-def material_tex_image(v):
- return {\
- "ShaderNodeTexImage":
- {
- "image": F"${v}"
- }
- }
+ # Unload libraries if existing
+ def _reload( lib, path ):
+ if lib != None:
+ _handle = lib._handle
+ for i in range(10): libc_dlclose( _handle )
+ lib = None
+ del lib
+ return cdll.LoadLibrary( F'{os.path.dirname(__file__)}/{path}.so' )
-cxr_graph_mapping = {
- "ShaderNodeBsdfPrincipled":
- {
- "Base Color":
- {
- "ShaderNodeMixRGB":
- {
- "Color1": material_tex_image("basetexture"),
- "Color2": material_tex_image("decaltexture")
- },
- "ShaderNodeTexImage":
- {
- "image":"$basetexture"
- },
- "default":
- [("VertexLitGeneric","$color2"),\
- ("UnlitGeneric","$color2"),\
- ("LightMappedGeneric","$color")]
- },
- "Normal":
- {
- "ShaderNodeNormalMap":
- {
- "Color": material_tex_image("bumpmap")
- }
- }
- }
-}
+ libnbvtf = _reload( libnbvtf, "libnbvtf" )
+ libcxr = _reload( libcxr, "libcxr" )
+
+ for fd in libnbvtf_funcs:
+ fd.loadfrom( libnbvtf )
+ libnbvtf_init.call()
-cxr_shader_params = {
- "Textures":
- {
- "type": "ui",
- "shaders": ("UnlitGeneric","VertexLitGeneric","LightMappedGeneric"),
-
- "$basetexture":
- {
- "name": "Base Texture",
- "type": "intrinsic",
- "default": None
- },
- "$decaltexture":
- {
- "name": "Decal Texture",
- "type": "intrinsic",
- "default": None,
-
- "$decalblendmode":
- {
- "name": "Blend Mode",
- "type": "enum",
- "items": [
- ('0',"AlphaOver","Default",'',0),
- ('1',"Multiply","",'',1),
- ('2',"Modulate","",'',2),
- ('3',"Additive","",'',3)
- ],
- "default": 0,
- "always": True
- }
- },
- "$bumpmap":
- {
- "name": "Normal Map",
- "type": "intrinsic",
- "flags": NBVTF_TEXTUREFLAGS_NORMAL,
- "default": None
- }
- },
- "$color":
- {
- "name": "Color",
- "type": "intrinsic",
- "default": None,
- "exponent": 2.2
- },
- "$color2":
- {
- "name": "Color2",
- "type": "intrinsic",
- "default": None,
- "exponent": 2.2
- },
- "Lighting":
- {
- "type": "ui",
- "shaders": ("VertexLitGeneric", "LightMappedGeneric"),
-
- "$phong":
- {
- "name": "Phong",
- "type": "bool",
- "default": False,
-
- "$phongexponent":
- {
- "name": "Exponent",
- "type": "float",
- "default": 5.0
- },
- "$phongboost":
- {
- "name": "Boost",
- "type": "float",
- "default": 1.0
- },
- "$phongfresnelranges":
- {
- "name": "Fresnel Ranges",
- "type": "vector",
- "default":(1.0,1.0,1.0)
- }
- },
- "$envmap":
- {
- "name": "Cubemap",
- "type": "string",
- "default": "",
-
- "$envmaptint":
- {
- "name": "Tint",
- "type": "vector",
- "subtype": 'COLOR',
- "default": (1.0,1.0,1.0)
- },
- "$envmaplightscale":
- {
- "name": "Light Scale",
- "type": "float",
- "default": 0.0
- },
- "$envmaplightscaleminmax":
- {
- "name": "Min/Max",
- "type": "vector",
- "default": (0.0,1.0)
- }
- }
- },
- "Transparency":
- {
- "type": "ui",
- "shaders": ("UnlitGeneric","VertexLitGeneric","LightMappedGeneric"),
-
- "$translucent":
- {
- "name": "Translucent",
- "type": "bool",
- "default": False
- },
- "$alphatest":
- {
- "name": "Alpha Test",
- "type": "bool",
- "default": False,
-
- "$alphatestreference":
- {
- "name": "Step",
- "type": "float",
- "default": 0.5
- }
- },
- "$nocull":
- {
- "name": "No Cull",
- "type": "bool",
- "default": False
- }
- }
-}
+ for fd in libcxr_funcs:
+ fd.loadfrom( libcxr )
+
+ # Callbacks
+ global c_libcxr_log_callback, c_libcxr_line_callback
+
+ LOG_FUNCTION_TYPE = CFUNCTYPE(None,c_char_p)
+ c_libcxr_log_callback = LOG_FUNCTION_TYPE(libcxr_log_callback)
+
+ LINE_FUNCTION_TYPE = CFUNCTYPE(None,\
+ POINTER(c_double), POINTER(c_double), POINTER(c_double))
+ c_libcxr_line_callback = LINE_FUNCTION_TYPE(libcxr_line_callback)
+
+ libcxr.cxr_set_log_function(cast(c_libcxr_log_callback,c_void_p))
+ libcxr.cxr_set_line_function(cast(c_libcxr_line_callback,c_void_p))
+
+ build_time = c_char_p.in_dll(libcxr,'cxr_build_time')
+ print( F"libcxr build time: {build_time.value}" )
+
+shared_reload()
-def ent_get_origin(obj,context):
- return obj.location * context.scale
+# Configuration
+# ------------------------------------------------------------------------------
-def ent_get_angles(obj,context):
+# Standard entity functions, think of like base.fgd
+#
+def cxr_get_origin(context):
+ return context['object'].location * context['transform']['scale'] + \
+ mathutils.Vector(context['transform']['offset'])
+
+def cxr_get_angles(context):
+ obj = context['object']
euler = [ a*57.295779513 for a in obj.rotation_euler ]
angle = [0,0,0]
angle[0] = euler[1]
angle[2] = euler[0]
return angle
-def ent_baseclass(classes, other):
+def cxr_baseclass(classes, other):
base = other.copy()
for x in classes:
base.update(x.copy())
return base
-ent_origin = { "origin": ent_get_origin }
-ent_angles = { "angles": ent_get_angles }
-ent_transform = ent_baseclass( [ent_origin], ent_angles )
-
-def ent_lights(obj,context):
- kvs = ent_baseclass([ent_origin],\
+# EEVEE Light component converter -> Source 1
+#
+def ent_lights(context):
+ obj = context['object']
+ kvs = cxr_baseclass([ent_origin],\
{
"_distance": (0.0 if obj.data.cxr_data.realtime else -1.0),
- "_light": [int(pow(obj.data.color[i],1.0/2.2)*255.0) for i in range(3)] +\
- [int(obj.data.energy * bpy.context.scene.cxr_data.light_scale)],
"_lightHDR": '-1 -1 -1 1',
"_lightscaleHDR": 1
})
-
- if obj.data.type == 'SPOT':
- kvs['_cone'] = obj.data.spot_size*(57.295779513/2.0)
- kvs['_inner_cone'] = (1.0-obj.data.spot_blend)*kvs['_cone']
- # Blenders spotlights are -z forward
+ light_base = [(pow(obj.data.color[i],1.0/2.2)*255.0) for i in range(3)] +\
+ [obj.data.energy * bpy.context.scene.cxr_data.light_scale]
+
+ if obj.data.type == 'SPOT' or obj.data.type == 'SUN':
+ # Blenders directional lights are -z forward
# Source is +x, however, it seems to use a completely different system.
# Since we dont care about roll for spotlights, we just take the
# pitch and yaw via trig
_,mtx_rot,_ = obj.matrix_world.decompose()
fwd = mtx_rot @ mathutils.Vector((0,0,-1))
+ dir_pitch = math.asin(fwd[2]) * 57.295779513
+ dir_yaw = math.atan2(fwd[1],fwd[0]) * 57.295779513
+
+ if obj.data.type == 'SPOT':
+ kvs['_light'] = [ int(x) for x in light_base ]
+ kvs['_cone'] = obj.data.spot_size*(57.295779513/2.0)
+ kvs['_inner_cone'] = (1.0-obj.data.spot_blend)*kvs['_cone']
- kvs['pitch'] = math.asin(fwd[2]) * 57.295779513
- kvs['angles'] = [ 0.0, math.atan2(fwd[1],fwd[0]) * 57.295779513, 0.0 ]
+ kvs['pitch'] = dir_pitch
+ kvs['angles'] = [ 0, dir_yaw, 0 ]
kvs['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
# Really bad...
#
kvs['_linear_attn'] = 1.0
elif obj.data.type == 'POINT':
+ kvs['_light'] = [ int(x) for x in light_base]
kvs['_quadratic_attn'] = 1.0
- kvs['_linear_attn'] = 0.0
+ kvs['_linear_attn'] = 1.0
elif obj.data.type == 'SUN':
- pass # TODO
+ light_base[3] *= 300.0 * 5
+ kvs['_light'] = [ int(x) for x in light_base ]
+
+ ambient = bpy.context.scene.world.color
+ kvs['_ambient'] = [int(pow(ambient[i],1.0/2.2)*255.0) for i in range(3)] +\
+ [80 * 5]
+ kvs['_ambientHDR'] = [-1,-1,-1,1]
+ kvs['_AmbientScaleHDR'] = 1
+ kvs['pitch'] = dir_pitch
+ kvs['angles'] = [ dir_pitch, dir_yaw, 0.0 ]
+ kvs['SunSpreadAngle'] = 0
return kvs
-def ent_cubemap(obj,context):
- return ent_baseclass([ent_origin],\
- {"cubemapsize": obj.data.cxr_data.size})
+def ent_prop(context):
+ if isinstance( context['object'], bpy.types.Collection ):
+ kvs = {}
+ target = context['object']
+ pos = mathutils.Vector(context['origin'])
+ pos += mathutils.Vector(context['transform']['offset'])
+
+ kvs['origin'] = [pos[1],-pos[0],pos[2]]
+ kvs['angles'] = [0,180,0]
+ kvs['uniformscale'] = 1.0
+ else:
+ kvs = cxr_baseclass([ent_origin],{})
+ target = context['object'].instance_collection
+
+ obj = context['object']
+ euler = [ a*57.295779513 for a in obj.rotation_euler ]
+ angle = [0,0,0]
+ angle[0] = euler[1]
+ angle[1] = euler[2] + 180.0 # Dunno...
+ angle[2] = euler[0]
+
+ kvs['angles'] = angle
+ kvs['uniformscale'] = obj.scale[0]
+
+ if target.cxr_data.shadow_caster:
+ kvs['enablelightbounce'] = 1
+ kvs['disableshadows'] = 0
+ else:
+ kvs['enablelightbounce'] = 0
+ kvs['disableshadows'] = 1
+
+ kvs['fademindist'] = -1
+ kvs['fadescale'] = 1
+ kvs['model'] = F"{asset_path('models',target)}.mdl".lower()
+ kvs['renderamt'] = 255
+ kvs['rendercolor'] = [255, 255, 255]
+ kvs['skin'] = 0
+ kvs['solid'] = 6
-cxr_entities = {
- "info_player_counterterrorist":
- {
- "gizmo": [],
- "allow": ('EMPTY',),
- "keyvalues": ent_baseclass([ent_transform],\
- {
- "priority": {"type": "int", "default": 0 },
- "enabled": {"type": "int", "default": 1 },
- })
- },
- "info_player_terrorist":
- {
- "gizmo": [],
- "allow": ('EMPTY',),
- "keyvalues": ent_baseclass([ent_transform],\
- {
- "priority": {"type": "int", "default": 0 },
- "enabled": {"type": "int", "default": 1 },
- })
- },
- "light": { "keyvalues": ent_lights },
- "light_spot": { "keyvalues": ent_lights },
- # SUN
- "env_cubemap": { "keyvalues": ent_cubemap },
-
- # Brush entites
- "func_buyzone":
- {
- "allow": ('MESH',),
- "keyvalues":
- {
- "TeamNum": {"type": "int", "default": 0 }
- }
+ return kvs
+
+def ent_sky_camera(context):
+ settings = bpy.context.scene.cxr_data
+ scale = settings.scale_factor / settings.skybox_scale_factor
+
+ kvs = {
+ "origin": [_ for _ in context['transform']['offset']],
+ "angles": [ 0, 0, 0 ],
+ "fogcolor": [255, 255, 255],
+ "fogcolor2": [255, 255, 255],
+ "fogdir": [1,0,0],
+ "fogend": 2000.0,
+ "fogmaxdensity": 1,
+ "fogstart": 500.0,
+ "HDRColorScale": 1.0,
+ "scale": scale
}
-}
+ return kvs
+
+def ent_cubemap(context):
+ obj = context['object']
+ return cxr_baseclass([ent_origin], {"cubemapsize": obj.data.cxr_data.size})
+
+ent_origin = { "origin": cxr_get_origin }
+ent_angles = { "angles": cxr_get_angles }
+ent_transform = cxr_baseclass( [ent_origin], ent_angles )
+
+#include the user config
+exec(open(F'{os.path.dirname(__file__)}/config.py').read())
+
+# Blender state callbacks
+# ------------------------------------------------------------------------------
+
+@persistent
+def cxr_on_load(dummy):
+ global cxr_view_lines, cxr_view_mesh
+
+ cxr_view_lines = None
+ cxr_view_mesh = None
+
+@persistent
+def cxr_dgraph_update(scene,dgraph):
+ return
+ print( F"Hallo {time.time()}" )
+
+# Convexer compilation functions
+# ------------------------------------------------------------------------------
+
+# Asset path management
+
+def asset_uid(asset):
+ if isinstance(asset,str):
+ return asset
+
+ # Create a unique ID string
+ base = "bopshei"
+ v = asset.cxr_data.asset_id
+ name = ""
+
+ if v == 0:
+ name = "a"
+ else:
+ dig = []
+
+ while v:
+ dig.append( int( v % len(base) ) )
+ v //= len(base)
+
+ for d in dig[::-1]:
+ name += base[d]
+
+ if bpy.context.scene.cxr_data.include_names:
+ name += asset.name.replace('.','_')
+
+ return name
+
+# -> <project_name>/<asset_name>
+def asset_name(asset):
+ return F"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
+
+# -> <subdir>/<project_name>/<asset_name>
+def asset_path(subdir, asset):
+ return F"{subdir}/{asset_name(asset_uid(asset))}"
+
+# -> <csgo>/<subdir>/<project_name>/<asset_name>
+def asset_full_path(sdir,asset):
+ return F"{bpy.context.scene.cxr_data.subdir}/"+\
+ F"{asset_path(sdir,asset_uid(asset))}"
+
+# Entity functions / infos
+# ------------------------
def cxr_intrinsic_classname(obj):
if obj.type == 'LIGHT':
return {
'SPOT': "light_spot",
'POINT': "light",
- 'SUN': "light_directional" }[ obj.data.type ]
+ 'SUN': "light_environment" }[ obj.data.type ]
elif obj.type == 'LIGHT_PROBE':
return "env_cubemap"
#
# Error: None
#
-def cxr_entity_keyvalues(obj,context,classname):
+def cxr_entity_keyvalues(context):
+ classname = context['classname']
+ obj = context['object']
if classname not in cxr_entities: return None
result = []
entdef = cxr_entities[classname]
kvs = entdef['keyvalues']
- if callable(kvs): kvs = kvs(obj, context)
+ if callable(kvs): kvs = kvs(context)
for k in kvs:
kv = kvs[k]
value = obj[ F"cxrkv_{k}" ]
else:
if callable(kv):
- value = kv(obj,context)
+ value = kv(context)
if isinstance(value,mathutils.Vector):
value = [_ for _ in value]
return result
+# Extract material information from shader graph data
+#
def material_info(mat):
info = {}
info['res'] = (512,512)
info['name'] = mat.name
return info
- if not hasattr(material_info,'references'):
- material_info.references = set()
-
- # Custom material
- material_info.references.add(mat)
+ # Custom materials
info['name'] = asset_name(mat)
# Using the cxr_graph_mapping as a reference, go through the shader
return info
-def mesh_cxr_format(obj):
- dgraph = bpy.context.evaluated_depsgraph_get()
- data = obj.evaluated_get(dgraph).data
-
- _,mtx_rot,_ = obj.matrix_world.decompose()
+def vec3_min( a, b ):
+ return mathutils.Vector((min(a[0],b[0]),min(a[1],b[1]),min(a[2],b[2])))
+def vec3_max( a, b ):
+ return mathutils.Vector((max(a[0],b[0]),max(a[1],b[1]),max(a[2],b[2])))
- mesh = cxr_input_mesh()
+def cxr_collection_center(collection, transform):
+ BIG=999999999
+ bounds_min = mathutils.Vector((BIG,BIG,BIG))
+ bounds_max = mathutils.Vector((-BIG,-BIG,-BIG))
+
+ for obj in collection.objects:
+ if obj.type == 'MESH':
+ corners = [ mathutils.Vector(c) for c in obj.bound_box ]
- vertex_data = ((c_double*3)*len(data.vertices))()
- for i, vert in enumerate(data.vertices):
- v = obj.matrix_world @ vert.co
- vertex_data[i][0] = c_double(v[0])
- vertex_data[i][1] = c_double(v[1])
- vertex_data[i][2] = c_double(v[2])
+ for corner in [ obj.matrix_world@c for c in corners ]:
+ bounds_min = vec3_min( bounds_min, corner )
+ bounds_max = vec3_max( bounds_max, corner )
- loop_data = (cxr_input_loop*len(data.loops))()
- polygon_data = (cxr_polygon*len(data.polygons))()
+ center = (bounds_min + bounds_max) / 2.0
+
+ origin = mathutils.Vector((-center[1],center[0],center[2]))
+ origin *= transform['scale']
- for i, poly in enumerate(data.polygons):
- loop_start = poly.loop_start
- loop_end = poly.loop_start + poly.loop_total
- for loop_index in range(loop_start, loop_end):
- loop = data.loops[loop_index]
- loop_data[loop_index].index = loop.vertex_index
- loop_data[loop_index].edge_index = loop.edge_index
+ return origin
- if data.uv_layers:
- uv = data.uv_layers.active.data[loop_index].uv
- loop_data[loop_index].uv[0] = c_double(uv[0])
- loop_data[loop_index].uv[1] = c_double(uv[1])
+# Prepares Scene into dictionary format
+#
+def cxr_scene_collect():
+ context = bpy.context
+
+ # Make sure all of our asset types have a unique ID
+ def _uid_prepare(objtype):
+ used_ids = [0]
+ to_generate = []
+ id_max = 0
+ for o in objtype:
+ vs = o.cxr_data
+ if vs.asset_id in used_ids:
+ to_generate+=[vs]
else:
- loop_data[loop_index].uv[0] = c_double(0.0)
- loop_data[loop_index].uv[1] = c_double(0.0)
- center = obj.matrix_world @ poly.center
- normal = mtx_rot @ poly.normal
+ id_max = max(id_max,vs.asset_id)
+ used_ids+=[vs.asset_id]
+ for vs in to_generate:
+ id_max += 1
+ vs.asset_id = id_max
+ _uid_prepare(bpy.data.materials)
+ _uid_prepare(bpy.data.images)
+ _uid_prepare(bpy.data.collections)
+
+ sceneinfo = {
+ "entities": [], # Everything with a classname
+ "geo": [], # All meshes without a classname
+ "heros": [] # Collections prefixed with mdl_
+ }
- polygon_data[i].loop_start = poly.loop_start
- polygon_data[i].loop_total = poly.loop_total
- polygon_data[i].normal[0] = normal[0]
- polygon_data[i].normal[1] = normal[1]
- polygon_data[i].normal[2] = normal[2]
- polygon_data[i].center[0] = center[0]
- polygon_data[i].center[1] = center[1]
- polygon_data[i].center[2] = center[2]
- polygon_data[i].material_id = poly.material_index
+ def _collect(collection,transform):
+ nonlocal sceneinfo
+
+ if collection.name.startswith('.'): return
+ if collection.hide_render: return
+
+ if collection.name.startswith('mdl_'):
+ sceneinfo['entities'] += [{
+ "object": collection,
+ "classname": "prop_static",
+ "transform": transform,
+ "origin": cxr_collection_center( collection, transform )
+ }]
+
+ sceneinfo['heros'] += [{
+ "collection": collection,
+ "transform": transform,
+ "origin": cxr_collection_center( collection, transform )
+ }]
+ return
+
+ for obj in collection.objects:
+ if obj.hide_get(): continue
+
+ classname = cxr_classname( obj )
+
+ if classname != None:
+ sceneinfo['entities'] += [{
+ "object": obj,
+ "classname": classname,
+ "transform": transform
+ }]
+ elif obj.type == 'MESH':
+ sceneinfo['geo'] += [{
+ "object": obj,
+ "transform": transform
+ }]
+
+ for c in collection.children:
+ _collect( c, transform )
+
+ transform_main = {
+ "scale": context.scene.cxr_data.scale_factor,
+ "offset": (0,0,0)
+ }
- edge_data = (cxr_edge*len(data.edges))()
+ transform_sky = {
+ "scale": context.scene.cxr_data.skybox_scale_factor,
+ "offset": (0,0,context.scene.cxr_data.skybox_offset )
+ }
- for i, edge in enumerate(data.edges):
- edge_data[i].i0 = edge.vertices[0]
- edge_data[i].i1 = edge.vertices[1]
- edge_data[i].freestyle = edge.use_freestyle_mark
+ if 'main' in bpy.data.collections:
+ _collect( bpy.data.collections['main'], transform_main )
- material_data = (cxr_material*len(obj.material_slots))()
+ if 'skybox' in bpy.data.collections:
+ _collect( bpy.data.collections['skybox'], transform_sky )
- for i, ms in enumerate(obj.material_slots):
- inf = material_info(ms.material)
- material_data[i].res[0] = inf['res'][0]
- material_data[i].res[1] = inf['res'][1]
- material_data[i].vmt_path = inf['name'].encode('utf-8')
-
- mesh.edges = cast(edge_data, POINTER(cxr_edge))
- mesh.vertices = cast(vertex_data, POINTER(c_double*3))
- mesh.loops = cast(loop_data,POINTER(cxr_input_loop))
- mesh.polys = cast(polygon_data, POINTER(cxr_polygon))
- mesh.materials = cast(material_data, POINTER(cxr_material))
-
- mesh.poly_count = len(data.polygons)
- mesh.vertex_count = len(data.vertices)
- mesh.edge_count = len(data.edges)
- mesh.loop_count = len(data.loops)
- mesh.material_count = len(obj.material_slots)
+ sceneinfo['entities'] += [{
+ "object": None,
+ "transform": transform_sky,
+ "classname": "sky_camera"
+ }]
- return mesh
+ return sceneinfo
-class CXR_WRITE_VMF(bpy.types.Operator):
- bl_idname="convexer.write_vmf"
- bl_label="Write VMF"
+# Write VMF out to file (JOB HANDLER)
+#
+def cxr_export_vmf(sceneinfo, output_vmf):
+ cxr_reset_lines()
- def execute(_,context):
- libcxr_use()
- libcxr_reset_debug_lines()
-
- # Setup output and state
- filepath = bpy.data.filepath
- directory = os.path.dirname(filepath)
- settings = context.scene.cxr_data
-
- asset_dir = F"{directory}/bin"
- material_dir = F"{settings.subdir}/materials/{settings.project_name}"
- model_dir = F"{settings.subdir}/models/{settings.project_name}"
+ with vdf_structure(output_vmf) as m:
+ print( F"Write: {output_vmf}" )
- os.makedirs( asset_dir, exist_ok=True )
- os.makedirs( material_dir, exist_ok=True )
- os.makedirs( model_dir, exist_ok=True )
+ vmfinfo = cxr_vmf_context()
+ vmfinfo.mapversion = 4
- # States
- material_info.references = set()
- libcxr_context_reset()
+ #TODO: These need to be in options...
+ vmfinfo.skyname = bpy.context.scene.cxr_data.skyname.encode('utf-8')
+ vmfinfo.detailvbsp = b"detail.vbsp"
+ vmfinfo.detailmaterial = b"detail/detailsprites"
+ vmfinfo.lightmap_scale = 12
+
+ vmfinfo.brush_count = 0
+ vmfinfo.entity_count = 0
+ vmfinfo.face_count = 0
- output_vmf = F"{directory}/{settings.project_name}.vmf"
-
- with vdf_structure(output_vmf) as m:
- print( F"Write: {output_vmf}" )
-
- m.node('versioninfo')
- m.kv('editorversion','400')
- m.kv('editorbuild','8456')
- m.kv('mapversion','4')
- m.kv('formatversion','100')
- m.kv('prefab','0')
- m.edon()
-
- m.node('visgroups')
- m.edon()
-
- m.node('viewsettings')
- m.kv('bSnapToGrid','1')
- m.kv('bShowGrid','1')
- m.kv('bShowLogicalGrid','0')
- m.kv('nGridSpacing','64')
- m.kv('bShow3DGrid','0')
- m.edon()
-
- m.node('world')
- m.kv('id','1')
- m.kv('mapversion','1')
- m.kv('classname','worldspawn')
- m.kv('skyname','sky_csgo_night02b')
- m.kv('maxpropscreenwidth','-1')
- m.kv('detailvbsp','detail.vbsp')
- m.kv('detailmaterial','detail/detailsprites')
-
- # Make sure all of our asset types have a unique ID
- def _uid_prepare(objtype):
- used_ids = [0]
- to_generate = []
- id_max = 0
- for o in objtype:
- vs = o.cxr_data
- if vs.asset_id in used_ids:
- to_generate+=[vs]
- else:
- id_max = max(id_max,vs.asset_id)
- used_ids+=[vs.asset_id]
- for vs in to_generate:
- id_max += 1
- vs.asset_id = id_max
-
- _uid_prepare(bpy.data.materials)
- _uid_prepare(bpy.data.images)
- _uid_prepare(bpy.data.collections)
-
- # Export Brushes and displacement
- def _collect(collection,transform):
- if collection.name.startswith('.'):
- return
-
- if collection.hide_render:
- return
-
- if collection.name.startswith('mdl_'):
- _collect.heros += [(collection,transform)]
- return
-
- for obj in collection.objects:
- if obj.hide_get(): continue
-
- classname = cxr_classname( obj )
-
- if classname != None:
- _collect.entities += [( obj,transform,classname )]
- elif obj.type == 'MESH':
- _collect.geo += [(obj,transform)]
-
- for c in collection.children:
- _collect( c, transform )
-
- _collect.a_models = set()
- _collect.entities = []
- _collect.geo = []
- _collect.heros = []
-
- transform_main = cxr_object_context( \
- context.scene.cxr_data.scale_factor, 0.0 )
+ libcxr_begin_vmf.call( pointer(vmfinfo), m.fp )
- transform_sky = cxr_object_context( \
- context.scene.cxr_data.skybox_scale_factor, \
- context.scene.cxr_data.skybox_offset )
+ def _buildsolid( cmd ):
+ nonlocal m
- if 'main' in bpy.data.collections:
- _collect( bpy.data.collections['main'], transform_main )
-
- if 'skybox' in bpy.data.collections:
- _collect( bpy.data.collections['skybox'], transform_sky )
-
- # World geometry
- for brush in _collect.geo:
- baked = mesh_cxr_format( brush[0] )
- libcxr_set_scale_factor( brush[1].scale )
- libcxr_set_offset( brush[1].offset_z )
- libcxr_convert_mesh_to_vmf(baked,m.fp)
-
- m.edon()
-
- # Entities
- for entity in _collect.entities:
- obj = entity[0]
- ctx = entity[1]
- cls = entity[2]
- m.node( 'entity' )
- m.kv( 'classname', cls )
-
- kvs = cxr_entity_keyvalues( obj, ctx, cls )
-
- for kv in kvs:
- if isinstance(kv[2], list):
- m.kv( kv[0], ' '.join([str(_) for _ in kv[2]]) )
- else: m.kv( kv[0], str(kv[2]) )
-
- if obj.type == 'MESH':
- baked = mesh_cxr_format( obj )
- libcxr_set_scale_factor( ctx.scale )
- libcxr_set_offset( ctx.offset_z )
- libcxr_convert_mesh_to_vmf(baked,m.fp)
-
- m.edon()
-
- print( "[CONVEXER] Compile materials / textures" )
-
- for mat in material_info.references:
- compile_material(mat)
-
- print( "[CONVEXER] Compiling models" )
-
- libcxr_batch_debug_lines()
- scene_redraw()
-
- return {'FINISHED'}
-
-class CXR_DEV_OPERATOR(bpy.types.Operator):
- bl_idname="convexer.dev_test"
- bl_label="Export development data"
-
- def execute(_,context):
- libcxr_use()
-
- # Prepare input data
- mesh_src = mesh_cxr_format(context.active_object)
-
- libcxr_reset_debug_lines()
- libcxr_write_test_data( pointer(mesh_src) )
- libcxr_batch_debug_lines()
+ print( F"{vmfinfo.brush_count} :: {cmd['object'].name}" )
+
+ baked = mesh_cxr_format( cmd['object'] )
+ world = libcxr_decompose.call( baked, None )
- scene_redraw()
- return {'FINISHED'}
+ if world == None:
+ return False
-class CXR_INTERFACE(bpy.types.Panel):
- bl_label="Convexer"
- bl_idname="SCENE_PT_convexer"
- bl_space_type='PROPERTIES'
- bl_region_type='WINDOW'
- bl_context="scene"
+ vmfinfo.scale = cmd['transform']['scale']
- def draw(_,context):
- _.layout.operator("convexer.reload")
- _.layout.operator("convexer.dev_test")
- _.layout.operator("convexer.write_vmf")
+ offset = cmd['transform']['offset']
+ vmfinfo.offset[0] = offset[0]
+ vmfinfo.offset[1] = offset[1]
+ vmfinfo.offset[2] = offset[2]
- settings = context.scene.cxr_data
+ libcxr_push_world_vmf.call( world, pointer(vmfinfo), m.fp )
+ libcxr_free_world.call( world )
- _.layout.prop(settings, "debug")
- _.layout.prop(settings, "scale_factor")
- _.layout.prop(settings, "lightmap_scale")
- _.layout.prop(settings, "light_scale" )
+ return True
+
+ # World geometry
+ for brush in sceneinfo['geo']:
+ if not _buildsolid( brush ):
+ cxr_batch_lines()
+ scene_redraw()
+ return False
+
+ libcxr_vmf_begin_entities.call(pointer(vmfinfo), m.fp)
- box = _.layout.box()
+ # Entities
+ for ent in sceneinfo['entities']:
+ obj = ent['object']
+ ctx = ent['transform']
+ cls = ent['classname']
- box.prop(settings, "project_name")
- box.prop(settings, "subdir")
+ m.node( 'entity' )
+ m.kv( 'classname', cls )
- box = _.layout.box()
- box.operator("convexer.detect_compilers")
- box.prop(settings, "exe_studiomdl")
- box.prop(settings, "exe_vbsp")
- box.prop(settings, "exe_vvis")
- box.prop(settings, "exe_vrad")
+ kvs = cxr_entity_keyvalues( ent )
+
+ for kv in kvs:
+ if isinstance(kv[2], list):
+ m.kv( kv[0], ' '.join([str(_) for _ in kv[2]]) )
+ else: m.kv( kv[0], str(kv[2]) )
+
+ if obj == None:
+ pass
+ elif not isinstance( obj, bpy.types.Collection ):
+ if obj.type == 'MESH':
+ if not _buildsolid( ent ):
+ cxr_batch_lines()
+ scene_redraw()
+ return False
-# COmpile image using NBVTF and hash it
-def compile_image(img,flags):
+ m.edon()
+
+ print( "Done" )
+ return True
+
+# COmpile image using NBVTF and hash it (JOB HANDLER)
+#
+def compile_image(img):
if img==None:
return None
dims = img.cxr_data.export_res
fmt = {
- 'RGBA': NBVTF_IMAGE_FORMAT_RGBA8888,
+ 'RGBA': NBVTF_IMAGE_FORMAT_ABGR8888,
'DXT1': NBVTF_IMAGE_FORMAT_DXT1,
'DXT5': NBVTF_IMAGE_FORMAT_DXT5,
- 'RGB': NBVTF_IMAGE_FORMAT_RGB888
+ 'RGB': NBVTF_IMAGE_FORMAT_BGR888
}[ img.cxr_data.fmt ]
mipmap = img.cxr_data.mipmap
lod = img.cxr_data.lod
clamp = img.cxr_data.clamp
+ flags = img.cxr_data.flags
+
+ q=bpy.context.scene.cxr_data.image_quality
- userflag_hash = F"{mipmap}.{lod}.{clamp}"
+ userflag_hash = F"{mipmap}.{lod}.{clamp}.{flags}"
file_hash = F"{name}.{os.path.getmtime(src_path)}"
- comphash = F"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}"
+ comphash = F"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
if img.cxr_data.last_hash != comphash:
print( F"Texture update: {img.filepath}" )
flags_full |= NBVTF_TEXTUREFLAGS_CLAMPS
flags_full |= NBVTF_TEXTUREFLAGS_CLAMPT
- if libnbvtf_convert(src,dims[0],dims[1],mipmap,fmt,flags_full,dst):
+ if libnbvtf_convert.call(src,dims[0],dims[1],mipmap,fmt,q,flags_full,dst):
img.cxr_data.last_hash = comphash
return name
+#
+# Compile a material to VMT format. This is quick to run, doesnt need to be a
+# job handler.
+#
def compile_material(mat):
- print( F"Compile {asset_full_path('materials',mat)}.vmt" )
-
info = material_info(mat)
properties = mat.cxr_data
+ print( F"Compile {asset_full_path('materials',mat)}.vmt" )
+ if properties.shader == 'Builtin':
+ return []
+
props = []
+ # Walk the property tree
def _mlayer( layer ):
nonlocal properties, props
_mlayer( cxr_shader_params )
+ # Write the vmt
with vdf_structure( F"{asset_full_path('materials',mat)}.vmt" ) as vmt:
vmt.node( properties.shader )
vmt.put( "// Convexer export\n" )
def _numeric(v):
nonlocal pdef
-
if 'exponent' in pdef: return str(pow( v, pdef['exponent'] ))
else: return str(v)
if isinstance(prop,bpy.types.Image):
- flags = 0
- if 'flags' in pdef:
- flags = pdef['flags']
- vmt.kv( decl,compile_image(prop,flags))
-
+ vmt.kv( decl, asset_name(prop))
elif isinstance(prop,bool):
vmt.kv( decl, '1' if prop else '0' )
elif isinstance(prop,str):
vmt.put( F"// (cxr) unkown shader value type'{type(prop)}'" )
vmt.edon()
+ return props
+
+def cxr_modelsrc_vphys( mdl ):
+ for obj in mdl.objects:
+ if obj.name == F"{mdl.name}_phy":
+ return obj
+ return None
+
+def cxr_export_modelsrc( mdl, origin, asset_dir, project_name, transform ):
+ dgraph = bpy.context.evaluated_depsgraph_get()
+
+ # Compute hash value
+ chash = asset_uid(mdl)+str(origin)+str(transform)
+
+ #for obj in mdl.objects:
+ # if obj.type != 'MESH':
+ # continue
+
+ # ev = obj.evaluated_get(dgraph).data
+ # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
+ # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
+
+ # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
+ # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
+
+ # if ev.uv_layers.active != None:
+ # uv_layer = ev.uv_layers.active.data
+ # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
+ # else:
+ # srcuv=['none']
+
+ # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
+ # srcmats=[ ms.material.name for ms in obj.material_slots ]
+ # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
+ # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
+ # srctr=[(v[0],v[1],v[2]) for v in transforms]
+ # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
+
+ #if chash != mdl.cxr_data.last_hash:
+ # mdl.cxr_data.last_hash = chash
+ # print( F"Compile: {mdl.name}" )
+ #else:
+ # return True
+
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # Get viewlayer
+ def _get_layer(col,name):
+ for c in col.children:
+ if c.name == name:
+ return c
+ sub = _get_layer(c,name)
+ if sub != None:
+ return sub
+ return None
+ layer = _get_layer(bpy.context.view_layer.layer_collection,mdl.name)
+
+ prev_state = layer.hide_viewport
+ layer.hide_viewport=False
+
+ # Collect materials to be compiled, and temp rename for export
+ mat_dict = {}
+
+ vphys = None
+ for obj in mdl.objects:
+ if obj.name == F"{mdl.name}_phy":
+ vphys = obj
+ continue
+
+ obj.select_set(state=True)
+ for ms in obj.material_slots:
+ if ms.material != None:
+ if ms.material not in mat_dict:
+ mat_dict[ms.material] = ms.material.name
+ ms.material.name = asset_uid(ms.material)
+ ms.material.use_nodes = False
+
+ uid=asset_uid(mdl)
+ bpy.ops.export_scene.fbx( filepath=F'{asset_dir}/{uid}_ref.fbx',\
+ check_existing=False,
+ use_selection=True,
+ apply_unit_scale=False,
+ bake_space_transform=False
+ )
+
+ bpy.ops.object.select_all(action='DESELECT')
+
+ if vphys != None:
+ vphys.select_set(state=True)
+ bpy.ops.export_scene.fbx( filepath=F'{asset_dir}/{uid}_phy.fbx',\
+ check_existing=False,
+ use_selection=True,
+ apply_unit_scale=False,
+ bake_space_transform=False
+ )
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # Fix material names back to original
+ for mat in mat_dict:
+ mat.name = mat_dict[mat]
+ mat.use_nodes = True
+
+ layer.hide_viewport=prev_state
+
+ # Write out QC file
+ with open(F'{asset_dir}/{uid}.qc','w') as o:
+ o.write(F'$modelname "{project_name}/{uid}"\n')
+ #o.write(F'$scale .32\n')
+ o.write(F'$scale {transform["scale"]/100.0}\n')
+ o.write(F'$body _ "{uid}_ref.fbx"\n')
+ o.write(F'$staticprop\n')
+ o.write(F'$origin {origin[0]:.6f} {origin[1]:.6f} {origin[2]:.6f}\n')
+
+ if mdl.cxr_data.preserve_order:
+ o.write(F"$preservetriangleorder\n")
+
+ if mdl.cxr_data.texture_shadows:
+ o.write(F"$casttextureshadows\n")
+
+ o.write(F"$surfaceprop {mdl.cxr_data.surfaceprop}\n")
+
+ if vphys != None:
+ o.write(F'$collisionmodel "{uid}_phy.fbx"\n')
+ o.write("{\n")
+ o.write(" $concave\n")
+ o.write("}\n")
+
+ o.write(F'$cdmaterials {project_name}\n')
+ o.write(F'$sequence idle {uid}_ref.fbx\n')
+
+ return True
+#
+# Copy bsp file (and also lightpatch it)
+#
+def cxr_patchmap( src, dst ):
+ libcxr_lightpatch_bsp.call( src.encode('utf-8') )
+ shutil.copyfile( src, dst )
+ return True
+
+# Convexer operators
+# ------------------------------------------------------------------------------
+
+# Force reload of shared libraries
+#
+class CXR_RELOAD(bpy.types.Operator):
+ bl_idname="convexer.reload"
+ bl_label="Reload"
+ def execute(_,context):
+ shared_reload()
+ return {'FINISHED'}
+
+# Used for exporting data to use with ASAN builds
+#
+class CXR_DEV_OPERATOR(bpy.types.Operator):
+ bl_idname="convexer.dev_test"
+ bl_label="Export development data"
+
+ def execute(_,context):
+ # Prepare input data
+ mesh_src = mesh_cxr_format(context.active_object)
+ libcxr_write_test_data.call( pointer(mesh_src) )
+ return {'FINISHED'}
+
+# UI: Preview how the brushes will looks in 3D view
+#
+class CXR_PREVIEW_OPERATOR(bpy.types.Operator):
+ bl_idname="convexer.preview"
+ bl_label="Preview Brushes"
+
+ LASTERR = None
+ RUNNING = False
+
+ def execute(_,context):
+ return {'FINISHED'}
+
+ def modal(_,context,event):
+ global cxr_view_mesh
+ static = _.__class__
+
+ if event.type == 'ESC':
+ cxr_reset_lines()
+ cxr_batch_lines()
+ cxr_view_mesh = None
+ static.RUNNING = False
+
+ scene_redraw()
+ return {'FINISHED'}
+
+ return {'PASS_THROUGH'}
+
+ def invoke(_,context,event):
+ global cxr_view_shader, cxr_view_mesh
+ static = _.__class__
+ static.LASTERR = None
+
+ cxr_reset_lines()
+
+ mesh_src = mesh_cxr_format(context.active_object)
+
+ err = c_int32(0)
+ world = libcxr_decompose.call( mesh_src, pointer(err) )
+
+ if world == None:
+ cxr_view_mesh = None
+ cxr_batch_lines()
+ scene_redraw()
+
+ static.LASTERR = ["There is no error", \
+ "Non-Manifold",\
+ "Bad-Manifold",\
+ "No-Candidate",\
+ "Internal-Fail",\
+ "Non-Coplanar",\
+ "Non-Convex Polygon",\
+ "Bad Result",\
+ "Invalid-Input"]\
+ [err.value]
+
+ if static.RUNNING:
+ return {'CANCELLED'}
+ else:
+ context.window_manager.modal_handler_add(_)
+ return {'RUNNING_MODAL'}
+
+ # Generate preview using cxr
+ #
+ ptrpreview = libcxr_world_preview.call( world )
+ preview = ptrpreview[0]
+
+ vertices = preview.vertices[:preview.vertex_count]
+ vertices = [(_[0],_[1],_[2]) for _ in vertices]
+
+ colours = preview.colours[:preview.vertex_count]
+ colours = [(_[0],_[1],_[2],_[3]) for _ in colours]
+
+ indices = preview.indices[:preview.indices_count]
+ indices = [ (indices[i*3+0],indices[i*3+1],indices[i*3+2]) \
+ for i in range(int(preview.indices_count/3)) ]
+
+ cxr_view_mesh = batch_for_shader(
+ cxr_view_shader, 'TRIS',
+ { "pos": vertices, "color": colours },
+ indices = indices,
+ )
+
+ libcxr_free_tri_mesh.call( ptrpreview )
+ libcxr_free_world.call( world )
+ cxr_batch_lines()
+ scene_redraw()
+
+ # Allow user to spam the operator
+ if static.RUNNING:
+ return {'CANCELLED'}
+
+ if not static.RUNNING:
+ static.RUNNING = True
+ context.window_manager.modal_handler_add(_)
+ return {'RUNNING_MODAL'}
+
+# Search for VMF compiler executables in subdirectory
+#
+class CXR_DETECT_COMPILERS(bpy.types.Operator):
+ bl_idname="convexer.detect_compilers"
+ bl_label="Find compilers"
+
+ def execute(self,context):
+ scene = context.scene
+ settings = scene.cxr_data
+ subdir = settings.subdir
+
+ for exename in ['studiomdl','vbsp','vvis','vrad']:
+ searchpath = os.path.normpath(F'{subdir}/../bin/{exename}.exe')
+ if os.path.exists(searchpath):
+ settings[F'exe_{exename}'] = searchpath
+
+ return {'FINISHED'}
+
+def cxr_compiler_path( compiler ):
+ settings = bpy.context.scene.cxr_data
+ subdir = settings.subdir
+ path = os.path.normpath(F'{subdir}/../bin/{compiler}.exe')
+
+ if os.path.exists( path ): return path
+ else: return None
+
+def cxr_winepath( path ):
+ return 'z:'+path.replace('/','\\')
+
+# Main compile function
+#
+class CXR_COMPILER_CHAIN(bpy.types.Operator):
+ bl_idname="convexer.chain"
+ bl_label="Compile Chain"
+
+ # 'static'
+ USER_EXIT = False
+ SUBPROC = None
+ TIMER = None
+ TIMER_LAST = 0.0
+ WAIT_REDRAW = False
+ FILE = None
+ LOG = []
+
+ JOBINFO = None
+ JOBID = 0
+ JOBSYS = None
+
+ def cancel(_,context):
+ global cxr_jobs_batch
+ static = _.__class__
+ wm = context.window_manager
+
+ if static.SUBPROC != None:
+ static.SUBPROC.terminate()
+ static.SUBPROC = None
+
+ if static.TIMER != None:
+ wm.event_timer_remove( static.TIMER )
+ static.TIMER = None
+
+ static.FILE.close()
+
+ cxr_jobs_batch = None
+ scene_redraw()
+ return {'FINISHED'}
+
+ def modal(_,context,ev):
+ static = _.__class__
+
+ if ev.type == 'TIMER':
+ global cxr_jobs_batch
+
+ if static.WAIT_REDRAW:
+ scene_redraw()
+ return {'PASS_THROUGH'}
+ static.WAIT_REDRAW = True
+
+ if static.USER_EXIT:
+ print( "Chain USER_EXIT" )
+ return _.cancel(context)
+
+ if static.SUBPROC != None:
+ # Deal with async modes
+ status = static.SUBPROC.poll()
+ if status == None:
+
+ # Cannot redirect STDOUT through here without causing
+ # undefined behaviour due to the Blender Python specification.
+ #
+ # Have to write it out to a file and read it back in.
+ #
+ with open("/tmp/convexer_compile_log.txt","r") as log:
+ static.LOG = log.readlines()
+ return {'PASS_THROUGH'}
+ else:
+ #for l in static.SUBPROC.stdout:
+ # print( F'-> {l.decode("utf-8")}',end='' )
+ static.SUBPROC = None
+
+ if status != 0:
+ print(F'Compiler () error: {status}')
+ return _.cancel(context)
+
+ static.JOBSYS['jobs'][static.JOBID] = None
+ cxr_jobs_update_graph( static.JOBINFO )
+ scene_redraw()
+ return {'PASS_THROUGH'}
+
+ # Compile syncronous thing
+ for sys in static.JOBINFO:
+ for i,target in enumerate(sys['jobs']):
+ if target != None:
+
+ if callable(sys['exec']):
+ print( F"Run (sync): {static.JOBID} @{time.time()}" )
+
+ if not sys['exec'](*target):
+ print( "Job failed" )
+ return _.cancel(context)
+
+ sys['jobs'][i] = None
+ static.JOBID += 1
+ else:
+ # Run external executable (wine)
+ static.SUBPROC = subprocess.Popen( target,
+ stdout=static.FILE,\
+ stderr=subprocess.PIPE,\
+ cwd=sys['cwd'])
+ static.JOBSYS = sys
+ static.JOBID = i
+
+ cxr_jobs_update_graph( static.JOBINFO )
+ scene_redraw()
+ return {'PASS_THROUGH'}
+
+ # All completed
+ print( "All jobs completed!" )
+ cxr_jobs_batch = None
+
+ scene_redraw()
+ return _.cancel(context)
+
+ return {'PASS_THROUGH'}
+
+ def invoke(_,context,event):
+ static = _.__class__
+ wm = context.window_manager
+
+ if static.TIMER != None:
+ print("Chain exiting...")
+ static.USER_EXIT=True
+ return {'RUNNING_MODAL'}
+
+ print("Launching compiler toolchain")
+
+ # Run static compilation units now (collect, vmt..)
+ filepath = bpy.data.filepath
+ directory = os.path.dirname(filepath)
+ settings = bpy.context.scene.cxr_data
+
+ asset_dir = F"{directory}/modelsrc"
+ material_dir = F"{settings.subdir}/materials/{settings.project_name}"
+ model_dir = F"{settings.subdir}/models/{settings.project_name}"
+ output_vmf = F"{directory}/{settings.project_name}.vmf"
+
+ bsp_local = F"{directory}/{settings.project_name}.bsp"
+ bsp_remote = F"{settings.subdir}/maps/{settings.project_name}.bsp"
+ bsp_packed = F"{settings.subdir}/maps/{settings.project_name}_pack.bsp"
+ packlist = F"{directory}/{settings.project_name}_assets.txt"
+
+ os.makedirs( asset_dir, exist_ok=True )
+ os.makedirs( material_dir, exist_ok=True )
+ os.makedirs( model_dir, exist_ok=True )
+
+ static.FILE = open(F"/tmp/convexer_compile_log.txt","w")
+ static.LOG = []
+
+ sceneinfo = cxr_scene_collect()
+ image_jobs = []
+ qc_jobs = []
+
+ # Collect materials
+ a_materials = set()
+ for brush in sceneinfo['geo']:
+ for ms in brush['object'].material_slots:
+ a_materials.add( ms.material )
+ if ms.material.cxr_data.shader == 'VertexLitGeneric':
+ errmat = ms.material.name
+ errnam = brush['object'].name
+ print( F"Vertex shader {errmat} used on {errnam}")
+ return {'CANCELLED'}
+
+ a_models = set()
+ model_jobs = []
+ for ent in sceneinfo['entities']:
+ if ent['object'] == None: continue
+
+ if ent['classname'] == 'prop_static':
+ obj = ent['object']
+ if isinstance(obj,bpy.types.Collection):
+ target = obj
+ a_models.add( target )
+ model_jobs += [(target, ent['origin'], asset_dir, \
+ settings.project_name, ent['transform'])]
+ else:
+ target = obj.instance_collection
+ if target in a_models:
+ continue
+ a_models.add( target )
+
+ # TODO: Should take into account collection instancing offset
+ model_jobs += [(target, [0,0,0], asset_dir, \
+ settings.project_name, ent['transform'])]
+
+ elif ent['object'].type == 'MESH':
+ for ms in ent['object'].material_slots:
+ a_materials.add( ms.material )
+
+ for mdl in a_models:
+ uid = asset_uid(mdl)
+ qc_jobs += [F'{uid}.qc']
+
+ for obj in mdl.objects:
+ for ms in obj.material_slots:
+ a_materials.add( ms.material )
+ if ms.material.cxr_data.shader == 'LightMappedGeneric' or \
+ ms.material.cxr_data.shader == 'WorldVertexTransition':
+
+ errmat = ms.material.name
+ errnam = obj.name
+ print( F"Lightmapped shader {errmat} used on {errnam}")
+ return {'CANCELLED'}
+
+ # Collect images
+ for mat in a_materials:
+ for pair in compile_material(mat):
+ decl = pair[0]
+ pdef = pair[1]
+ prop = pair[2]
+
+ if isinstance(prop,bpy.types.Image):
+ flags = 0
+ if 'flags' in pdef: flags = pdef['flags']
+ if prop not in image_jobs:
+ image_jobs += [(prop,)]
+ prop.cxr_data.flags = flags
+
+ # Create packlist
+ with open( packlist, "w" ) as fp:
+
+ for mat in a_materials:
+ if mat.cxr_data.shader == 'Builtin': continue
+ fp.write(F"{asset_path('materials',mat)}.vmt\n")
+ fp.write(F"{cxr_winepath(asset_full_path('materials',mat))}.vmt\n")
+
+ for img_job in image_jobs:
+ img = img_job[0]
+ fp.write(F"{asset_path('materials',img)}.vtf\n")
+ fp.write(F"{cxr_winepath(asset_full_path('materials',img))}.vtf\n")
+
+ for mdl in a_models:
+ local = asset_path('models',mdl)
+ winep = cxr_winepath(asset_full_path('models',mdl))
+
+ fp.write(F"{local}.vvd\n")
+ fp.write(F"{winep}.vvd\n")
+ fp.write(F"{local}.dx90.vtx\n")
+ fp.write(F"{winep}.dx90.vtx\n")
+ fp.write(F"{local}.mdl\n")
+ fp.write(F"{winep}.mdl\n")
+ fp.write(F"{local}.vvd\n")
+ fp.write(F"{winep}.vvd\n")
+
+ if cxr_modelsrc_vphys(mdl):
+ fp.write(F"{local}.phy\n")
+ fp.write(F"{winep}.phy\n")
+
+ # Convexer jobs
+ static.JOBID = 0
+ static.JOBINFO = []
+
+ if settings.comp_vmf:
+ static.JOBINFO += [{
+ "title": "Convexer",
+ "w": 20,
+ "colour": (1.0,0.3,0.1,1.0),
+ "exec": cxr_export_vmf,
+ "jobs": [(sceneinfo,output_vmf)]
+ }]
+
+ if settings.comp_textures:
+ if len(image_jobs) > 0:
+ static.JOBINFO += [{
+ "title": "Textures",
+ "w": 40,
+ "colour": (0.1,1.0,0.3,1.0),
+ "exec": compile_image,
+ "jobs": image_jobs
+ }]
+
+ game = cxr_winepath( settings.subdir )
+ args = [ \
+ '-game', game, settings.project_name
+ ]
+
+ # FBX stage
+ if settings.comp_models:
+ if len(model_jobs) > 0:
+ static.JOBINFO += [{
+ "title": "Batches",
+ "w": 25,
+ "colour": (0.5,0.5,1.0,1.0),
+ "exec": cxr_export_modelsrc,
+ "jobs": model_jobs
+ }]
+
+ if len(qc_jobs) > 0:
+ static.JOBINFO += [{
+ "title": "StudioMDL",
+ "w": 20,
+ "colour": (0.8,0.1,0.1,1.0),
+ "exec": "studiomdl",
+ "jobs": [[settings[F'exe_studiomdl']] + [\
+ '-nop4', '-game', game, qc] for qc in qc_jobs],
+ "cwd": asset_dir
+ }]
+
+ # VBSP stage
+ if settings.comp_compile:
+ static.JOBINFO += [{
+ "title": "VBSP",
+ "w": 25,
+ "colour": (0.1,0.2,1.0,1.0),
+ "exec": "vbsp",
+ "jobs": [[settings[F'exe_vbsp']] + args],
+ "cwd": directory
+ }]
+
+ static.JOBINFO += [{
+ "title": "VVIS",
+ "w": 25,
+ "colour": (0.9,0.5,0.5,1.0),
+ "exec": "vvis",
+ "jobs": [[settings[F'exe_vvis']] + ['-fast'] + args ],
+ "cwd": directory
+ }]
+
+ vrad_opt = settings.opt_vrad.split()
+ static.JOBINFO += [{
+ "title": "VRAD",
+ "w": 25,
+ "colour": (0.9,0.2,0.3,1.0),
+ "exec": "vrad",
+ "jobs": [[settings[F'exe_vrad']] + vrad_opt + args ],
+ "cwd": directory
+ }]
+
+ static.JOBINFO += [{
+ "title": "CXR",
+ "w": 5,
+ "colour": (0.0,1.0,0.4,1.0),
+ "exec": cxr_patchmap,
+ "jobs": [(bsp_local,bsp_remote)]
+ }]
+
+ if settings.comp_pack:
+ static.JOBINFO += [{
+ "title": "Pack",
+ "w": 5,
+ "colour": (0.2,0.2,0.2,1.0),
+ "exec": "bspzip",
+ "jobs": [[cxr_compiler_path("bspzip"), '-addlist', \
+ cxr_winepath(bsp_remote),
+ cxr_winepath(packlist),
+ cxr_winepath(bsp_packed) ]],
+ "cwd": directory
+ }]
+
+ if len(static.JOBINFO) == 0:
+ return {'CANCELLED'}
+
+ static.USER_EXIT=False
+ static.TIMER=wm.event_timer_add(0.1,window=context.window)
+ wm.modal_handler_add(_)
+
+ cxr_jobs_update_graph( static.JOBINFO )
+ scene_redraw()
+ return {'RUNNING_MODAL'}
+
+class CXR_RESET_HASHES(bpy.types.Operator):
+ bl_idname="convexer.hash_reset"
+ bl_label="Reset asset hashes"
+
+ def execute(_,context):
+ for c in bpy.data.collections:
+ c.cxr_data.last_hash = F"<RESET>{time.time()}"
+ c.cxr_data.asset_id=0
+
+ for t in bpy.data.images:
+ t.cxr_data.last_hash = F"<RESET>{time.time()}"
+ t.cxr_data.asset_id=0
+
+ return {'FINISHED'}
+
+class CXR_COMPILE_MATERIAL(bpy.types.Operator):
+ bl_idname="convexer.matcomp"
+ bl_label="Recompile Material"
+
+ def execute(_,context):
+ active_obj = bpy.context.active_object
+ active_mat = active_obj.active_material
+
+ #TODO: reduce code dupe (L1663)
+ for pair in compile_material(active_mat):
+ decl = pair[0]
+ pdef = pair[1]
+ prop = pair[2]
+
+ if isinstance(prop,bpy.types.Image):
+ flags = 0
+ if 'flags' in pdef: flags = pdef['flags']
+ prop.cxr_data.flags = flags
+
+ compile_image( prop )
+
+ settings = bpy.context.scene.cxr_data
+ with open(F'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o:
+ o.write(F'mat_reloadmaterial {asset_name(active_mat)}')
+
+ # TODO: Move this
+ with open(F'{settings.subdir}/cfg/convexer.cfg','w') as o:
+ o.write('sv_cheats 1\n')
+ o.write('mp_warmup_pausetimer 1\n')
+ o.write('bot_kick\n')
+ o.write('alias cxr_reload "exec convexer_mat_update"\n')
+
+ return {'FINISHED'}
+
+# Convexer panels
+# ------------------------------------------------------------------------------
+
+# Helper buttons for 3d toolbox view
+#
+class CXR_VIEW3D( bpy.types.Panel ):
+ bl_idname = "VIEW3D_PT_convexer"
+ bl_label = "Convexer"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_category = "Convexer"
+
+ @classmethod
+ def poll(cls, context):
+ return (context.object is not None)
+
+ def draw(_, context):
+ layout = _.layout
+ row = layout.row()
+ row.scale_y = 2
+ row.operator("convexer.preview")
+
+ if CXR_PREVIEW_OPERATOR.LASTERR != None:
+ box = layout.box()
+ box.label(text=CXR_PREVIEW_OPERATOR.LASTERR, icon='ERROR')
+
+# Main scene properties interface, where all the settings go
+#
+class CXR_INTERFACE(bpy.types.Panel):
+ bl_label="Convexer"
+ bl_idname="SCENE_PT_convexer"
+ bl_space_type='PROPERTIES'
+ bl_region_type='WINDOW'
+ bl_context="scene"
+
+ def draw(_,context):
+ _.layout.operator("convexer.reload")
+ _.layout.operator("convexer.dev_test")
+ _.layout.operator("convexer.preview")
+ _.layout.operator("convexer.hash_reset")
+
+ settings = context.scene.cxr_data
+
+ _.layout.prop(settings, "debug")
+ _.layout.prop(settings, "scale_factor")
+ _.layout.prop(settings, "skybox_scale_factor")
+ _.layout.prop(settings, "skyname" )
+ _.layout.prop(settings, "lightmap_scale")
+ _.layout.prop(settings, "light_scale" )
+ _.layout.prop(settings, "image_quality" )
+
+ box = _.layout.box()
+
+ box.prop(settings, "project_name")
+ box.prop(settings, "subdir")
+
+ box = _.layout.box()
+ box.operator("convexer.detect_compilers")
+ box.prop(settings, "exe_studiomdl")
+ box.prop(settings, "exe_vbsp")
+ box.prop(settings, "exe_vvis")
+ box.prop(settings, "exe_vrad")
+ box.prop(settings, "opt_vrad")
+
+ box = box.box()
+ row = box.row()
+ row.prop(settings,"comp_vmf")
+ row.prop(settings,"comp_textures")
+ row.prop(settings,"comp_models")
+ row.prop(settings,"comp_compile")
+ row.prop(settings,"comp_pack")
+
+ text = "Compile" if CXR_COMPILER_CHAIN.TIMER == None else "Cancel"
+ row = box.row()
+ row.scale_y = 3
+ row.operator("convexer.chain", text=text)
+
class CXR_MATERIAL_PANEL(bpy.types.Panel):
bl_label="VMT Properties"
info = material_info( active_material )
_.layout.label(text=F"{info['name']} @{info['res'][0]}x{info['res'][1]}")
- _.layout.prop( properties, "shader" )
+ row = _.layout.row()
+ row.prop( properties, "shader" )
+ row.operator( "convexer.matcomp" )
- for xk in info:
- _.layout.label(text=F"{xk}:={info[xk]}")
+ #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
def _mtex( name, img, uiParent ):
nonlocal properties
# Create ID properties
entdef = None
- classname = active_object.cxr_data.classname
+ classname = cxr_custom_class(active_object)
if classname in cxr_entities:
entdef = cxr_entities[classname]
if active_object == None: return
- default_context = cxr_object_context( \
- bpy.context.scene.cxr_data.scale_factor, 0.0 )
+ default_context = {
+ "scale": bpy.context.scene.cxr_data.scale_factor,
+ "offset": (0,0,0)
+ }
ecn = cxr_intrinsic_classname( active_object )
classname = cxr_custom_class( active_object )
_.layout.enabled=False
classname = ecn
- kvs = cxr_entity_keyvalues( active_object, default_context, classname )
+ kvs = cxr_entity_keyvalues( {
+ "object": active_object,
+ "transform": default_context,
+ "classname": classname
+ })
+
if kvs != None:
for kv in kvs:
if kv[1]:
elif active_object.type == 'LIGHT_PROBE':
layout.prop( properties, "size" )
+class CXR_COLLECTION_PANEL(bpy.types.Panel):
+ bl_label = "Source Settings"
+ bl_idname = "COL_PT_cxr"
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "collection"
+
+ def draw(self, context):
+ layout = self.layout
+ scene = context.scene
+
+ active_collection = bpy.context.collection
+
+ if active_collection != None:
+ layout.prop( active_collection.cxr_data, "shadow_caster" )
+ layout.prop( active_collection.cxr_data, "texture_shadows" )
+ layout.prop( active_collection.cxr_data, "preserve_order" )
+ layout.prop( active_collection.cxr_data, "surfaceprop" )
+
+# Settings groups
+# ------------------------------------------------------------------------------
+
class CXR_IMAGE_SETTINGS(bpy.types.PropertyGroup):
export_res: bpy.props.IntVectorProperty(
name="",
mipmap: bpy.props.BoolProperty(name="MIP",default=True)
lod: bpy.props.BoolProperty(name="LOD",default=True)
clamp: bpy.props.BoolProperty(name="CLAMP",default=False)
+ flags: bpy.props.IntProperty(name="flags",default=0)
class CXR_LIGHT_SETTINGS(bpy.types.PropertyGroup):
realtime: bpy.props.BoolProperty(name="Realtime Light", default=True)
class CXR_MODEL_SETTINGS(bpy.types.PropertyGroup):
last_hash: bpy.props.StringProperty( name="" )
asset_id: bpy.props.IntProperty(name="vmf_settings",default=0)
+ shadow_caster: bpy.props.BoolProperty( name="Shadow caster", default=True )
+ texture_shadows: bpy.props.BoolProperty( name="Texture Shadows", default=False )
+ preserve_order: bpy.props.BoolProperty( name="Preserve Order", default=False )
+ surfaceprop: bpy.props.StringProperty( name="Suface prop",default="default" )
class CXR_SCENE_SETTINGS(bpy.types.PropertyGroup):
project_name: bpy.props.StringProperty( name="Project Name" )
exe_vvis: bpy.props.StringProperty( name="vvis" )
opt_vvis: bpy.props.StringProperty( name="args" )
exe_vrad: bpy.props.StringProperty( name="vrad" )
- opt_vrad: bpy.props.StringProperty( name="args" )
+ opt_vrad: bpy.props.StringProperty( name="args", \
+ default="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
debug: bpy.props.BoolProperty(name="Debug",default=False)
scale_factor: bpy.props.FloatProperty( name="VMF Scale factor", \
default=32.0,min=1.0)
skybox_scale_factor: bpy.props.FloatProperty( name="Sky Scale factor", \
default=1.0,min=0.01)
-
+ skyname: bpy.props.StringProperty(name="Skyname",default="sky_csgo_night02b")
skybox_offset: bpy.props.FloatProperty(name="Sky offset",default=-4096.0)
light_scale: bpy.props.FloatProperty(name="Light Scale",default=1.0/5.0)
include_names: bpy.props.BoolProperty(name="Append original file names",\
default=True)
lightmap_scale: bpy.props.IntProperty(name="Global Lightmap Scale",\
default=12)
+ image_quality: bpy.props.IntProperty(name="Texture Quality (0-18)",\
+ default=8, min=0, max=18 )
-class CXR_DETECT_COMPILERS(bpy.types.Operator):
- bl_idname="convexer.detect_compilers"
- bl_label="Find compilers"
-
- def execute(self,context):
- scene = context.scene
- settings = scene.cxr_data
- subdir = settings.subdir
-
- for exename in ['studiomdl','vbsp','vvis','vrad']:
- searchpath = os.path.normpath(F'{subdir}/../bin/{exename}.exe')
- if os.path.exists(searchpath):
- settings[F'exe_{exename}'] = searchpath
-
- return {'FINISHED'}
+ comp_vmf: bpy.props.BoolProperty(name="VMF",default=True)
+ comp_models: bpy.props.BoolProperty(name="Models",default=True)
+ comp_textures: bpy.props.BoolProperty(name="Textures",default=True)
+ comp_compile: bpy.props.BoolProperty(name="Compile",default=True)
+ comp_pack: bpy.props.BoolProperty(name="Pack",default=False)
classes = [ CXR_RELOAD, CXR_DEV_OPERATOR, CXR_INTERFACE, \
- CXR_WRITE_VMF, CXR_MATERIAL_PANEL, CXR_IMAGE_SETTINGS,\
+ CXR_MATERIAL_PANEL, CXR_IMAGE_SETTINGS,\
CXR_MODEL_SETTINGS, CXR_ENTITY_SETTINGS, CXR_CUBEMAP_SETTINGS,\
CXR_LIGHT_SETTINGS, CXR_SCENE_SETTINGS, CXR_DETECT_COMPILERS,\
- CXR_ENTITY_PANEL, CXR_LIGHT_PANEL ]
+ CXR_ENTITY_PANEL, CXR_LIGHT_PANEL, CXR_PREVIEW_OPERATOR,\
+ CXR_VIEW3D, CXR_COMPILER_CHAIN, CXR_RESET_HASHES,\
+ CXR_COMPILE_MATERIAL, CXR_COLLECTION_PANEL ]
+
+vmt_param_dynamic_class = None
def register():
- global debug_draw_handler, vmt_param_dynamic_class
+ global cxr_view_draw_handler, vmt_param_dynamic_class, cxr_ui_handler
for c in classes:
bpy.utils.register_class(c)
# CXR Scene settings
# GPU / callbacks
- debug_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
+ cxr_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
cxr_draw,(),'WINDOW','POST_VIEW')
+ cxr_ui_handler = bpy.types.SpaceView3D.draw_handler_add(\
+ cxr_ui,(None,None),'WINDOW','POST_PIXEL')
+
bpy.app.handlers.load_post.append(cxr_on_load)
bpy.app.handlers.depsgraph_update_post.append(cxr_dgraph_update)
def unregister():
- global debug_draw_handler, vmt_param_dynamic_class
+ global cxr_view_draw_handler, vmt_param_dynamic_class, cxr_ui_handler
bpy.utils.unregister_class( vmt_param_dynamic_class )
for c in classes:
bpy.app.handlers.depsgraph_update_post.remove(cxr_dgraph_update)
bpy.app.handlers.load_post.remove(cxr_on_load)
- bpy.types.SpaceView3D.draw_handler_remove(debug_draw_handler,'WINDOW')
+ bpy.types.SpaceView3D.draw_handler_remove(cxr_view_draw_handler,'WINDOW')
+ bpy.types.SpaceView3D.draw_handler_remove(cxr_ui_handler,'WINDOW')