X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=__init__.py;h=f8747467157eb627bba10d62dffc6d63fcc32a66;hb=d2457a5d45ca4ae6a56a35f9bc99061846eb61d8;hp=0a5ddcf0b9b365212c9f439a1048bb94d5075fdd;hpb=9a92cec0e25c758d12c535bb737be598f053b56d;p=convexer.git diff --git a/__init__.py b/__init__.py index 0a5ddcf..f874746 100644 --- a/__init__.py +++ b/__init__.py @@ -15,7 +15,7 @@ bl_info = { print( "Convexer reload" ) #from mathutils import * -import bpy, gpu, math, os, time, mathutils, blf +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 @@ -81,12 +81,19 @@ def cxr_ui(_,context): blf.position(0,ji[0]*w,35,0) blf.size(0,50,20) blf.draw(0,ji[1]) - - 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,"This is a stoopid error\nWIthiuawdnaw") + + 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 + + #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") # Something is off with TIMER, # this forces the viewport to redraw before we can continue with our @@ -147,7 +154,7 @@ def cxr_jobs_update_graph(jobs): else: colour = colourwait px = (cur + (i)*sfsub) * sf - px1 = (cur + (i+1.0)*sfsub) * sf - 0.003 + px1 = (cur + (i+1.0)*sfsub) * sf i += 1 verts += [(px,0), (px, h), (px1, 0.0), (px1,h)] @@ -463,8 +470,8 @@ def libcxr_line_callback( p0,p1,colour ): libnbvtf = None # Constants -NBVTF_IMAGE_FORMAT_RGBA8888 = 0 -NBVTF_IMAGE_FORMAT_RGB888 = 2 +NBVTF_IMAGE_FORMAT_ABGR8888 = 1 +NBVTF_IMAGE_FORMAT_BGR888 = 3 NBVTF_IMAGE_FORMAT_DXT1 = 13 NBVTF_IMAGE_FORMAT_DXT5 = 15 NBVTF_TEXTUREFLAGS_CLAMPS = 0x00000004 @@ -528,10 +535,12 @@ shared_reload() # Standard entity functions, think of like base.fgd # -def cxr_get_origin(obj,context): - return obj.location * context['scale'] + mathutils.Vector(context['offset']) +def cxr_get_origin(context): + return context['object'].location * context['transform']['scale'] + \ + mathutils.Vector(context['transform']['offset']) -def cxr_get_angles(obj,context): +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] @@ -547,30 +556,36 @@ def cxr_baseclass(classes, other): # EEVEE Light component converter -> Source 1 # -def ent_lights(obj,context): +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... # @@ -579,15 +594,81 @@ def ent_lights(obj,context): 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): +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 + + + 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 } @@ -622,7 +703,7 @@ def asset_uid(asset): return asset # Create a unique ID string - base = "ABCDEFGHIJKLMNOPQRSTUV" + base = "bopshei" v = asset.cxr_data.asset_id name = "" @@ -664,7 +745,7 @@ def cxr_intrinsic_classname(obj): 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" @@ -696,7 +777,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 = [] @@ -704,7 +787,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] @@ -716,7 +799,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] @@ -805,6 +888,31 @@ def material_info(mat): return info +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]))) + +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 ] + + 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 ) + + center = (bounds_min + bounds_max) / 2.0 + + origin = mathutils.Vector((-center[1],center[0],center[2])) + origin *= transform['scale'] + + return origin + # Prepares Scene into dictionary format # def cxr_scene_collect(): @@ -842,9 +950,17 @@ def cxr_scene_collect(): 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 + "transform": transform, + "origin": cxr_collection_center( collection, transform ) }] return @@ -866,7 +982,7 @@ def cxr_scene_collect(): }] for c in collection.children: - cxr_scene_collect( c, transform ) + _collect( c, transform ) transform_main = { "scale": context.scene.cxr_data.scale_factor, @@ -884,29 +1000,18 @@ def cxr_scene_collect(): if 'skybox' in bpy.data.collections: _collect( bpy.data.collections['skybox'], transform_sky ) + sceneinfo['entities'] += [{ + "object": None, + "transform": transform_sky, + "classname": "sky_camera" + }] + return sceneinfo # Write VMF out to file (JOB HANDLER) # -def cxr_export_vmf(sceneinfo): - cxr_reset_lines() - - # Setup output and state - filepath = bpy.data.filepath - directory = os.path.dirname(filepath) - settings = bpy.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 +def cxr_export_vmf(sceneinfo, output_vmf): cxr_reset_lines() - output_vmf = F"{directory}/{settings.project_name}.vmf" with vdf_structure(output_vmf) as m: print( F"Write: {output_vmf}" ) @@ -915,7 +1020,7 @@ def cxr_export_vmf(sceneinfo): vmfinfo.mapversion = 4 #TODO: These need to be in options... - vmfinfo.skyname = b"sky_csgo_night02b" + 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 @@ -928,6 +1033,8 @@ def cxr_export_vmf(sceneinfo): def _buildsolid( cmd ): nonlocal m + + print( F"{vmfinfo.brush_count} :: {cmd['object'].name}" ) baked = mesh_cxr_format( cmd['object'] ) world = libcxr_decompose.call( baked, None ) @@ -965,18 +1072,21 @@ def cxr_export_vmf(sceneinfo): m.node( 'entity' ) m.kv( 'classname', cls ) - kvs = cxr_entity_keyvalues( obj, ctx, cls ) + 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.type == 'MESH': - if not _buildsolid( ent ): - cxr_batch_lines() - scene_redraw() - return False + 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 m.edon() @@ -994,10 +1104,10 @@ def compile_image(img): 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 @@ -1035,11 +1145,13 @@ def compile_image(img): # 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 @@ -1123,6 +1235,129 @@ def compile_material(mat): vmt.edon() return props +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 # ------------------------------------------------------------------------------ @@ -1197,7 +1432,8 @@ class CXR_PREVIEW_OPERATOR(bpy.types.Operator): "No-Candidate",\ "Internal-Fail",\ "Non-Coplanar",\ - "Non-Convex Polygon"]\ + "Non-Convex Polygon",\ + "Bad Result"]\ [err.value] if static.RUNNING: @@ -1271,11 +1507,15 @@ class CXR_COMPILER_CHAIN(bpy.types.Operator): 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 @@ -1286,7 +1526,10 @@ class CXR_COMPILER_CHAIN(bpy.types.Operator): if static.TIMER != None: wm.event_timer_remove( static.TIMER ) static.TIMER = None + + static.FILE.close() + cxr_jobs_batch = None scene_redraw() return {'FINISHED'} @@ -1294,7 +1537,10 @@ class CXR_COMPILER_CHAIN(bpy.types.Operator): static = _.__class__ if ev.type == 'TIMER': + global cxr_jobs_batch + if static.WAIT_REDRAW: + scene_redraw() return {'PASS_THROUGH'} static.WAIT_REDRAW = True @@ -1304,20 +1550,53 @@ class CXR_COMPILER_CHAIN(bpy.types.Operator): if static.SUBPROC != None: # Deal with async modes - pass + 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: - print( F"Start job: {static.JOBID} @{time.time()}" ) - - if not sys['exec'](target): - print( "Job failed" ) - return _.cancel(context) - sys['jobs'][i] = None - static.JOBID += 1 + 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() @@ -1325,7 +1604,6 @@ class CXR_COMPILER_CHAIN(bpy.types.Operator): # All completed print( "All jobs completed!" ) - global cxr_jobs_batch cxr_jobs_batch = None scene_redraw() @@ -1341,14 +1619,77 @@ class CXR_COMPILER_CHAIN(bpy.types.Operator): 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" + + 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: @@ -1361,30 +1702,98 @@ class CXR_COMPILER_CHAIN(bpy.types.Operator): flags = 0 if 'flags' in pdef: flags = pdef['flags'] if prop not in image_jobs: - image_jobs += [prop] + image_jobs += [(prop,)] prop.cxr_data.flags = flags # Convexer jobs static.JOBID = 0 static.JOBINFO = [] - - static.JOBINFO += [{ - "title": "Convexer", - "w": 40, - "colour": (1.0,0.3,0.1,1.0), - "exec": cxr_export_vmf, - "jobs": [sceneinfo] - }] - 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 + 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 = 'z:'+settings.subdir.replace('/','\\') + 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": [(F"{directory}/{settings.project_name}.bsp",\ + F"{settings.subdir}/maps/{settings.project_name}.bsp")] + }] + static.USER_EXIT=False static.TIMER=wm.event_timer_add(0.1,window=context.window) wm.modal_handler_add(_) @@ -1397,6 +1806,55 @@ class CXR_COMPILER_CHAIN(bpy.types.Operator): static.USER_EXIT=True 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 # ------------------------------------------------------------------------------ @@ -1436,11 +1894,14 @@ class CXR_INTERFACE(bpy.types.Panel): _.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" ) @@ -1456,6 +1917,14 @@ class CXR_INTERFACE(bpy.types.Panel): 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") text = "Compile" if CXR_COMPILER_CHAIN.TIMER == None else "Cancel" row = box.row() @@ -1481,10 +1950,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 @@ -1572,7 +2042,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] @@ -1603,8 +2073,10 @@ 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 ) @@ -1621,7 +2093,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]: @@ -1741,14 +2218,15 @@ 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) - + 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",\ @@ -1758,13 +2236,18 @@ class CXR_SCENE_SETTINGS(bpy.types.PropertyGroup): 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) 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_PREVIEW_OPERATOR,\ - CXR_VIEW3D, CXR_COMPILER_CHAIN ] + CXR_VIEW3D, CXR_COMPILER_CHAIN, CXR_RESET_HASHES,\ + CXR_COMPILE_MATERIAL] vmt_param_dynamic_class = None