X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=__init__.py;h=c3dfacfda7c42633bc3fdbca514154b16c8f5709;hb=966e26e78d3da0b6136c95e418e2bb9ed491ad81;hp=0e4ddd283edf8fbbdf125f4f38b01793ee1dc2fe;hpb=730fbd0939c0f334b40840a1e97757f642366448;p=convexer.git diff --git a/__init__.py b/__init__.py index 0e4ddd2..c3dfacf 100644 --- a/__init__.py +++ b/__init__.py @@ -15,95 +15,211 @@ bl_info = { 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_decompose = 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') -class cxr_input_loop(Structure): + 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 + + 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)] @@ -115,19 +231,14 @@ class cxr_polygon(Structure): ("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)), @@ -137,435 +248,301 @@ class cxr_input_mesh(Structure): ("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_decompose - global 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_decompose = libcxr.cxr_decompose - libcxr_decompose.argtypes = [\ - POINTER(cxr_input_mesh) - ] - libcxr_decompose.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 -# -> / -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 -# -> // -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 ) -# -> /// -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 -def ent_get_origin(obj,context): - return obj.location * context.scale + LOG_FUNCTION_TYPE = CFUNCTYPE(None,c_char_p) + c_libcxr_log_callback = LOG_FUNCTION_TYPE(libcxr_log_callback) -def ent_get_angles(obj,context): + 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() + +# Configuration +# ------------------------------------------------------------------------------ + +# 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] @@ -573,91 +550,204 @@ def ent_get_angles(obj,context): 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['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look awful. - # Blender's default has a much more 'accurate' look - # They appear correct when using linear scale. + kvs['pitch'] = dir_pitch + kvs['angles'] = [ 0, dir_yaw, 0 ] + kvs['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look + # Really bad... + # + # Blender's default has a much more 'nice' + # look. 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 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] + 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 -cxr_entities = { - "info_player_counterterrorist": - { - "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 } - } + + kvs['enablelightbounce'] = 1 + kvs['disableshadows'] = 0 + 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 + kvs['uniformscale'] = 1.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 + +# -> / +def asset_name(asset): + return F"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}" + +# -> // +def asset_path(subdir, asset): + return F"{subdir}/{asset_name(asset_uid(asset))}" + +# -> /// +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" @@ -689,7 +779,9 @@ def cxr_classname(obj): # # 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 = [] @@ -697,7 +789,7 @@ def cxr_entity_keyvalues(obj,context,classname): 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] @@ -709,7 +801,7 @@ def cxr_entity_keyvalues(obj,context,classname): 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] @@ -718,6 +810,8 @@ def cxr_entity_keyvalues(obj,context,classname): return result +# Extract material information from shader graph data +# def material_info(mat): info = {} info['res'] = (512,512) @@ -731,11 +825,7 @@ def material_info(mat): 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 @@ -800,287 +890,214 @@ def material_info(mat): 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() - - mesh = cxr_input_mesh() - - 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]) - - loop_data = (cxr_input_loop*len(data.loops))() - polygon_data = (cxr_polygon*len(data.polygons))() - - 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 - - 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 - - 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 - - edge_data = (cxr_edge*len(data.edges))() +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]))) - 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 - - material_data = (cxr_material*len(obj.material_slots))() - - 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') +def cxr_collection_center(collection, transform): + BIG=999999999 + bounds_min = mathutils.Vector((BIG,BIG,BIG)) + bounds_max = mathutils.Vector((-BIG,-BIG,-BIG)) - 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) + for obj in collection.objects: + if obj.type == 'MESH': + corners = [ mathutils.Vector(c) for c in obj.bound_box ] - return mesh - -class CXR_WRITE_VMF(bpy.types.Operator): - bl_idname="convexer.write_vmf" - bl_label="Write VMF" - - 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}" - - os.makedirs( asset_dir, exist_ok=True ) - os.makedirs( material_dir, exist_ok=True ) - os.makedirs( model_dir, exist_ok=True ) - - # States - material_info.references = set() - libcxr_context_reset() - - 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() + 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 ) - 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') + center = (bounds_min + bounds_max) / 2.0 - # 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) + origin = mathutils.Vector((-center[1],center[0],center[2])) + origin *= transform['scale'] - # Export Brushes and displacement - def _collect(collection,transform): - if collection.name.startswith('.'): - return + return origin - if collection.name.startswith('mdl_'): - _collect.heros += [(collection,transform)] - return +# 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: + 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_ + } - for obj in collection.objects: - classname = cxr_classname( obj ) + 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) + } - if classname != None: - _collect.entities += [( obj,transform,classname )] - elif obj.type == 'MESH': - _collect.geo += [(obj,transform)] + transform_sky = { + "scale": context.scene.cxr_data.skybox_scale_factor, + "offset": (0,0,context.scene.cxr_data.skybox_offset ) + } - for c in collection.children: - _collect( c, transform ) - - _collect.a_models = set() - _collect.entities = [] - _collect.geo = [] + if 'main' in bpy.data.collections: + _collect( bpy.data.collections['main'], transform_main ) - transform_main = cxr_object_context( context.scene.cxr_data.scale_factor, 0.0 ) - transform_sky = cxr_object_context( context.scene.cxr_data.skybox_scale_factor, \ - context.scene.cxr_data.skybox_offset ) - - 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 ) - 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 ) + sceneinfo['entities'] += [{ + "object": None, + "transform": transform_sky, + "classname": "sky_camera" + }] - kvs = cxr_entity_keyvalues( obj, ctx, cls ) + return sceneinfo - 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]) ) +# Write VMF out to file (JOB HANDLER) +# +def cxr_export_vmf(sceneinfo, output_vmf): + cxr_reset_lines() - 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) + with vdf_structure(output_vmf) as m: + print( F"Write: {output_vmf}" ) + + vmfinfo = cxr_vmf_context() + vmfinfo.mapversion = 4 + + #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 + + libcxr_begin_vmf.call( pointer(vmfinfo), m.fp ) - m.edon() + def _buildsolid( cmd ): + nonlocal m - print( "[CONVEXER] Compile materials / textures" ) + print( F"{vmfinfo.brush_count} :: {cmd['object'].name}" ) - for mat in material_info.references: - compile_material(mat) + baked = mesh_cxr_format( cmd['object'] ) + world = libcxr_decompose.call( baked, None ) + + if world == None: + return False - print( "[CONVEXER] Compiling models" ) + vmfinfo.scale = cmd['transform']['scale'] - libcxr_batch_debug_lines() - scene_redraw() + offset = cmd['transform']['offset'] + vmfinfo.offset[0] = offset[0] + vmfinfo.offset[1] = offset[1] + vmfinfo.offset[2] = offset[2] - return {'FINISHED'} + libcxr_push_world_vmf.call( world, pointer(vmfinfo), m.fp ) + libcxr_free_world.call( world ) -class CXR_DECOMPOSE_SOLID(bpy.types.Operator): - bl_idname="convexer.decompose_solid" - bl_label="Decompose Solid" + return True - def execute(_,context): - libcxr_use() + # World geometry + for brush in sceneinfo['geo']: + if not _buildsolid( brush ): + cxr_batch_lines() + scene_redraw() + return False - # Prepare input data - mesh_src = mesh_cxr_format(context.active_object) + libcxr_vmf_begin_entities.call(pointer(vmfinfo), m.fp) - libcxr_reset_debug_lines() - libcxr_decompose( pointer(mesh_src) ) - libcxr_batch_debug_lines() - - scene_redraw() - return {'FINISHED'} - -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" + # Entities + for ent in sceneinfo['entities']: + obj = ent['object'] + ctx = ent['transform'] + cls = ent['classname'] - def draw(_,context): - _.layout.operator("convexer.reload") - _.layout.operator("convexer.decompose_solid") - _.layout.operator("convexer.write_vmf") + m.node( 'entity' ) + m.kv( 'classname', cls ) - settings = context.scene.cxr_data + kvs = cxr_entity_keyvalues( ent ) - _.layout.prop(settings, "debug") - _.layout.prop(settings, "scale_factor") - _.layout.prop(settings, "lightmap_scale") - _.layout.prop(settings, "light_scale" ) - - box = _.layout.box() + 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 - box.prop(settings, "project_name") - box.prop(settings, "subdir") + m.edon() - 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") + print( "Done" ) + return True -# COmpile image using NBVTF and hash it -def compile_image(img,flags): +# COmpile image using NBVTF and hash it (JOB HANDLER) +# +def compile_image(img): if img==None: return None @@ -1089,19 +1106,22 @@ def compile_image(img,flags): 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 - userflag_hash = F"{mipmap}.{lod}.{clamp}" + q=bpy.context.scene.cxr_data.image_quality + + 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}" ) @@ -1117,19 +1137,26 @@ def compile_image(img,flags): 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 @@ -1179,6 +1206,7 @@ def compile_material(mat): _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" ) @@ -1190,16 +1218,11 @@ def compile_material(mat): 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): @@ -1212,6 +1235,772 @@ def compile_material(mat): 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]} {origin[1]} {origin[2]}\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"]\ + [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"{time.time()}" + c.cxr_data.asset_id=0 + + for t in bpy.data.images: + t.cxr_data.last_hash = F"{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" @@ -1231,10 +2020,11 @@ class CXR_MATERIAL_PANEL(bpy.types.Panel): 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 @@ -1283,7 +2073,8 @@ class CXR_MATERIAL_PANEL(bpy.types.Panel): expandview = True drawthis = True - if 'shaders' in pdef and properties.shader not in pdef['shaders']: + if ('shaders' in pdef) and \ + (properties.shader not in pdef['shaders']): continue if ptype == 'intrinsic': @@ -1309,7 +2100,7 @@ class CXR_MATERIAL_PANEL(bpy.types.Panel): else: # hidden intrinsic value. # Means its a float array or something not an image - thisnode.label( text=F"-- hidden intrinsic '{decl}' --" ) + thisnode.label(text=F"-- hidden intrinsic '{decl}' --") else: thisnode.prop(properties,decl) if expandview: _mview(pdef,thisnode) @@ -1321,7 +2112,7 @@ def cxr_entity_changeclass(_,context): # 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] @@ -1352,12 +2143,17 @@ class CXR_ENTITY_PANEL(bpy.types.Panel): 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 ) if ecn == None: - if active_object.type == 'MESH': _.layout.prop( active_object.cxr_data, 'brushclass' ) + if active_object.type == 'MESH': + _.layout.prop( active_object.cxr_data, 'brushclass' ) else: _.layout.prop( active_object.cxr_data, 'classname' ) if classname == 'NONE': @@ -1367,7 +2163,12 @@ class CXR_ENTITY_PANEL(bpy.types.Panel): _.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]: @@ -1403,6 +2204,9 @@ class CXR_LIGHT_PANEL(bpy.types.Panel): elif active_object.type == 'LIGHT_PROBE': layout.prop( properties, "size" ) +# Settings groups +# ------------------------------------------------------------------------------ + class CXR_IMAGE_SETTINGS(bpy.types.PropertyGroup): export_res: bpy.props.IntVectorProperty( name="", @@ -1429,6 +2233,7 @@ class CXR_IMAGE_SETTINGS(bpy.types.PropertyGroup): 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) @@ -1483,41 +2288,42 @@ class CXR_SCENE_SETTINGS(bpy.types.PropertyGroup): 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) + 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) - displacement_cardinal: bpy.props.BoolProperty(name="Cardinal displacements",default=True) - include_names: bpy.props.BoolProperty(name="Append original file names",default=True) - lightmap_scale: bpy.props.IntProperty(name="Global Lightmap Scale",default=12) - -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'} - -classes = [ CXR_RELOAD, CXR_DECOMPOSE_SOLID, CXR_INTERFACE, \ - CXR_WRITE_VMF, CXR_MATERIAL_PANEL, CXR_IMAGE_SETTINGS,\ + 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 ) + + 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_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] + +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) @@ -1580,7 +2386,8 @@ def register(): '') for _ in cxr_shaders],\ default = next(iter(cxr_shaders))) - annotations_dict["asset_id"] = bpy.props.IntProperty(name="intl_assetid",default=0) + annotations_dict["asset_id"] = bpy.props.IntProperty(name="intl_assetid",\ + default=0) _dvmt_propogate( cxr_shader_params ) vmt_param_dynamic_class = type( @@ -1611,14 +2418,17 @@ def register(): # 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: @@ -1627,4 +2437,5 @@ def unregister(): 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')