├── .gitignore ├── .idea └── .gitignore ├── README.md └── utils ├── mdl_to_vmdl.py ├── old_versions ├── README.md ├── global_vars.txt ├── vmt_to_vmat.py └── vmt_to_vmat_dota.py ├── qc_to_vmdl.py ├── vmt_to_vmat.py └── working files └── vmf_convert.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | \.vscode/ 3 | utils/vmt_to_vmat/VTFLibWrapper/__init__.py 4 | utils/vmt_to_vmat/VTFLibWrapper/__pycache__/__init__.cpython-37.pyc 5 | utils/vmt_to_vmat/VTFLibWrapper/__pycache__/__init__.cpython-38.pyc 6 | utils/vmt_to_vmat/VTFLibWrapper/__pycache__/VTFLib.cpython-37.pyc 7 | utils/vmt_to_vmat/VTFLibWrapper/__pycache__/VTFLib.cpython-38.pyc 8 | utils/vmt_to_vmat/VTFLibWrapper/__pycache__/VTFLibConstants.cpython-37.pyc 9 | utils/vmt_to_vmat/VTFLibWrapper/__pycache__/VTFLibConstants.cpython-38.pyc 10 | utils/vmt_to_vmat/VTFLibWrapper/__pycache__/VTFLibEnums.cpython-37.pyc 11 | utils/vmt_to_vmat/VTFLibWrapper/__pycache__/VTFLibEnums.cpython-38.pyc 12 | utils/vmt_to_vmat/VTFLibWrapper/__pycache__/VTFLibStructures.cpython-37.pyc 13 | utils/vmt_to_vmat/VTFLibWrapper/__pycache__/VTFLibStructures.cpython-38.pyc 14 | utils/vmt_to_vmat/VTFLibWrapper/bin/DevIL.dll 15 | utils/vmt_to_vmat/VTFLibWrapper/bin/HLLib.dll 16 | utils/vmt_to_vmat/VTFLibWrapper/bin/libtxc_dxtn.so 17 | utils/vmt_to_vmat/VTFLibWrapper/bin/libVTFLib13.so 18 | utils/vmt_to_vmat/VTFLibWrapper/bin/VTFCmd.exe 19 | utils/vmt_to_vmat/VTFLibWrapper/bin/VTFEdit.exe 20 | utils/vmt_to_vmat/VTFLibWrapper/bin/VTFLib.dll 21 | utils/vmt_to_vmat/VTFLibWrapper/bin/VTFLib.x64.dll 22 | utils/vmt_to_vmat/VTFLibWrapper/bin/VTFLib.x86.dll 23 | utils/vmt_to_vmat/VTFLibWrapper/bin/x64/DevIL.dll 24 | utils/vmt_to_vmat/VTFLibWrapper/bin/x64/HLLib.dll 25 | utils/vmt_to_vmat/VTFLibWrapper/bin/x64/VTFCmd.exe 26 | utils/vmt_to_vmat/VTFLibWrapper/bin/x64/VTFEdit.exe 27 | utils/vmt_to_vmat/VTFLibWrapper/bin/x64/VTFLib.dll 28 | utils/vmt_to_vmat/VTFLibWrapper/bin/x86/DevIL.dll 29 | utils/vmt_to_vmat/VTFLibWrapper/bin/x86/HLLib.dll 30 | utils/vmt_to_vmat/VTFLibWrapper/bin/x86/VTFCmd.exe 31 | utils/vmt_to_vmat/VTFLibWrapper/bin/x86/VTFEdit.exe 32 | utils/vmt_to_vmat/VTFLibWrapper/bin/x86/VTFLib.dll 33 | utils/vmt_to_vmat/VTFLibWrapper/lib/VTFLib.h 34 | utils/vmt_to_vmat/VTFLibWrapper/lib/x64/VTFLib.lib 35 | utils/vmt_to_vmat/VTFLibWrapper/lib/x86/VTFLib.lib 36 | utils/vmt_to_vmat/VTFLibWrapper/VTFLib.py 37 | utils/vmt_to_vmat/VTFLibWrapper/VTFLibConstants.py 38 | utils/vmt_to_vmat/VTFLibWrapper/VTFLibEnums.py 39 | utils/vmt_to_vmat/VTFLibWrapper/VTFLibStructures.py 40 | .idea/codeStyles/codeStyleConfig.xml 41 | .idea/inspectionProfiles/profiles_settings.xml 42 | .idea/misc.xml 43 | .idea/modules.xml 44 | .idea/source2utils.iml 45 | .idea/vcs.xml 46 | utils/extract_alpha_channel.py 47 | utils/extract_alpha_channel.py 48 | utils/extract_alpha_channel.py 49 | utils/working files/extract_alpha_channel.py 50 | utils/__pycache__/mdl_to_vmdl.cpython-38.pyc 51 | utils/__pycache__/vmt_to_vmat.cpython-38.pyc 52 | utils/build/mdl_to_vmdl/Analysis-00.toc 53 | utils/build/mdl_to_vmdl/base_library.zip 54 | utils/build/mdl_to_vmdl/EXE-00.toc 55 | utils/build/mdl_to_vmdl/mdl_to_vmdl.exe.manifest 56 | utils/build/mdl_to_vmdl/PKG-00.pkg 57 | utils/build/mdl_to_vmdl/PKG-00.toc 58 | utils/build/mdl_to_vmdl/PYZ-00.pyz 59 | utils/build/mdl_to_vmdl/PYZ-00.toc 60 | utils/build/mdl_to_vmdl/Tree-00.toc 61 | utils/build/mdl_to_vmdl/Tree-01.toc 62 | utils/build/mdl_to_vmdl/warn-mdl_to_vmdl.txt 63 | utils/build/mdl_to_vmdl/xref-mdl_to_vmdl.html 64 | utils/build/vmt_to_vmat/Analysis-00.toc 65 | utils/build/vmt_to_vmat/base_library.zip 66 | utils/build/vmt_to_vmat/COLLECT-00.toc 67 | utils/build/vmt_to_vmat/EXE-00.toc 68 | utils/build/vmt_to_vmat/PKG-00.pkg 69 | utils/build/vmt_to_vmat/PKG-00.toc 70 | utils/build/vmt_to_vmat/PYZ-00.pyz 71 | utils/build/vmt_to_vmat/PYZ-00.toc 72 | utils/build/vmt_to_vmat/Tree-00.toc 73 | utils/build/vmt_to_vmat/Tree-01.toc 74 | utils/build/vmt_to_vmat/vmt_to_vmat.exe 75 | utils/build/vmt_to_vmat/vmt_to_vmat.exe.manifest 76 | utils/build/vmt_to_vmat/warn-vmt_to_vmat.txt 77 | utils/build/vmt_to_vmat/xref-vmt_to_vmat.html 78 | utils/dist/dist.7z 79 | utils/doom_to_vmat.py 80 | utils/test_process.py 81 | utils/vmt_to_vmat.spec 82 | utils/vmt_to_vmat_backup.py 83 | utils/VTFLibWrapper/bin/VTFLib.x64.dll 84 | utils/VTFLibWrapper/bin/VTFLib.x86.dll 85 | utils/VTFLibWrapper/README.md 86 | utils/VTFLibWrapper/VTFLib.py 87 | utils/VTFLibWrapper/VTFLibConstants.py 88 | utils/VTFLibWrapper/VTFLibEnums.py 89 | utils/VTFLibWrapper/VTFLibStructures.py 90 | utils/mdl_to_vmdl.spec 91 | utils/dist/mdl_to_vmdl.exe 92 | utils/dist/vmt_to_vmat.exe 93 | utils/VTFLibWrapper/bin/libVTFLib13.so 94 | utils/VTFLibWrapper/bin/libtxc_dxtn.so 95 | utils/VTFLibWrapper/__pycache__/VTFLibStructures.cpython-38.pyc 96 | utils/VTFLibWrapper/__pycache__/VTFLibEnums.cpython-38.pyc 97 | utils/VTFLibWrapper/__pycache__/VTFLibConstants.cpython-38.pyc 98 | utils/VTFLibWrapper/__pycache__/VTFLib.cpython-38.pyc 99 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ***HEY! These utilities are still broken in a lot of ways and will fail very often. Please use with the understanding that it doesn't perfectly convert files yet and will only go through certain texture sets perfectly.*** 2 | 3 | # source2utils 4 | 5 | This is a 3rd generation fork, first created by Rectus and then Forked by Alpyne and Caseytube. These are a set of scripts to help convert Source 1 assets to Source 2 with ease, partly using the tools Valve already have available, and using a materials script that takes a lot of guesswork. These tools were intended to be used with the Source 2 Filmmaker, but can be applied to any Source 2 project. 6 | 7 | ![screenie-1](https://i.imgur.com/XvADzGe.jpg) 8 | ![screenie-2](https://i.imgur.com/yYJu0fI.jpg) 9 | 10 | Below is a list of branches that have been tested with the tool (with the HL:A Bootleg Tools): 11 | - Source Filmmaker Branch (most content from this build is stable and should work nicely.) 12 | - Half-Life: 2 (character models and some others crash while trying to compile, but then finish compiling fine) 13 | - Counter-Strike: Source 14 | - Black Mesa Source (same as HL2) 15 | - Team Fortress 2 (Models compile fine, but HWM characters are pretty well broken) 16 | 17 | Below is a list of branches that DON'T work with this tool (with the HL:A Bootleg Tools): 18 | - Left 4 Dead 2 (most content works, but player models crash the engine.) 19 | - Half-Life 1 (this should be obvious but just in-case you were curious) 20 | 21 | ## Installation Instructions 22 | 1. Go to the Releases Tab on the top right of this page 23 | 24 | 2. Download the latest release and extract to anywhere. 25 | 26 | 3. Download and set up the [Half-Life Alyx Bootleg Tools](https://github.com/thenayr/Half-Life-Alyx-SDK) 27 | 28 | 4. Run the scripts as instructed below. 29 | 30 | ## vmt_to_vmat.py 31 | 32 | A more advanced script meant to help ease the conversion process of materials from Source 1's phong-specular approach to the modern PBR-specular approach in most Source 2 games. 33 | 34 | **I would recommend to run this script first as it prepares your modname_imported folder, which will be where your imported content will live.** 35 | 36 | To run, you will need to have all your materials in the content folder of your target game (for instance, steamapps/common/Half-Life Alyx/content/tf/materials), **with the .VTFs converted to .TGAs (best to do this with VTFEdit's "Tools->Convert Folder" process.)** Then, run the tool and point it in the direction of the "materials" folder or the specific .VMT you wish to convert. This will create a new content folder called "modname_imported" which will be where your imported content will live and be compiled out of. 37 | 38 | Once that's done, open your tools and it will hang for a minute. I'd recommend listing "Materials" as the only asset type on the top right, going into "List" mode on the top left of the Asset Browser, and then selecting all of your materials by pressing "CTRL+A" and right clicking them, then clicking "Force Recompile." This will take a long time depending on your computer and how many materials you have, so set this up and walk away for a bit. After that's done, just right click and select "Refresh Thumbnails" and your content should be pretty easy to load from there. 39 | 40 | ## mdl_to_vmdl.py 41 | 42 | Generates a .vmdl file that will tell Source 2 to import its accompanying .mdl file. You **must** leave the .mdl with the .vmdl file, or else it won't compile! 43 | 44 | To run, you will need to have all your models in the content folder of your target game (for instance, steamapps/common/Half-Life Alyx/content/tf_imported/models). This should be the same folder as the path generated by the vmt_to_vmat tool. Then, run the tool and point it in the direction of the "models" folder or the specific .MDL you wish to convert. 45 | 46 | Once that's done, open your tools and it will hang for a minute. I'd recommend listing "Models" as the only asset type on the top right, going into "List" mode on the top left of the Asset Browser, and then selecting all of your models by pressing "CTRL+A" and right clicking them, then clicking "Force Recompile." This will take a long time depending on your computer and how many materials you have, so set this up and walk away for a bit. After that's done, just right click and select "Refresh Thumbnails" and your content should be pretty easy to load from there. 47 | 48 | ## qc_to_vmdl.py 49 | 50 | (deprecated) 51 | 52 | An older attempt at converting models before I figured out how to directly import .mdl files. 53 | You can use this as a base if you want to import the source files manually. 54 | 55 | ## Troubleshooting 56 | ##### *I got an error when converting materials! Something about not being able to convert something to something!* 57 | 58 | If you get an error that says something like "ValueError: could not convert string to float," most likely your .VMT file has a parameter in it that is incorrectly formatted. Please refer to the [Valve Developer Community](https://developer.valvesoftware.com/wiki/Category:List_of_Shader_Parameters) for instructions on how your .VMTs should be formatted. Be sure to check for white space after the value, or white space inside of the quotation marks of the value. I also see a lot of `s (grave accents) where there should be quotes. 59 | 60 | The plan is to stomp parsing errors like this out, 61 | 62 | ##### *My materials are appering as errors in the engine!* 63 | 64 | There may be a couple non-uniform textures that don't parse correctly, and so the materials may show up as an error. To fix these, you must go to the material editor and find the problematic texture and correct it. Most likely, it will just be read as blank and 65 | 66 | ##### *The material I'm trying to use is not compiling!* 67 | 68 | Some materials, like sprites for particles, or cable materials for the old cable system, aren't converted over just yet. My main goal with this right now is to pass the VertexLitGeneric and LightmappedGeneric materials, with those other ones coming next. 69 | 70 | In the interm, if you'd like to add an unsupported shader to the script, just download the original .py script and add your shaders into the "Supported Shaders" class on the top of the script. This will force them to pass through, but be warned that if your shader doesn't use $basetexture or any of the other standard .VMT parameters, it won't know what it's doing and will most likely compile incorrectly. 71 | 72 | ## System Requirements (for running the Python Scripts): 73 | Python 3.7 or later 74 | 75 | [Python Image Library (PIL) 5.4.1](https://pillow.readthedocs.io/en/5.1.x/installation.html) 76 | 77 | [The Half-Life Alyx Bootleg Tools](https://github.com/thenayr/Half-Life-Alyx-SDK) 78 | 79 | A Source 1 game's content, with the .vtfs extracted to .tgas in the same file structure as the .vmts 80 | -------------------------------------------------------------------------------- /utils/mdl_to_vmdl.py: -------------------------------------------------------------------------------- 1 | # cmd command: python mdl_to_vmdl.py "C:\Program Files (x86)\Steam\steamapps\common\SteamVR\tools\steamvr_environments\content\steamtours_addons\l4d2_converted\models" 2 | # MUST run in the models folder 3 | 4 | import re, sys, os 5 | 6 | INPUT_FILE_EXT = '.mdl' 7 | OUTPUT_FILE_EXT = '.vmdl' 8 | 9 | VMDL_BASE = ''' 10 | { 11 | m_sMDLFilename = "" 12 | } 13 | ''' 14 | 15 | def text_parser(filepath, separator="="): 16 | return_dict = {} 17 | with open(filepath, "r") as f: 18 | for line in f: 19 | if not line.startswith("//") or line in ['\n', '\r\n'] or line.strip() == '': 20 | line = line.replace('\t', '').replace('\n', '') 21 | line = line.split(separator) 22 | return_dict[line[0]] = line[1] 23 | return return_dict 24 | 25 | def walk_dir(dirname): 26 | files = [] 27 | 28 | for root, dirs, filenames in os.walk(dirname): 29 | for filename in filenames: 30 | if filename.lower().endswith(INPUT_FILE_EXT): 31 | files.append(os.path.join(root,filename)) 32 | 33 | return files 34 | 35 | def putl(f, line, indent = 0): 36 | f.write(('\t' * indent) + line + '\r\n') 37 | 38 | def strip_quotes(s): 39 | return s.strip('"').strip("'") 40 | 41 | def fix_path(s): 42 | return strip_quotes(s).replace('\\', '/').replace('//', '/').strip('/') 43 | 44 | def relative_path(s, base): 45 | base = base.replace(abspath, '') 46 | base = base.replace(os.path.basename(base), '') 47 | 48 | return fix_path(os.path.basename(abspath) + base + '/' + fix_path(s)) 49 | 50 | 51 | def get_mesh_name(file): 52 | return os.path.splitext(os.path.basename(fix_path(file)))[0] 53 | 54 | print('--------------------------------------------------------------------------------------------------------') 55 | print('Source 2 VMDL Generator! By Rectus via Github.') 56 | print('Initially forked by Alpyne, this version by caseytube.') 57 | print('--------------------------------------------------------------------------------------------------------') 58 | print('Reminder to put your models in the same directory structure as Source 1, starting with models!\n') 59 | abspath = '' 60 | files = [] 61 | 62 | PATH_TO_CONTENT_ROOT = input("What folder would you like to convert? Valid Format: C:\\Steam\\steamapps\\Half-Life Alyx\\content\\tf\\models\\props_spytech\\: ").lower() 63 | if not os.path.exists(PATH_TO_CONTENT_ROOT): 64 | print("Please respond with a valid folder or file path! Quitting Process!") 65 | quit() 66 | 67 | # recursively search all dirs and files 68 | abspath = os.path.abspath(PATH_TO_CONTENT_ROOT) 69 | print(abspath) 70 | if os.path.isdir(abspath): 71 | files.extend(walk_dir(abspath)) 72 | #else: 73 | # if abspath.lower().endswith(INPUT_FILE_EXT): 74 | # files.append(abspath) 75 | 76 | for filename in files: 77 | out_name = filename.replace(INPUT_FILE_EXT, OUTPUT_FILE_EXT) 78 | #if os.path.exists(out_name): continue 79 | 80 | print('Importing', os.path.basename(filename)) 81 | 82 | out = sys.stdout 83 | 84 | sourcePath = "models" + filename.split("models", 1)[1] # HACK? 85 | mdl_path = fix_path(sourcePath) 86 | 87 | with open(out_name, 'w') as out: 88 | putl(out, VMDL_BASE.replace('', mdl_path).replace((' ' * 4), '\t')) 89 | 90 | input("Press the key to close...") -------------------------------------------------------------------------------- /utils/old_versions/README.md: -------------------------------------------------------------------------------- 1 | These are old versions of the scripts, kept here for compatibility reasons or otherwise. Eventually these will dissapear, but are kept around for reference and are not required to run the current versions in the root folder. -------------------------------------------------------------------------------- /utils/old_versions/global_vars.txt: -------------------------------------------------------------------------------- 1 | // NOTE: Keep your variables here as plain as possible. Adding things like quotation marks may cause your script to flip out. 2 | // Adjust to your own root content folder 3 | gameContentRoot = C:\Program Files (x86)\Steam\steamapps\common\SteamVR\tools\steamvr_environments\content\steamtours_addons\ 4 | // Default reflectance range, defines how strong the envMap effect is. Usually needs to be toned down for PBR shaders in S2. 5 | reflectanceRange = g_vReflectanceRange "[0.000 0.050]" -------------------------------------------------------------------------------- /utils/old_versions/vmt_to_vmat.py: -------------------------------------------------------------------------------- 1 | # Converts Source 1 .vmt material files to simple Source 2 .vmat files. 2 | # 3 | # Copyright (c) 2016 Rectus 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | # Usage Instructions: 24 | # python vmt_to_vmat.py MODNAME OPTIONAL_PATH_TO_FOLDER 25 | 26 | import sys 27 | import os 28 | import os.path 29 | from os import path 30 | import re 31 | from PIL import Image 32 | import PIL.ImageOps 33 | 34 | # What shader to use. 35 | SHADER = 'vr_standard' 36 | # File format of the textures. 37 | TEXTURE_FILEEXT = '.tga' 38 | # substring added after an alpha map's name, but before the extension 39 | MAP_SUBSTRING = '_alpha' 40 | # this leads to the root of the game folder, i.e. dota 2 beta/content/dota_addons/, make sure to remember the final slash!! 41 | PATH_TO_GAME_CONTENT_ROOT = "" 42 | PATH_TO_CONTENT_ROOT = "" 43 | # Set this to True if you wish to overwrite your old vmat files 44 | OVERWRITE_VMAT = True 45 | 46 | # material types need to be lowercase because python is a bit case sensitive 47 | materialTypes = [ 48 | "vertexlitgeneric", 49 | "unlitgeneric", 50 | "lightmappedgeneric", 51 | "patch", 52 | "teeth", 53 | "eyes", 54 | "eyeball", 55 | #"modulate", 56 | "water", #TODO: integrate water/refract shaders into this script 57 | "refract", 58 | "worldvertextransition", 59 | #"lightmapped_4wayblend", 60 | "unlittwotexture", #TODO: make this system functional 61 | #"lightmappedreflective", 62 | #"cables" 63 | ] 64 | ignoreList = [ 65 | "vertexlitgeneric_hdr_dx9", 66 | "vertexlitgeneric_dx9", 67 | "vertexlitgeneric_dx8", 68 | "vertexlitgeneric_dx7", 69 | "lightmappedgeneric_hdr_dx9", 70 | "lightmappedgeneric_dx9", 71 | "lightmappedgeneric_dx8", 72 | "lightmappedgeneric_dx7", 73 | ] 74 | 75 | def text_parser(filepath, separator="="): 76 | return_dict = {} 77 | with open(filepath, "r") as f: 78 | for line in f: 79 | if not line.startswith("//"): 80 | line = line.replace('\t', '').replace('\n', '') 81 | line = line.split(separator) 82 | return_dict[line[0]] = line[1] 83 | return return_dict 84 | 85 | def parseVMTParameter(line, parameters): 86 | words = [] 87 | 88 | if line.startswith('\t') or line.startswith(' '): 89 | words = re.split(r'\s+', line, 2) 90 | else: 91 | words = re.split(r'\s+', line, 1) 92 | 93 | words = list(filter(len, words)) 94 | if len(words) < 2: 95 | return 96 | 97 | key = words[0].strip('"') 98 | 99 | if key.startswith('/'): 100 | return 101 | 102 | if not key.startswith('$'): 103 | if not key.startswith('include'): 104 | return 105 | 106 | val = words[1].strip('\n') 107 | 108 | # remove comments, HACK 109 | commentTuple = val.partition('//') 110 | 111 | if(val.strip('"' + "'") == ""): 112 | print("+ No value found, moving on") 113 | return 114 | 115 | if not commentTuple[0] in parameters: 116 | parameters[key] = commentTuple[0] 117 | 118 | def fixTexturePath(p, addonString = ""): 119 | retPath = p.strip().strip('"') 120 | retPath = retPath.replace('\\', '/') # Convert paths to use forward slashes. 121 | retPath = retPath.replace('.vtf', '') # remove any old extensions 122 | retPath = '"materials/' + retPath + addonString + TEXTURE_FILEEXT + '"' 123 | return retPath 124 | 125 | def fixVector(s): 126 | s = s.strip('"][}{ ') # some VMT vectors use {} 127 | parts = [str(float(i)) for i in s.split(' ')] 128 | extra = (' 0.0' * max(3 - s.count(' '), 0) ) 129 | return '"[' + ' '.join(parts) + extra + ']"' 130 | 131 | def extractAlphaTextures(localPath, invertColor): 132 | image_path = PATH_TO_CONTENT_ROOT + localPath 133 | mask_path = PATH_TO_CONTENT_ROOT + localPath[:-4] + MAP_SUBSTRING + TEXTURE_FILEEXT 134 | print("+ Attempting to extract alpha from " + image_path) 135 | 136 | if path.exists(image_path): 137 | # Open the image and convert it to RGBA, just in case it was indexed 138 | image = Image.open(image_path).convert('RGBA') 139 | 140 | # Extract just the alpha channel 141 | alpha = image.split()[-1] 142 | 143 | # Unfortunately the alpha channel is still treated as such and can't be dumped 144 | # as-is 145 | 146 | # Create a new image with an opaque black background 147 | bg = Image.new("RGBA", image.size, (0,0,0,255)) 148 | 149 | # Copy the alpha channel to the new image using itself as the mask 150 | bg.paste(alpha) 151 | 152 | if invertColor: 153 | r,g,b,a = bg.split() 154 | rgb_image = Image.merge('RGB', (r,g,b)) 155 | inverted_image = PIL.ImageOps.invert(rgb_image) 156 | 157 | r2,g2,b2 = inverted_image.split() 158 | 159 | final_transparent_image = Image.merge('RGB', (r2,g2,b2)) 160 | 161 | final_transparent_image.save(mask_path) 162 | final_transparent_image.close() 163 | else: 164 | bg.save(mask_path) 165 | bg.close() 166 | 167 | def flipNormalMap(localPath): 168 | image_path = PATH_TO_CONTENT_ROOT + localPath 169 | 170 | if path.exists(image_path): 171 | # Open the image and convert it to RGBA, just in case it was indexed 172 | image = Image.open(image_path).convert('RGBA') 173 | 174 | # Extract just the green channel 175 | r,g,b,a = image.split() 176 | 177 | g = PIL.ImageOps.invert(g) 178 | 179 | final_transparent_image = Image.merge('RGBA', (r,g,b,a)) 180 | 181 | final_transparent_image.save(image_path) 182 | 183 | #flipNormalMap("materials/models/player/demo/demoman_normal.tga") 184 | 185 | #extractAlphaTextures("materials/models/bots/boss_bot/carrier_body.tga") 186 | 187 | def getVmatParameter(key, val): 188 | key = key.strip('$').lower() 189 | 190 | # Dict for converting parameters 191 | convert = { 192 | #VMT paramter: VMAT parameter, value, additional lines to add. The last two variables take functions or strings, or None for using the old value. 193 | 'basetexture': ('TextureColor', fixTexturePath, None), 194 | 'basetexture2': ('TextureLayer1Color', fixTexturePath, None), 195 | 'bumpmap': ('TextureNormal', fixTexturePath, None), 196 | 'normalmap': ('TextureNormal', fixTexturePath, None), 197 | 'envmap': ('F_SPECULAR', '1', '\tF_SPECULAR_CUBE_MAP 1\n\tF_SPECULAR_CUBE_MAP_PROJECTION 1\n\tg_flCubeMapBlurAmount "1.000"\n\tg_flCubeMapScalar "1.000"\n'), #Assumes env_cubemap 198 | #'envmaptint': ('TextureReflectance', fixVector, None), 199 | 'envmapmask': ('TextureReflectance', fixTexturePath, None), 200 | 'color': ('g_vColorTint', None, None), #Assumes being used with basetexture 201 | 'selfillum': ('g_flSelfIllumScale', '"1.000"', None), 202 | 'selfillumtint': ('g_vSelfIllumTint', None, None), 203 | 'selfillummask': ('TextureSelfIllumMask', fixTexturePath, None), 204 | 'phongexponenttexture': ('TextureGlossiness', fixTexturePath, None), 205 | 'translucent': ('F_TRANSLUCENT', None, '\tg_flOpacityScale "1.000"\n'), 206 | 'additive': ('F_ADDITIVE_BLEND', None, None), 207 | 'nocull': ('F_RENDER_BACKFACES', None, None), 208 | 'decal':( 'F_OVERLAY', None, None), 209 | } 210 | 211 | if key in convert: 212 | outValue = val 213 | additionalLines = '' 214 | 215 | if isinstance(convert[key][1], str): 216 | outValue = convert[key][1] 217 | elif hasattr(convert[key][1], '__call__'): 218 | outValue = convert[key][1](val) 219 | 220 | if isinstance(convert[key][2], str): 221 | additionalLines = convert[key][2] 222 | elif hasattr(convert[key][2], '__call__'): 223 | additionalLines = convert[key][2](val) 224 | 225 | '''if isinstance(convert[key][3], bool): 226 | if(val.replace('"', '') == "0" or val.replace('"', '') == "false"): 227 | return '' 228 | elif hasattr(convert[key][3], '__call__'): 229 | print("Error: no bool at the end of dict!!") 230 | return '' 231 | ''' 232 | 233 | return '\t' + convert[key][0] + ' ' + outValue + '\n' + additionalLines 234 | 235 | return '' 236 | 237 | def parseDir(dirName): 238 | files = [] 239 | for root, dirs, fileNames in os.walk(dirName): 240 | for fileName in fileNames: 241 | if fileName.lower().endswith('.vmt'): 242 | files.append(os.path.join(root,fileName)) 243 | 244 | return files 245 | 246 | ### 247 | ### Main Execution 248 | ### 249 | 250 | globalVars = text_parser("global_vars.txt", " = ") 251 | PATH_TO_GAME_CONTENT_ROOT = globalVars["gameContentRoot"] 252 | PATH_TO_CONTENT_ROOT = PATH_TO_GAME_CONTENT_ROOT + sys.argv[1] + "/" 253 | print(PATH_TO_CONTENT_ROOT) 254 | 255 | if(PATH_TO_GAME_CONTENT_ROOT == ""): 256 | print("ERROR: Please open vmt_to_vmat in your favorite text editor, and modify PATH_TO_GAME_CONTENT_ROOT to lead to your games content files (i.e. ...\steamvr_environments\content\steamtours_addons\)") 257 | quit() 258 | 259 | print('Source 2 Material Conveter! By Rectus via Github.') 260 | print('Initially forked by Alpyne, this version by caseytube.') 261 | print('--------------------------------------------------------------------------------------------------------') 262 | 263 | # Verify file paths 264 | fileList = [] 265 | if(len(sys.argv) == 3): 266 | absFilePath = os.path.abspath(sys.argv[2]) 267 | if os.path.isdir(absFilePath): 268 | fileList.extend(parseDir(absFilePath)) 269 | elif(absFilePath.lower().endswith('.vmt')): 270 | fileList.append(absFilePath) 271 | else: 272 | print("ERROR: File path is invalid. required format: vmt_to_vmat.py modName C:\optional\path\to\root") 273 | quit() 274 | elif(len(sys.argv) == 2): 275 | absFilePath = os.path.abspath(PATH_TO_CONTENT_ROOT) 276 | print(PATH_TO_CONTENT_ROOT) 277 | if os.path.isdir(absFilePath): 278 | fileList.extend(parseDir(absFilePath)) 279 | elif(absFilePath.lower().endswith('.vmt')): 280 | fileList.append(absFilePath) 281 | else: 282 | print("ERROR: File path is invalid. required format: vmt_to_vmat.py modName C:\optional\path\to\root") 283 | quit() 284 | else: 285 | print("ERROR: CMD Arguments are invalid. Required format: vmt_to_vmat.py modName C:\optional\path\to\root") 286 | quit() 287 | 288 | # Main function, loop through every .vmt 289 | for fileName in fileList: 290 | print('--------------------------------------------------------------------------------------------------------') 291 | print('+ Loading File:\n' + fileName) 292 | vmtParameters = {} 293 | validMaterial = False 294 | validPatch = False 295 | skipNextLine = False 296 | 297 | matType = "" 298 | patchFile = "" 299 | basetexturePath = "" 300 | bumpmapPath = "" 301 | phong = False; #also counts for rimlight since they feed off each other 302 | baseMapAlphaPhongMask = False 303 | envMap = False 304 | baseAlphaEnvMapMask = False 305 | envMapMask = False 306 | normalMapAlphaEnvMapMask = False 307 | selfIllum = False 308 | translucent = False #also counts for alphatest 309 | alphatest = False 310 | 311 | wroteReflectanceRange = False 312 | 313 | with open(fileName, 'r') as vmtFile: 314 | for line in vmtFile.readlines(): 315 | if any(wd in line.lower() for wd in materialTypes): 316 | validMaterial = True 317 | matType = line.lower() 318 | 319 | if skipNextLine: 320 | if "]" in line or "}" in line: 321 | skipNextLine = False 322 | else: 323 | parseVMTParameter(line, vmtParameters) 324 | 325 | if any(wd in line.lower() for wd in ignoreList): 326 | skipNextLine = True 327 | 328 | if '"patch"' in matType.lower(): 329 | patchFile = vmtParameters["include"].replace('"', '').replace("'", ''); 330 | print("+ Patching materials details from: " + patchFile) 331 | with open(PATH_TO_CONTENT_ROOT + patchFile, 'r') as vmtFile: 332 | for line in vmtFile.readlines(): 333 | if any(wd in line.lower() for wd in materialTypes): 334 | validPatch = True 335 | parseVMTParameter(line, vmtParameters) 336 | 337 | if not validPatch: 338 | print("+ Patch file is not a valid material. Skipping!") 339 | continue 340 | 341 | if validMaterial: 342 | vmatFileName = fileName.replace('.vmt', '') + '.vmat' 343 | if os.path.exists(vmatFileName) and not OVERWRITE_VMAT: 344 | print('+ File already exists. Skipping!') 345 | continue 346 | 347 | print('+ Converting ' + os.path.basename(fileName)) 348 | with open(vmatFileName, 'w') as vmatFile: 349 | vmatFile.write('// Converted with vmt_to_vmat.py\n\n') 350 | vmatFile.write('Layer0\n{\n\tshader "' + SHADER + '.vfx"\n\n') 351 | for key, val in vmtParameters.items(): 352 | vmatFile.write(getVmatParameter(key, val)) 353 | if(key.lower() == "$phong" or key.lower() == "$rimlight"): 354 | if val.strip('"' + "'") != "0": 355 | phong = True 356 | elif(key.lower() == "$basemapalphaphongmask"): 357 | if val.strip('"' + "'") != "0": 358 | baseMapAlphaPhongMask = True 359 | elif(key.lower() == "$selfillum"): 360 | if val.strip('"' + "'") != "0": 361 | print("selfillum") 362 | selfIllum = True 363 | elif(key.lower() == "$translucent"): 364 | if val.strip('"' + "'") != "0": 365 | translucent = True 366 | elif(key.lower() == "$alphatest"): 367 | if val.strip('"' + "'") != "0": 368 | alphatest = True 369 | elif(key.lower() == "$basetexture"): 370 | basetexturePath = val.lower().strip().replace('.vtf', '') 371 | elif(key.lower() == "$bumpmap"): 372 | bumpmapPath = val.lower().strip().replace('.vtf', '') 373 | elif(key.lower() == "$envmap"): 374 | envMap = True 375 | elif(key.lower() == "$basealphaenvmapmask"): 376 | if val.strip('"' + "'") != "0": 377 | baseAlphaEnvMapMask = True 378 | elif(key.lower() == "$normalmapalphaenvmapmask"): 379 | if val.strip('"' + "'") != "0": 380 | normalMapAlphaEnvMapMask = True 381 | elif(key.lower() == "$envmapmask"): 382 | if val.strip('"' + "'") != "0": 383 | envMapMask = True 384 | 385 | #check if base texture is empty 386 | if "metal" in vmatFileName: 387 | vmatFile.write("\tg_flMetalness 1.000\n") 388 | 389 | if translucent: 390 | vmatFile.write('\tF_TRANSLUCENT 1\n\tTextureTranslucency ' + fixTexturePath(basetexturePath, MAP_SUBSTRING) + '\n') 391 | extractAlphaTextures("materials/" + basetexturePath.replace('"', '') + TEXTURE_FILEEXT, False) 392 | 393 | if alphatest: 394 | vmatFile.write('\tF_ALPHA_TEST 1\n\tTextureTranslucency ' + fixTexturePath(basetexturePath, MAP_SUBSTRING) + '\n') 395 | extractAlphaTextures("materials/" + basetexturePath.replace('"', '') + TEXTURE_FILEEXT, False) 396 | 397 | hasReflectance = False 398 | 399 | if phong: 400 | if not wroteReflectanceRange: 401 | vmatFile.write('\t' + globalVars["reflectanceRange"] + '\n') 402 | wroteReflectanceRange = True 403 | if baseMapAlphaPhongMask and basetexturePath != '': 404 | hasReflectance = True 405 | vmatFile.write('\tTextureReflectance ' + fixTexturePath(basetexturePath, MAP_SUBSTRING) + '\n') 406 | extractAlphaTextures("materials/" + basetexturePath.replace('"', '') + TEXTURE_FILEEXT, True) 407 | else: 408 | if(bumpmapPath == '') and not (baseAlphaEnvMapMask or normalMapAlphaEnvMapMask): 409 | vmatFile.write('\tTextureReflectance "[1.000000 1.000000 1.000000 0.000000]"\n') 410 | #extractAlphaTextures("materials/" + basetexturePath.replace('"', '') + TEXTURE_FILEEXT, True) 411 | else: 412 | hasReflectance = True 413 | vmatFile.write('\tTextureReflectance ' + fixTexturePath(bumpmapPath, MAP_SUBSTRING) + '\n') 414 | extractAlphaTextures("materials/" + bumpmapPath.replace('"', '') + TEXTURE_FILEEXT, True) 415 | if envMap: 416 | if not wroteReflectanceRange: 417 | vmatFile.write('\t' + globalVars["reflectanceRange"] + '\n') 418 | wroteReflectanceRange = True 419 | if baseAlphaEnvMapMask and not normalMapAlphaEnvMapMask and basetexturePath != '' and not hasReflectance: 420 | vmatFile.write('\tTextureReflectance ' + fixTexturePath(basetexturePath, MAP_SUBSTRING) + '\n') 421 | #Weird hack, apparently envmaps for LightmappedGeneric are flipped, whereas VertexLitGeneric ones aren't 422 | if "lightmappedgeneric" in matType: 423 | extractAlphaTextures("materials/" + basetexturePath.replace('"', '') + TEXTURE_FILEEXT, True) 424 | elif "vertexlitgeneric" in matType: 425 | extractAlphaTextures("materials/" + basetexturePath.replace('"', '') + TEXTURE_FILEEXT, True) 426 | if normalMapAlphaEnvMapMask and bumpmapPath != '' and not hasReflectance: 427 | vmatFile.write('\tTextureReflectance ' + fixTexturePath(bumpmapPath, MAP_SUBSTRING) + '\n') 428 | #Weird hack, apparently envmaps for LightmappedGeneric are flipped, whereas VertexLitGeneric ones aren't 429 | if "lightmappedgeneric" in matType: 430 | extractAlphaTextures("materials/" + bumpmapPath.replace('"', '') + TEXTURE_FILEEXT, True) 431 | elif "vertexlitgeneric" in matType: 432 | extractAlphaTextures("materials/" + bumpmapPath.replace('"', '') + TEXTURE_FILEEXT, True) 433 | 434 | if selfIllum: 435 | vmatFile.write('\tF_SELF_ILLUM 1\n\tTextureSelfIllumMask ' + fixTexturePath(basetexturePath, MAP_SUBSTRING) + '\n') 436 | extractAlphaTextures("materials/" + basetexturePath.replace('"', '') + TEXTURE_FILEEXT, False) 437 | 438 | vmatFile.write('}\n') 439 | 440 | bumpmapConvertedList = PATH_TO_CONTENT_ROOT + "convertedBumpmaps.txt" 441 | if not os.path.exists(bumpmapConvertedList): 442 | print('ERROR: Please create an empty text file named "convertedBumpmaps.txt" in the root of your mod files (i.e. content/steamtours_addons/hl2)') 443 | quit() 444 | 445 | # flip the green channels of any normal maps 446 | if(bumpmapPath != ""): 447 | print("Checking if normal file " + bumpmapPath + " has been converted") 448 | foundMaterial = False 449 | with open(bumpmapConvertedList, 'r+') as bumpList: #change the read type to write 450 | for line in bumpList.readlines(): 451 | if line.rstrip() == bumpmapPath.rstrip(): 452 | foundMaterial = True 453 | 454 | if not foundMaterial: 455 | flipNormalMap(fixTexturePath(bumpmapPath).strip("'" + '"')) 456 | print("flipped normal map of " + bumpmapPath) 457 | #append bumpmapPath to bumpmapCovertedList 458 | bumpList.write(bumpmapPath + "\n") 459 | bumpList.close() 460 | 461 | # TODO: reparse the vmt, see i.e. if alphatest, then TextureTranslucency "path/to/tex/name_alpha.tga", 462 | # basemap alpha can either be a transparency mask, selfillum mask, or specular mask 463 | # normalmap alpha can be a phong mask by default 464 | 465 | # if $translucent/$alphatest 466 | # TextureTranslucency "path/to/tex/basetexture_alpha.tga" 467 | # if $rimlight/$phong in vmt 468 | # if $basemapalphaphongmask in vmt 469 | #TextureRimMask/TextureSpecularMask "path/to/tex/basetexture_alpha.tga" 470 | # else 471 | #TextureRimMask/TextureSpecularMask "path/to/tex/bumpmap_alpha.tga" 472 | # if $selfillum in vmt 473 | # Add Mask 1 474 | # TextureSelfIllumMask "path/to/tex/basetexture_alpha.tga" 475 | 476 | # input("\nDone, press ENTER to continue...") 477 | -------------------------------------------------------------------------------- /utils/old_versions/vmt_to_vmat_dota.py: -------------------------------------------------------------------------------- 1 | # Converts Source 1 .vmt material files to simple Source 2 .vmat files. 2 | # 3 | # Copyright (c) 2016 Rectus 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | import sys 24 | import os 25 | import re 26 | from PIL import Image 27 | import PIL.ImageOps 28 | 29 | GAMEISTF2 = True; 30 | SHADER = 'hero' # What shader to use. 31 | TEXTURE_FILEEXT = '.tga' # File format of the textures. 32 | MAP_SUBSTRING = '_alpha' # substring added after an alpha map's name, but before the extension 33 | PATH_TO_CONTENT_ROOT = 'F:/Programs/Steam/steamapps/common/dota 2 beta/content/dota_addons/hl2/' # this leads to the root of the game folder, i.e. dota 2 beta/content/dota_addons/tf/, make sure to remember the final / 34 | 35 | def parseVMTParameter(line, parameters): 36 | words = [] 37 | 38 | if line.startswith('\t') or line.startswith(' '): 39 | words = re.split(r'\s+', line, 2) 40 | else: 41 | words = re.split(r'\s+', line, 1) 42 | 43 | words = list(filter(len, words)) 44 | if len(words) < 2: 45 | return 46 | 47 | key = words[0].strip('"') 48 | 49 | if key.startswith('/'): 50 | return 51 | 52 | if not key.startswith('$'): 53 | return 54 | 55 | val = words[1].strip('\n') 56 | 57 | # remove comments, HACK 58 | commentTuple = val.partition('//') 59 | 60 | if(val.strip('"' + "'") == ""): 61 | print("no value found, moving on") 62 | return 63 | 64 | parameters[key] = commentTuple[0] 65 | 66 | def fixTexturePath(path, addonString = ""): 67 | retPath = path.strip().strip('"') 68 | retPath = retPath.replace('\\', '/') # Convert paths to use forward slashes. 69 | retPath = retPath.replace('.vtf', '') # remove any old extensions 70 | retPath = '"materials/' + retPath + addonString + TEXTURE_FILEEXT + '"' 71 | return retPath 72 | 73 | def fixVector(s): 74 | s = s.strip('"[]{} ') # some VMT vectors use {} 75 | parts = [str(float(i)) for i in s.split(' ')] 76 | extra = (' 0.0' * max(3 - s.count(' '), 0) ) 77 | return '"[' + ' '.join(parts) + extra + ']"' 78 | 79 | def extractAlphaTextures(localPath): 80 | image_path = PATH_TO_CONTENT_ROOT + localPath 81 | mask_path = PATH_TO_CONTENT_ROOT + localPath[:-4] + "_alpha.tga" 82 | 83 | # Open the image and convert it to RGBA, just in case it was indexed 84 | image = Image.open(image_path).convert('RGBA') 85 | 86 | # Extract just the alpha channel 87 | alpha = image.split()[-1] 88 | 89 | # Unfortunately the alpha channel is still treated as such and can't be dumped 90 | # as-is 91 | 92 | # Create a new image with an opaque black background 93 | bg = Image.new("RGBA", image.size, (0,0,0,255)) 94 | 95 | # Copy the alpha channel to the new image using itself as the mask 96 | bg.paste(alpha, mask=alpha) 97 | 98 | # Since the bg image started as RGBA, we can save some space by converting it 99 | # to grayscale ('L') Optionally, we can convert the image to be indexed which 100 | # saves some more space ('P') In my experience, converting directly to 'P' 101 | # produces both the Gray channel and an Alpha channel when viewed in GIMP, 102 | # althogh the file sizes is about the same 103 | bg.convert('L').convert('P', palette=Image.ADAPTIVE, colors=8).save( 104 | mask_path, 105 | optimize=True) 106 | 107 | def flipNormalMap(localPath): 108 | image_path = PATH_TO_CONTENT_ROOT + localPath 109 | #mask_path = PATH_TO_CONTENT_ROOT + localPath[:-4] + "_alpha.tga" 110 | 111 | # Open the image and convert it to RGBA, just in case it was indexed 112 | image = Image.open(image_path).convert('RGBA') 113 | 114 | # Extract just the green channel 115 | r,g,b,a = image.split() 116 | 117 | g = PIL.ImageOps.invert(g) 118 | 119 | final_transparent_image = Image.merge('RGBA', (r,g,b,a)) 120 | 121 | final_transparent_image.save(image_path) 122 | 123 | #flipNormalMap("materials/models/player/demo/demoman_normal.tga") 124 | 125 | #extractAlphaTextures("materials/models/bots/boss_bot/carrier_body.tga") 126 | 127 | def getVmatParameter(key, val): 128 | key = key.strip('$').lower() 129 | 130 | # Dict for converting parameters 131 | convert = { 132 | #VMT paramter: VMAT parameter, value, additional lines to add. The last two variables take functions or strings, or None for using the old value. 133 | # fixTexturePath(val, "_test") if you want to add a value to the end of a path but before .tga 134 | 'basetexture': ('TextureColor', fixTexturePath, '\tg_flAmbientScale "0.000"\n', False), # use this for default variables too, like ambient scale 135 | 'bumpmap': ('TextureNormal', fixTexturePath, None, False), 136 | 'color': ('g_vColorTint', None, None, False), #Assumes being used with basetexture 137 | 'translucent': ('F_TRANSLUCENT', None, '\tg_flOpacityScale "1.000"\n', True), 138 | 'alphatest': ('F_ALPHA_TEST', None, '\tg_flAlphaTestReference "0.500"\n', True), 139 | 'additive': ('F_ADDITIVE_BLEND', None, None, True), 140 | 'nocull': ('F_RENDER_BACKFACES', None, None, True), 141 | 'decal':( 'F_OVERLAY', None, None, True), 142 | 143 | 'envmap': ('F_SPECULAR', '1', '\tF_SPECULAR_CUBE_MAP 1\n\tF_SPECULAR_CUBE_MAP_PROJECTION 1\n\tg_flCubeMapBlurAmount "1.000"\n\tg_flCubeMapScalar "1.000"\n', True), #Assumes env_cubemap 144 | 'envmaptint': ('TextureReflectance', fixVector, None, False), 145 | 'envmapmask': ('TextureReflectance', fixTexturePath, None, False), 146 | 147 | # we don't need detail mask or any of this stuff unless the user chooses to 148 | 'selfillum': ('g_flSelfIllumScale', '"1.000"', '\tF_MASKS_1 1\n\tTextureDetailMask "[0.000000 0.000000 0.000000 0.000000]"\n\tTextureDiffuseWarpMask "[0.000000 0.000000 0.000000 0.000000]"\n\tTextureMetalnessMask "[0.000000 0.000000 0.000000 0.000000]"\n', True), 149 | 'selfillumtint': ('g_vSelfIllumTint', None, None, False), 150 | 'selfillummask': ('TextureSelfIllumMask', fixTexturePath, None, False), 151 | 152 | 'phong': ('g_flSpecularScale', None, '\tF_MASKS_2 1\n', True), 153 | 'phongexponent': ('g_flSpecularExponent', None, None, False), 154 | 'phongfresnelranges': ('//fresnelcomment', None, None, False), 155 | #'phongexponenttexture': ('TextureSpecularMask', fixTexturePath, None), 156 | 157 | 'rimlight': ('g_flRimLightScale', None, '\tF_MASKS_2 1\n', False), 158 | 'rimexponent': ('g_flRimLightScale', None, None, False), 159 | #'rimmask': ('TextureRimMask', None, None, True), 160 | 161 | 'lightwarptexture': ('TextureFresnelWarpSpec', fixTexturePath, '\tTextureFresnelWarpColor ' + fixTexturePath(val) + '\n', False), 162 | 163 | # currently blanking this because TF2 uses detail for fire textures 164 | #'detail': ('TextureDetail', fixTexturePath, '\tF_DETAIL 1\n'), 165 | #'detailscale': ('g_vDetailTexCoordScale', '"[' + val + ' ' + val + ']"', None), 166 | #'detailblendfactor': ('g_flDetailBlendFactor', None, None), 167 | #note to self: create a function to fix values? 168 | } 169 | 170 | if key in convert: 171 | outValue = val 172 | additionalLines = '' 173 | 174 | if isinstance(convert[key][1], str): 175 | outValue = convert[key][1] 176 | elif hasattr(convert[key][1], '__call__'): 177 | outValue = convert[key][1](val) 178 | 179 | if isinstance(convert[key][2], str): 180 | additionalLines = convert[key][2] 181 | elif hasattr(convert[key][2], '__call__'): 182 | additionalLines = convert[key][2](val) 183 | 184 | '''if isinstance(convert[key][3], bool): 185 | if(val.replace('"', '') == "0" or val.replace('"', '') == "false"): 186 | return '' 187 | elif hasattr(convert[key][3], '__call__'): 188 | print("Error: no bool at the end of dict!!") 189 | return '' 190 | ''' 191 | 192 | return '\t' + convert[key][0] + ' ' + outValue + '\n' + additionalLines 193 | 194 | return '' 195 | 196 | def parseDir(dirName): 197 | files = [] 198 | for root, dirs, fileNames in os.walk(dirName): 199 | for fileName in fileNames: 200 | if fileName.lower().endswith('.vmt'): 201 | files.append(os.path.join(root,fileName)) 202 | 203 | return files 204 | 205 | print('\nSource 2 Material Conveter\n') 206 | 207 | fileList = [] 208 | 209 | for filePath in sys.argv: 210 | absFilePath = os.path.abspath(filePath) 211 | if os.path.isdir(absFilePath): 212 | fileList.extend(parseDir(absFilePath)) 213 | else: 214 | if absFilePath.lower().endswith('.vmt'): 215 | fileList.append(absFilePath) 216 | 217 | 218 | for fileName in fileList: 219 | 220 | vmtParameters = {} 221 | # material types need to be lowercase because python is a bit case sensitive 222 | materialTypes = [ 223 | "vertexlitgeneric", 224 | "unlitgeneric", 225 | "lightmappedgeneric", 226 | #"modulate", 227 | #"water", #TODO: integrate water/refract shaders into this script 228 | #"refract" 229 | #"lightmapped_4wayblend", 230 | #"lightmappedreflective", 231 | #"cables" 232 | ] 233 | validMaterial = False; 234 | 235 | basetexturePath = "" 236 | bumpmapPath = "" 237 | phong = False; #also counts for rimlight since they feed off each other 238 | baseMapAlphaPhongMask = False 239 | selfIllum = False 240 | translucent = False #also counts for alphatest 241 | 242 | with open(fileName, 'r') as vmtFile: 243 | for line in vmtFile.readlines(): 244 | if any(wd in line.lower() for wd in materialTypes): 245 | validMaterial = True 246 | parseVMTParameter(line, vmtParameters) 247 | 248 | if validMaterial: 249 | print('Converting ' + os.path.basename(fileName)) 250 | 251 | vmatFileName = fileName.replace('.vmt', '') + '.vmat' 252 | 253 | if os.path.exists(vmatFileName): continue 254 | 255 | with open(vmatFileName, 'w') as vmatFile: 256 | vmatFile.write('// Converted with vmt_to_vmat.py\n\n') 257 | vmatFile.write('Layer0\n{\n\tshader "' + SHADER + '.vfx"\n\n') 258 | for key, val in vmtParameters.items(): 259 | vmatFile.write(getVmatParameter(key, val)) 260 | if(key.lower() == "$phong" or key.lower() == "$rimlight"): 261 | if val.strip('"' + "'") != "0": 262 | phong = True 263 | elif(key.lower() == "$basemapalphaphongmask"): 264 | if val.strip('"' + "'") != "0": 265 | baseMapAlphaPhongMask = True 266 | elif(key.lower() == "$selfillum"): 267 | if val.strip('"' + "'") != "0": 268 | selfIllum = True 269 | elif(key.lower() == "$translucent" or key == "$alphatest"): 270 | if val.strip('"' + "'") != "0": 271 | translucent = True 272 | elif(key.lower() == "$basetexture"): 273 | basetexturePath = val.lower() 274 | elif(key.lower() == "$bumpmap"): 275 | bumpmapPath = val.lower() 276 | 277 | if "metal" in vmatFileName: 278 | vmatFile.write("\tg_flMetalness 1.000\n") 279 | 280 | if translucent: 281 | vmatFile.write('\tTextureTranslucency ' + fixTexturePath(basetexturePath, MAP_SUBSTRING) + '\n') 282 | extractAlphaTextures("materials/" + basetexturePath.replace('"', '') + TEXTURE_FILEEXT) 283 | 284 | if phong: 285 | if baseMapAlphaPhongMask: 286 | vmatFile.write('\tTextureRimMask ' + fixTexturePath(basetexturePath, MAP_SUBSTRING) + '\n\tTextureSpecularMask ' + fixTexturePath(basetexturePath, MAP_SUBSTRING) + '\n') 287 | extractAlphaTextures("materials/" + basetexturePath.replace('"', '') + TEXTURE_FILEEXT) 288 | else: 289 | if(bumpmapPath == ''): 290 | vmatFile.write('\tTextureSpecularMask "[1.000000 1.000000 1.000000 0.000000]"\n\tTextureRimMask "[1.000000 1.000000 1.000000 0.000000]"\n') 291 | extractAlphaTextures("materials/" + basetexturePath.replace('"', '') + TEXTURE_FILEEXT) 292 | else: 293 | vmatFile.write('\tTextureSpecularMask ' + fixTexturePath(bumpmapPath, MAP_SUBSTRING) + '\n\tTextureRimMask ' + fixTexturePath(bumpmapPath, MAP_SUBSTRING) + '\n') 294 | extractAlphaTextures("materials/" + bumpmapPath.replace('"', '') + TEXTURE_FILEEXT) 295 | 296 | if (selfIllum): 297 | vmatFile.write('\tTextureSelfIllumMask ' + fixTexturePath(basetexturePath, MAP_SUBSTRING) + '\n') 298 | extractAlphaTextures("materials/" + basetexturePath.replace('"', '') + TEXTURE_FILEEXT) 299 | 300 | vmatFile.write('}\n') 301 | 302 | bumpmapConvertedList = PATH_TO_CONTENT_ROOT + "convertedBumpmaps.txt" 303 | print(bumpmapConvertedList) 304 | #if os.path.exists(bumpmapConvertedList): continue 305 | 306 | # flip the green channels of any normal maps 307 | if(bumpmapPath != ""): 308 | foundMaterial = False 309 | with open(bumpmapConvertedList, 'r+') as bumpList: #change the read type to write 310 | for line in bumpList.readlines(): 311 | if line.rstrip() == bumpmapPath.rstrip(): 312 | foundMaterial = True 313 | 314 | if not foundMaterial: 315 | flipNormalMap(fixTexturePath(bumpmapPath).strip("'" + '"')) 316 | print("flipped normal map") 317 | #append bumpmapPath to bumpmapCovertedList 318 | bumpList.write(bumpmapPath + "\n") 319 | bumpList.close() 320 | 321 | # TODO: reparse the vmt, see i.e. if alphatest, then TextureTranslucency "path/to/tex/name_alpha.tga", 322 | # basemap alpha can either be a transparency mask, selfillum mask, or specular mask 323 | # normalmap alpha can be a phong mask by default 324 | 325 | # if $translucent/$alphatest 326 | # TextureTranslucency "path/to/tex/basetexture_alpha.tga" 327 | # if $rimlight/$phong in vmt 328 | # if $basemapalphaphongmask in vmt 329 | #TextureRimMask/TextureSpecularMask "path/to/tex/basetexture_alpha.tga" 330 | # else 331 | #TextureRimMask/TextureSpecularMask "path/to/tex/bumpmap_alpha.tga" 332 | # if $selfillum in vmt 333 | # Add Mask 1 334 | # TextureSelfIllumMask "path/to/tex/basetexture_alpha.tga" 335 | 336 | # input("\nDone, press ENTER to continue...") 337 | -------------------------------------------------------------------------------- /utils/qc_to_vmdl.py: -------------------------------------------------------------------------------- 1 | import re, sys, os 2 | 3 | INPUT_FILE_EXT = '.qc' 4 | OUTPUT_FILE_EXT = '.vmdl' 5 | 6 | MATERIALS_DIR_BASE = 'materials/' 7 | 8 | VMDL_BASE = ''' 9 | { 10 | m_meshList = 11 | { 12 | m_meshList = 13 | [ 14 | 15 | ] 16 | } 17 | 18 | } 19 | ''' 20 | VMDL_MESH = '''{{ 21 | m_meshName = "{mesh_name}" 22 | m_meshFile = "{mesh_file}" 23 | m_materialSearchPath = "{cdmaterials}" 24 | m_bSkinParentedObjects = true 25 | m_bLegacySkinParentedTransforms = false 26 | m_bExpensiveTangents = false 27 | m_bHighPrecisionTexCoords = false 28 | m_bPerVertexCurvature = false 29 | m_bBentNormals = false 30 | m_bIgnoreCloth = false 31 | m_bGetSkinningFromLod0 = false 32 | m_pMorphInfo = null 33 | }}, 34 | '''.replace('\n', '\n\t\t\t') 35 | 36 | def parse_qc(qc): 37 | # strip comments 38 | qc = re.sub(r'//.*', '', qc) 39 | tokens = re.split(r'\s+', qc) 40 | 41 | return tokens 42 | 43 | def walk_dir(dirname): 44 | files = [] 45 | 46 | for root, dirs, filenames in os.walk(dirname): 47 | for filename in filenames: 48 | if filename.lower().endswith(INPUT_FILE_EXT): 49 | files.append(os.path.join(root,filename)) 50 | 51 | return files 52 | 53 | abspath = '' 54 | files = [] 55 | 56 | # recursively search all dirs and files 57 | for path in sys.argv: 58 | abspath = os.path.abspath(path) 59 | if os.path.isdir(abspath): 60 | files.extend(walk_dir(abspath)) 61 | break 62 | #else: 63 | # if abspath.lower().endswith(INPUT_FILE_EXT): 64 | # files.append(abspath) 65 | 66 | def putl(f, line, indent = 0): 67 | f.write(('\t' * indent) + line + '\r\n') 68 | 69 | def strip_quotes(s): 70 | return s.strip('"').strip("'") 71 | 72 | def fix_path(s): 73 | return strip_quotes(s).replace('\\', '/').replace('//', '/').strip('/') 74 | 75 | def relative_path(s, base): 76 | base = base.replace(abspath, '') 77 | base = base.replace(os.path.basename(base), '') 78 | 79 | return fix_path(os.path.basename(abspath) + base + '/' + fix_path(s)) 80 | 81 | 82 | def get_mesh_name(file): 83 | return os.path.splitext(os.path.basename(fix_path(file)))[0] 84 | 85 | for filename in files: 86 | 87 | out_name = filename.replace(INPUT_FILE_EXT, OUTPUT_FILE_EXT) 88 | 89 | if os.path.exists(out_name): continue 90 | 91 | print('Converting', os.path.basename(filename)) 92 | 93 | qc_params = [] 94 | 95 | with open(filename, 'r') as qc_file: 96 | qc_params = parse_qc(qc_file.read()) 97 | 98 | cdmaterials = '' 99 | meshes = [] 100 | 101 | # naïve method of getting mesh list 102 | for i, p in enumerate(qc_params): 103 | if p in ['$model', '$body']: 104 | meshes.append((qc_params[i+1], qc_params[i+2])) 105 | elif p == '$bodygroup': 106 | meshes.append((qc_params[i+1], qc_params[i+4])) 107 | elif p == '$cdmaterials' and not cdmaterials: # just use first $cdmaterials 108 | cdmaterials = qc_params[i+1] 109 | 110 | meshes_str = '' 111 | for m in meshes: 112 | meshes_str += VMDL_MESH.format( 113 | mesh_name=get_mesh_name(m[1]), # ignore specified mesh name for now 114 | mesh_file=relative_path(m[1], filename), 115 | cdmaterials=MATERIALS_DIR_BASE + fix_path(cdmaterials) 116 | ) 117 | 118 | #out = sys.stdout 119 | 120 | with open(out_name, 'w') as out: 121 | putl(out, VMDL_BASE.replace('', meshes_str).replace((' ' * 4), '\t')) 122 | -------------------------------------------------------------------------------- /utils/vmt_to_vmat.py: -------------------------------------------------------------------------------- 1 | # Converts Source 1 .vmt material files to simple Source 2 .vmat files. 2 | # 3 | # Copyright (c) 2016 Rectus 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | # Usage Instructions: 24 | # Place all vmts and vtfs in their proper folder structure up till "materials" 25 | # (we'd recomend you just drop it in the content folder for ease of use) 26 | # Using VTFCmd or VTFEdit, use Tools->Convert Folder and convert your entire .vtf folder to .tgas 27 | # cmd: python vmt_to_vmat.py PATH 28 | # i.e.: python vmt_to_vmat.py "C:\Program Files (x86)\Steam\steamapps\common\Half-Life Alyx\content\hl2\materials\models\alyx" 29 | # OR 30 | # i.e.: python vmt_to_vmat.py "C:\Program Files (x86)\Steam\steamapps\common\Half-Life Alyx\content\hl2\materials\models\alyx\alyx_faceandhair.vmt" 31 | 32 | import sys 33 | import os 34 | import os.path 35 | from os import path 36 | import re 37 | from shutil import copyfile 38 | 39 | from PIL import Image 40 | import PIL.ImageOps 41 | 42 | #import numpy as np 43 | #from blend_modes import blending_functions 44 | 45 | #for the future 46 | #from VTFLibWrapper.VTFLibEnums import ImageFlag 47 | #from VTFLibWrapper import VTFLib 48 | #from VTFLibWrapper import VTFLibEnums 49 | #vtf_lib = VTFLib.VTFLib() 50 | 51 | # File format of the textures. 52 | TEXTURE_FILEEXT = '.tga' 53 | # Extension added to the end of the target folder for these new materials. Valve uses _imported, so we're using it here too 54 | TARGET_FOLDER_EXTENSION = "_imported" 55 | # For some reason, VR Complex doesn't have proper pbr support (as in no support for reflectivity maps or glossiness) 56 | # Because of this, we are just hacking so when compiling for VR_Complex, we set this to true 57 | PBR_HACK = False 58 | # A lot of looks for Source games seem to hinge on Reflectance Range being correct, so for now, we're making 59 | # a seperate variable for it to make it easier to modify on a per-game basis. HL2 seems to be 0.5 60 | reflRange = 0.5 61 | # If the user wishes, they can also generate .vmats for tools files (so debug, tools, dev, etc.) but usually this 62 | # causes compatibility issues since S2 already has it's own versions. 63 | skipDebugFiles = True 64 | 65 | debugPauseOnError = False # 66 | 67 | modPath = "" 68 | 69 | # material types need to be lowercase because python is a bit case sensitive 70 | # TODO: later, we will convert these to be in a dictionary with something like 71 | # "shaderName": ("HLATargetShader", "SVRHTargetShader", "DOTATargetShader"), 72 | # We will also have a flag for HLA, SVRH (steamtours), and DOTA) 73 | # For now though, just pretend everything uses TextureColor and fuck everything else 74 | vmtSupportedShaders = [ 75 | "vertexlitgeneric", # + Convert to VR Complex 76 | "unlitgeneric", # TODO: Convert to VR Complex, selfIllum with white mask 77 | "unlittwotexture", # TODO: Vr Simple 2layer Parallax? 78 | "patch", # TODO: ughhhhhhhhhhhhhhhhhhhhhhhhh 79 | "teeth", # + Convert to VR Complex 80 | "eyes", # + Convert to VR Complex 81 | "eyeball", # + Convert to VR Complex 82 | "eyerefract", # + Convert to VR Complex 83 | "modulate", # TODO: Refract/Glass Shader? 84 | "water", # TODO: Ditto 85 | "refract", # TODO: Ditto 86 | "worldvertextransition", # TODO: Vr Simple 2way Blend 87 | "lightmappedgeneric", # + Convert to VR Complex 88 | "lightmapped_4wayblend", # TODO: 4 Way Blend. No good shader for this, maybe just Vr Simple 2way Blend? 89 | "multiblend", # TODO: Ditto 90 | "hero", # TODO: DOTA Shader: to be worked on for compatibility 91 | "lightmappedtwotexture", # TODO: Vr Simple 2layer Parallax? 92 | "lightmappedreflective", # + Convert to VR Complex 93 | "decalmodulate", # TODO: See if this needs extra work 94 | "cables" # TODO: Find appropriate shader or maybe just vr_complex? 95 | ] 96 | ignoreList = [ 97 | "vertexlitgeneric_hdr_dx9", 98 | "vertexlitgeneric_dx9", 99 | "vertexlitgeneric_dx8", 100 | "vertexlitgeneric_dx7", 101 | "lightmappedgeneric_hdr_dx9", 102 | "lightmappedgeneric_dx9", 103 | "lightmappedgeneric_dx8", 104 | "lightmappedgeneric_dx7", 105 | ] 106 | 107 | ### 108 | ### Classes 109 | ### 110 | class RGBAImage: 111 | r = None 112 | g = None 113 | b = None 114 | a = None 115 | 116 | def __init__(self, size, col): 117 | self.r,self.g,self.b,self.a = Image.new("RGBA", size, col).split() 118 | 119 | def resizeAll(self, newSize): 120 | self.r = self.r.resize(newSize) 121 | self.g = self.g.resize(newSize) 122 | self.b = self.b.resize(newSize) 123 | self.a = self.a.resize(newSize) 124 | 125 | def setRG(self, image, flip = False): 126 | self.r.resize(image.size) 127 | self.g.resize(image.size) 128 | if (flip): 129 | self.r = PIL.ImageOps.invert(image) 130 | self.g = PIL.ImageOps.invert(image) 131 | else: 132 | self.r = image 133 | self.g = image 134 | 135 | def setRGB(self, image, flip = False): 136 | self.r.resize(image.size) 137 | self.g.resize(image.size) 138 | self.b.resize(image.size) 139 | 140 | if(flip): 141 | self.r = PIL.ImageOps.invert(image) 142 | self.g = PIL.ImageOps.invert(image) 143 | self.b = PIL.ImageOps.invert(image) 144 | else: 145 | self.r = image 146 | self.g = image 147 | self.b = image 148 | 149 | def setRGBA(self, image, flip = False): 150 | self.r.resize(image.size) 151 | self.g.resize(image.size) 152 | self.b.resize(image.size) 153 | self.a.resize(image.size) 154 | 155 | if(flip): 156 | self.r = PIL.ImageOps.invert(image) 157 | self.g = PIL.ImageOps.invert(image) 158 | self.b = PIL.ImageOps.invert(image) 159 | self.a = PIL.ImageOps.invert(image) 160 | else: 161 | self.r = image 162 | self.g = image 163 | self.b = image 164 | self.a = image 165 | 166 | def saveFile(self, filePath): 167 | imageOut = Image.merge('RGBA', (self.r, self.g, self.b, self.a)) 168 | imageOut.save(filePath) 169 | imageOut.close() 170 | 171 | ### 172 | ### Small Functions 173 | ### 174 | 175 | def parseDir(dirName): 176 | files = [] 177 | for root, dirs, fileNames in os.walk(dirName): 178 | if not os.path.exists(addFolderExtension(root)): 179 | print(" + Target Directory not found for folder " + os.path.basename(root) + ". Creating!") 180 | os.makedirs(addFolderExtension(root)) 181 | for fileName in fileNames: 182 | if fileName.lower().endswith('.vmt'): 183 | files.append(os.path.join(root,fileName)) 184 | return files 185 | 186 | def parseLine(inputString): 187 | #TODO: REGEX???? 188 | inputString = inputString.lower().replace('"', '').replace("'", "").replace("\n", "").replace("\t", "").replace("{", "").replace("}", "").replace(" ", "") 189 | return inputString 190 | 191 | def fixTexturePath(p, addonString = ""): 192 | retPath = p.strip().strip('"') 193 | retPath = retPath.replace('\\', '/') # Convert paths to use forward slashes. 194 | retPath = retPath.replace('.vtf', '') # remove any old extensions 195 | retPath = '"materials/' + retPath + addonString + TEXTURE_FILEEXT + '"' 196 | return retPath 197 | 198 | def fixVector(s, divVar = 1): 199 | s = s.strip('"][}{ ').strip("'").replace(" ", " ").replace(" ", " ") # some VMT vectors use {} 200 | parts = [str(float(i) / divVar) for i in s.split(' ')] 201 | extra = (' 0.0' * max(3 - s.count(' '), 0) ) 202 | return '"[' + ' '.join(parts) + extra + ']"' 203 | 204 | def vectorToArray(s, divVar = 1): 205 | s = s.strip('"][}{ ') # some VMT vectors use {} 206 | parts = [float(i) / divVar for i in s.split(' ')] 207 | #extra = (' 0.0' * max(3 - s.count(' '), 0) ) 208 | return parts 209 | 210 | def text_parser(filepath, separator="="): 211 | return_dict = {} 212 | with open(filepath, "r") as f: 213 | for line in f: 214 | if not line.startswith("//"): 215 | line = line.replace('\t', '').replace('\n', '') 216 | line = line.split(separator) 217 | return_dict[line[0]] = line[1] 218 | return return_dict 219 | 220 | def parseVMTPath(inputPath): 221 | inputPath = inputPath.lower().replace(".vtf", "") 222 | return inputPath 223 | 224 | def addFolderExtension(filePath): 225 | outPath = filePath.split('\\materials')[0] + TARGET_FOLDER_EXTENSION + "\\materials" + filePath.split('materials')[1] 226 | return outPath 227 | 228 | ### 229 | ### Big Functions 230 | ### 231 | def parseVMTParameter(line, parameters): 232 | words = [] 233 | 234 | if line.startswith('\t') or line.startswith(' '): 235 | words = re.split(r'\s+', line, 2) 236 | else: 237 | words = re.split(r'\s+', line, 1) 238 | 239 | words = list(filter(len, words)) 240 | if len(words) < 2: 241 | return 242 | 243 | key = words[0].strip('"').lower() # we process all values and keys as lowercase 244 | 245 | if key.startswith('/'): 246 | return 247 | 248 | if not key.startswith('$'): 249 | if not key.startswith('include'): 250 | return 251 | 252 | val = words[1].strip('\n').lower() # we process all values and keys as lowercase 253 | 254 | commentTuple = val.partition('//') 255 | 256 | if (val.strip('"' + "'") == ""): 257 | print("+ WARNING: No value found in parameter " + key + ", skipping!") 258 | return 259 | # So I chose this to be simple in code later, so I don't have to check if a value exists in vmtParameters AND check it's value 260 | # But, this should be fine cuz .vmt seems to treat any 0 values as the default parameter. If there's a value out there though 261 | # that relies on 0, we can add it here as an exception. 262 | if (val.strip('"' + "'") == "0"): 263 | print("+ WARNING: Value of " + key + " found to be 0, skipping!") 264 | return 265 | 266 | if not commentTuple[0] in parameters: 267 | #TODO: Tidy this up with regex 268 | parameters[key] = commentTuple[0].replace("'", "").replace('"', '').replace("\n", "").replace("\t", "") 269 | # reports back as dict with the format $basetexture models/alyx/alyx_faceandhair 270 | 271 | ### 272 | ### Main Execution 273 | ### 274 | 275 | print('--------------------------------------------------------------------------------------------------------\n' 276 | 'Source 2 Material Conveter! By Rectus via Github.\nInitially forked by Alpyne, this version by caseytube.\n' 277 | '--------------------------------------------------------------------------------------------------------\n') 278 | print(" + As a reminder, please extract all of your .vtfs to .tga using VTFEdit's 'Convert Folder' before running! + \n") 279 | # Start by asking some basic questions 280 | yes = {'yes','y', 'ye'} 281 | no = {'no','n', ''} 282 | validS2Shaders = {'vr_complex','vr_standard'} 283 | convertVTFs = False 284 | 285 | targetFolder = input("What folder would you like to convert? Valid Format: C:\\Steam\\steamapps\\Half-Life Alyx\\content\\tf\\materials: ").lower() 286 | if not os.path.exists(targetFolder): 287 | print("Please respond with a valid folder or file path! Quitting Process!") 288 | quit() 289 | 290 | overwriteInput = input("Would you like to overwrite any existing .vmat files? (y/n): ").lower() 291 | if overwriteInput in yes: 292 | OVERWRITE_VMAT = True 293 | elif overwriteInput in no: 294 | OVERWRITE_VMAT = False 295 | elif overwriteInput == "": # debug: casey's favorite default value 296 | OVERWRITE_VMAT = True 297 | else: 298 | print("Please respond with 'yes' or 'no.' Quitting process!") 299 | quit() 300 | 301 | overwriteTGAInput = input("Would you like to overwrite any existing .tga files? (y/n): ").lower() 302 | if overwriteTGAInput in yes: 303 | OVERWRITE_TGA = True 304 | elif overwriteTGAInput in no: 305 | OVERWRITE_TGA = False 306 | elif overwriteTGAInput == "": # debug: casey's favorite default value 307 | OVERWRITE_TGA = False 308 | else: 309 | print("Please respond with 'yes' or 'no.' Quitting process!") 310 | quit() 311 | 312 | '''convertInput = input("Would you like to convert .vtf files to .tga? (y/n): ").lower() 313 | if convertInput in yes: 314 | convertVTFs = True 315 | elif convertInput in no: 316 | convertVTFs = False 317 | elif convertInput == "": # debug: casey's favorite default value 318 | convertVTFs = False 319 | else: 320 | print("Please respond with 'yes' or 'no.' Quitting process!") 321 | quit()''' 322 | 323 | SHADER = input("What is your target shader? Valid Options: vr_complex (vr_standard support coming soon) - ").lower() 324 | if SHADER == "": 325 | SHADER = "vr_complex" 326 | elif SHADER not in validS2Shaders: 327 | print("Please respond with a valid shader! Quitting process!") 328 | quit() 329 | 330 | # Verify file paths 331 | fileList = [] 332 | vtfList = [] 333 | 334 | # HACK; See note under PBR_HACK 335 | if SHADER.lower() == "vr_complex": 336 | PBR_HACK = True 337 | 338 | # TODO: make this work so that when parsing directories, skip tools/debug stuff 339 | foldersToSkip = [ 340 | "materials\\tools", 341 | "materials\\debug" 342 | ] 343 | 344 | if(targetFolder): 345 | absFilePath = os.path.abspath(targetFolder) 346 | if os.path.isdir(absFilePath): 347 | fileList.extend(parseDir(absFilePath)) 348 | elif(absFilePath.lower().endswith('.vmt')): 349 | fileList.append(absFilePath) 350 | elif(absFilePath.lower().endswith('.vtf')): 351 | vtfList.append(absFilePath) 352 | else: 353 | print('ERROR: File path is invalid. required format: "vmt_to_vmat.py C:\\path\\to\\folder_or_vmt"') 354 | quit() 355 | else: 356 | print('ERROR: CMD Arguments are invalid. Required format: "vmt_to_vmat.py C:\\path\\to\\folder_or_vmt"') 357 | quit() 358 | 359 | for vmtFileName in fileList: 360 | print("+ Processing .vmt file: " + vmtFileName) 361 | baseFileName = os.path.basename(vmtFileName.replace('.vmt', '')) 362 | if modPath == "": 363 | modPath = vmtFileName.split('materials')[0] 364 | vmtParameters = {} 365 | vmtShader = "" 366 | 367 | vmatFileName = addFolderExtension(vmtFileName).replace('.vmt', '.vmat') 368 | if os.path.exists(vmatFileName) and not OVERWRITE_VMAT: 369 | print('+ WARNING: File already exists. Skipping!') 370 | continue 371 | 372 | basePath = 'materials' + vmtFileName.split('materials', 1)[1].replace('.vmt', '') 373 | 374 | with open(vmtFileName, 'r') as vmtFile: 375 | for line in vmtFile.readlines(): 376 | if parseLine(line) in vmtSupportedShaders: 377 | vmtShader = parseLine(line) 378 | else: 379 | parseVMTParameter(line, vmtParameters) 380 | 381 | if vmtShader == "": # vmt shader not supported 382 | print("- ERROR: Unsupported shader in " + baseFileName + ". Skipping!") 383 | if debugPauseOnError: 384 | input("Press the key to continue...") 385 | continue #skip! 386 | 387 | print('+ Parsing ' + os.path.basename(vmtFileName)) 388 | 389 | # default image, to later check if we actually found something 390 | nullImage = Image.new("RGB", (4, 4)) 391 | nullImage.info['is_null'] = True 392 | 393 | baseMap = nullImage 394 | bumpMap = nullImage 395 | phongMap = nullImage 396 | phongExpMap = nullImage 397 | envMap = nullImage 398 | illumMap = nullImage 399 | transMap = nullImage 400 | aoMap = nullImage 401 | maskMap = nullImage 402 | detailMap = nullImage 403 | 404 | # Prep TextureColor 405 | if "$basetexture" in vmtParameters: 406 | tgaPath = modPath + "materials\\" + parseVMTPath(vmtParameters["$basetexture"]) + ".tga" 407 | try: 408 | baseTexture = Image.open(tgaPath) 409 | baseMap = baseTexture 410 | 411 | if "$basemapalphaphongmask" in vmtParameters: 412 | phongMap = baseTexture.getchannel('A') 413 | if "$basemapalphaenvmapmask" in vmtParameters: 414 | envMap = baseTexture.getchannel('A') 415 | if "$selfillum" in vmtParameters and "$selfillummask" not in vmtParameters: 416 | illumMap = baseTexture.getchannel('A') 417 | if "$translucent" in vmtParameters or "$alphatest" in vmtParameters: 418 | transMap = baseTexture.getchannel('A') 419 | if "$basealphaenvmapmask" in vmtParameters: 420 | envMap = baseTexture.getchannel('A') 421 | if "$blendtintbybasealpha" in vmtParameters: 422 | maskMap = baseTexture.getchannel('A') 423 | except: 424 | print("- ERROR: $basetexture file " + parseVMTPath(vmtParameters["$basetexture"]) + " in TGA does not exist. Skipping!") 425 | 426 | # Prep TextureNormal for normal/bump maps 427 | if "$bumpmap" in vmtParameters or "$normalmap" in vmtParameters: 428 | if "$bumpmap" in vmtParameters: 429 | tgaPath = modPath + "materials\\" + parseVMTPath(vmtParameters["$bumpmap"]) + ".tga" 430 | elif "$normalmap" in vmtParameters: 431 | tgaPath = modPath + "materials\\" + parseVMTPath(vmtParameters["$normalmap"]) + ".tga" 432 | 433 | try: 434 | bumpTexture = Image.open(tgaPath) 435 | bumpMap = bumpTexture 436 | if "$basemapalphaphongmask" in vmtParameters: 437 | phongMap = bumpTexture.getchannel("A") 438 | 439 | if "$normalmapalphaenvmapmask" in vmtParameters: 440 | envMap = bumpTexture.getchannel("A") 441 | except: 442 | print("- ERROR: $bumpmap/$normalmap file " + tgaPath.split(modPath + "materials\\")[1] + " in TGA does not exist. Skipping!") 443 | 444 | if "$envmap" in vmtParameters and "$envmapmask" in vmtParameters: 445 | tgaPath = modPath + "materials\\" + parseVMTPath(vmtParameters["$envmapmask"]) + ".tga" 446 | try: 447 | envTexture = Image.open(tgaPath) 448 | envMap = envTexture.convert("RGB") 449 | except: 450 | print("- ERROR: $envmapmask file " + parseVMTPath(vmtParameters["$envmapmask"]) + " in TGA does not exist. Skipping!") 451 | 452 | # Prep Glossiness Map using Phong Exponent 453 | if "$phongexponenttexture" in vmtParameters: 454 | tgaPath = modPath + "materials\\" + parseVMTPath(vmtParameters["$phongexponenttexture"]) + ".tga" 455 | try: 456 | phongTexture = Image.open(tgaPath) 457 | phongExpMap = phongTexture 458 | except: 459 | print("- ERROR: $phongexponenttexture file " + parseVMTPath(vmtParameters["$phongexponenttexture"]) + " in TGA does not exist. Skipping!") 460 | 461 | # Prep TextureSelfIllum using selfillum stuff 462 | if "$selfillum" in vmtParameters and "$selfillummask" in vmtParameters: 463 | tgaPath = modPath + "materials\\" + parseVMTPath(vmtParameters["$selfillummask"]) + ".tga" 464 | try: 465 | illumTexture = Image.open(tgaPath) 466 | illumMap = illumTexture 467 | except: 468 | print("- ERROR: $selfillummask file " + parseVMTPath(vmtParameters["$selfillummask"]) + " in TGA does not exist. Skipping!") 469 | 470 | # Rarely used, but ambient occlusion maps are sometimes available 471 | if "$ambientoccltexture" in vmtParameters or "$ambientocclusiontexture" in vmtParameters: 472 | if "$ambientoccltexture" in vmtParameters: 473 | tgaPath = modPath + "materials\\" + vmtParameters["$ambientoccltexture"] + ".tga" 474 | elif "$ambientocclusiontexture" in vmtParameters: 475 | tgaPath = modPath + "materials\\" + vmtParameters["$ambientocclusiontexture"] + ".tga" 476 | try: 477 | aoTexture = Image.open(tgaPath) 478 | aoMap = aoTexture 479 | except: 480 | print("- ERROR: $ambientoccltexture/$ambientocclusiontexture file " + tgaPath.split(modPath + "materials\\")[1] + " in TGA does not exist. Skipping!") 481 | 482 | if SHADER == "vr_complex": 483 | print('+ Creating ' + os.path.basename(vmatFileName)) 484 | with open(vmatFileName, 'w') as vmatFile: 485 | # VMT Maps are now parsed. Moving onto creating the vmat! 486 | vmatFile.write('// Converted with vmt_to_vmat.py\n\n') 487 | vmatFile.write('Layer0\n{\n\tshader "' + SHADER + '.vfx"\n\n') 488 | 489 | # move onto writing materials if they exist 490 | # Prep TextureColor 491 | if 'is_null' not in baseMap.info: 492 | if not OVERWRITE_TGA or OVERWRITE_TGA and not os.path.exists(vmatFileName.replace('.vmat', '_color.tga')): 493 | #baseMap.save(vmatFileName.replace('.vmat', '_color.tga')) 494 | baseMap.save(vmatFileName.replace('.vmat', '_color.tga')) 495 | print(os.path.basename(vmatFileName.replace('.vmat', '_color.tga')) + " saved!") 496 | vmatFile.write('\tTextureColor "' + basePath + '_color.tga' + '"\n') 497 | 498 | # Prep TextureNormal for normal/bump maps 499 | if 'is_null' not in bumpMap.info: 500 | if "$ssbump" in vmtParameters and "1" in vmtParameters["$ssbump"]: 501 | print("- WARNING: " + os.path.basename(vmtFileName) + " uses $ssbump, which is not supported in Source 2. Skipping normal maps.") 502 | vmatFile.write('\t// $ssbump in original .vmt used, which are unsupported in Source 2. Normal maps skipped to retain visual quality.\n') 503 | else: 504 | if not OVERWRITE_TGA or OVERWRITE_TGA and not os.path.exists(vmatFileName.replace('.vmat', '_normal.tga')): 505 | bumpMap.save(vmatFileName.replace('.vmat', '_normal.tga')) 506 | print(os.path.basename(vmatFileName.replace('.vmat', '_normal.tga')) + " saved!") 507 | # For normal maps, we produce a file called fileName.txt that tells Source 2 to flip the green channel 508 | bumpSettingsFileName = vmatFileName.replace(".vmat", "_normal.txt") 509 | with open(bumpSettingsFileName, 'w') as bumpSettings: 510 | bumpSettings.write('"settings"\n' 511 | '{\n' 512 | '\t"legacy_source1_inverted_normal"\t"1"\n' 513 | '}') 514 | vmatFile.write('\tTextureNormal "' + basePath + '_normal.tga' + '"\n') 515 | 516 | # Rarely used, but ambient occlusion maps are sometimes available 517 | # However, since we use a hack in vr_complex for phong masks, we prioritize that over custom AO textures 518 | if 'is_null' not in phongMap.info: 519 | if not OVERWRITE_TGA or OVERWRITE_TGA and not os.path.exists(vmatFileName.replace('.vmat', '_ao.tga')): 520 | phongMap.save(vmatFileName.replace('.vmat', '_ao.tga')) 521 | print(os.path.basename(vmatFileName.replace('.vmat', '_ao.tga')) + " saved!") 522 | if "$phongboost" in vmtParameters: 523 | # For phong boost, we scale brightness of the roughness/phong map which seems to be a 1:1 ratio 524 | aoSettingsFileName = vmatFileName.replace(".vmat", "_ao.txt") 525 | with open(aoSettingsFileName, 'w') as aoSettings: 526 | aoSettings.write('"settings"\n' 527 | '{\n' 528 | '\t"brightness"\t"' + vmtParameters["$phongboost"] + '"\n' 529 | '}') 530 | vmatFile.write('\tg_vReflectanceRange "[0.000 ' + str(reflRange) + ']"\n') 531 | vmatFile.write('\tTextureAmbientOcclusion "' + basePath + '_ao.tga' + '"\n') 532 | vmatFile.write('\tg_flAmbientOcclusionDirectSpecular "1.000"\n') 533 | elif 'is_null' not in envMap.info: # unsure if this is the correct way to handle this, will have to see how HLA truly handles cubemaps in materials 534 | if not OVERWRITE_TGA or OVERWRITE_TGA and not os.path.exists(vmatFileName.replace('.vmat', '_ao.tga')): 535 | envMap.save(vmatFileName.replace('.vmat', '_ao.tga')) 536 | print(os.path.basename(vmatFileName.replace('.vmat', '_ao.tga')) + " saved!") 537 | vmatFile.write('\tTextureAmbientOcclusion "' + basePath + '_ao.tga' + '"\n') 538 | vmatFile.write('\tg_flAmbientOcclusionDirectSpecular "0.000"\n') 539 | elif "$ambientoccltexture" in vmtParameters or "$ambientocclusiontexture" in vmtParameters: 540 | if not OVERWRITE_TGA or OVERWRITE_TGA and not os.path.exists(vmatFileName.replace('.vmat', '_ao.tga')): 541 | aoMapConvert = aoMap.convert("L") 542 | aoMapConvert.save(vmatFileName.replace('.vmat', '_ao.tga')) 543 | print(os.path.basename(vmatFileName.replace('.vmat', '_ao.tga')) + " saved!") 544 | vmatFile.write('\tTextureAmbientOcclusion "' + basePath + '_ao.tga' + '"\n') 545 | 546 | # This value is a guess on comparing strengths of Phong exponent. 547 | # https://developer.valvesoftware.com/wiki/Phong_materials 548 | # My current theory is that $phongexponent's equvalent range (of usable numbers is 5-150 in Source 1) 549 | # is an exponential value (duh) which lands between 60 and 255. I've produced a really quick and dirty 550 | # expression with some online tools to help decide this, but we should probably find a cleaner solution. 551 | # y = -10642.28 + (254.2042 - -10642.28)/(1 + (x/2402433000000)^0.1705696) 552 | phongExpDivider = 150 553 | if "$phong" in vmtParameters: 554 | vmatFile.write('\tF_SPECULAR 1\n') 555 | if 'is_null' not in phongExpMap.info: 556 | if not OVERWRITE_TGA or OVERWRITE_TGA and not os.path.exists(vmatFileName.replace('.vmat', '_rough.tga')): 557 | phongExpMapFlip = phongExpMap.convert('RGB') 558 | phongExpMapFlip = PIL.ImageOps.invert(phongExpMapFlip) 559 | phongExpMapFlip.save(vmatFileName.replace('.vmat', '_rough.tga')) 560 | print(os.path.basename(vmatFileName.replace('.vmat', '_rough.tga')) + " saved!") 561 | vmatFile.write('\tTextureRoughness "' + basePath + '_rough.tga' + '"\n') 562 | elif "$phongexponent" in vmtParameters: 563 | specValue = vmtParameters["$phongexponent"] 564 | finalSpec = (-10642.28 + (254.2042 - -10642.28)/(1 + (float(specValue)/2402433000000)**0.1705696))/255 565 | vmatFile.write('\tTextureRoughness "[' + str(finalSpec) + ' ' + str(finalSpec) + ' ' + str(finalSpec) + ' 0.000]"\n') 566 | else: # VDC says the default value for $phongexponent is 5.0, but in my testing, I think it's actually 150, at least in SFM. TODO: Check this 567 | finalSpec = 60 568 | vmatFile.write('\tTextureRoughness "[' + str(finalSpec) + ' ' + str(finalSpec) + ' ' + str(finalSpec) + ' 0.000]"\n') 569 | 570 | # Prep TextureSelfIllum using selfillum stuff 571 | if "$selfillum" in vmtParameters: 572 | vmatFile.write('\tF_SELF_ILLUM 1\n') 573 | vmatFile.write('\tTextureSelfIllumMask "' + basePath + '_selfillum.tga' + '"\n') 574 | if "$selfillumtint" in vmtParameters: 575 | vmatFile.write('\tg_vSelfIllumTint ' + fixVector(vmtParameters["$selfillumtint"]) + '\n') 576 | if "$selfillummaskscale" in vmtParameters: 577 | vmatFile.write('\tg_flSelfIllumScale "' + vmtParameters['$selfillummaskscale'] + '"\n') 578 | if not OVERWRITE_TGA or OVERWRITE_TGA and not os.path.exists(vmatFileName.replace('.vmat', '_selfillum.tga')): 579 | illumMapConvert = illumMap.convert("L") 580 | illumMapConvert.save(vmatFileName.replace('.vmat', '_selfillum.tga')) 581 | print(os.path.basename(vmatFileName.replace('.vmat', '_selfillum.tga')) + " saved!") 582 | 583 | # Prep TextureTransparancy using either alphatest or translucent 584 | if "$translucent" in vmtParameters or "$alphatest" in vmtParameters: 585 | if "$translucent" in vmtParameters: 586 | vmatFile.write('\tF_TRANSLUCENT 1\n') 587 | elif "$alphatest" in vmtParameters: 588 | vmatFile.write('\tF_ALPHA_TEST 1\n') 589 | if "$additive" in vmtParameters: 590 | vmatFile.write('\tF_ADDITIVE_BLEND 1\n') 591 | if not OVERWRITE_TGA or OVERWRITE_TGA and not os.path.exists(vmatFileName.replace('.vmat', '_trans.tga')): 592 | transMapConvert = transMap.convert("L") 593 | transMapConvert.save(vmatFileName.replace('.vmat', '_trans.tga')) 594 | print(os.path.basename(vmatFileName.replace('.vmat', '_trans.tga')) + " saved!") 595 | vmatFile.write('\tTextureTranslucency "' + basePath + '_trans.tga' + '"\n') 596 | 597 | # Setting up Color Tint 598 | if "$color" in vmtParameters: 599 | if "{" in vmtParameters["$color"]: 600 | vmatFile.write('\tg_vColorTint ' + fixVector(vmtParameters["$color"], 255) + '\n') # process as int 601 | elif "[" in vmtParameters["$color"]: 602 | vmatFile.write('\tg_vColorTint ' + fixVector(vmtParameters["$color"]) + '\n') # process as float 603 | elif "$color2" in vmtParameters: 604 | # $blendtintbybasealpha does what it says on the tin, and is used by TF to tint items for team colors 605 | if "$blendtintbybasealpha" in vmtParameters: 606 | if not OVERWRITE_TGA or OVERWRITE_TGA and not os.path.exists(vmatFileName.replace('.vmat', '_colormask.tga')): 607 | maskMapConvert = maskMap.convert("L") 608 | maskMapConvert.save(vmatFileName.replace('.vmat', '_colormask.tga')) 609 | print(os.path.basename(vmatFileName.replace('.vmat', '_colormask.tga')) + " saved!") 610 | vmatFile.write('\tF_TINT_MASK 1\n\tTextureTintMask "' + basePath + '_colormask.tga' + '"\n') 611 | if "{" in vmtParameters["$color2"]: 612 | vmatFile.write('\tg_vColorTint ' + fixVector(vmtParameters["$color2"], 255) + '\n') # process as int 613 | elif "[" in vmtParameters["$color2"]: 614 | vmatFile.write('\tg_vColorTint ' + fixVector(vmtParameters["$color2"]) + '\n') # process as float 615 | 616 | # Setting up Detail Parameters 617 | if "$detail" in vmtParameters: 618 | # Detail textures are unique since they're almost always shared with other materials, 619 | # So in this case we just copy it once and then continue to process like normal 620 | tgaPath = modPath + "materials\\" + parseVMTPath(vmtParameters["$detail"]) + ".tga" 621 | if not os.path.exists(addFolderExtension(tgaPath)): 622 | try: 623 | copyfile(tgaPath, addFolderExtension(tgaPath)) 624 | print("+ " + addFolderExtension(tgaPath) + " copied to target directory!") 625 | except: 626 | print("- ERROR: $detail file " + parseVMTPath(vmtParameters["$detail"]) + " in TGA does not exist. Skipping!") 627 | 628 | vmatFile.write('\tTextureDetail "' + 'materials/' + parseVMTPath(vmtParameters["$detail"]) + '.tga"\n') 629 | if "$detailblendmode" in vmtParameters: 630 | vmatFile.write('\tF_DETAIL_TEXTURE 2\n') # Overlay 631 | else: 632 | vmatFile.write('\tF_DETAIL_TEXTURE 1\n') # Mod2X 633 | if "$detailscale" in vmtParameters: 634 | vmatFile.write('\tg_vDetailTexCoordScale "[' + vmtParameters["$detailscale"] + ' ' + vmtParameters["$detailscale"] + ']"\n') 635 | if "$detailblendfactor" in vmtParameters: 636 | vmatFile.write('\tg_flDetailBlendFactor "' + vmtParameters["$detailblendfactor"] + '"\n') 637 | 638 | vmatFile.write('}\n') 639 | 640 | elif SHADER == "vr_standard": 641 | # die 642 | print("die in real") 643 | elif SHADER == "globallitsimple": 644 | # die 2.0 645 | print("die in real for real") 646 | elif SHADER == "customhero": 647 | # die 3: the finale 648 | print("death death death death death") 649 | 650 | print('+ Finished Writing ' + vmatFileName) 651 | 652 | input("Press the key to close...") -------------------------------------------------------------------------------- /utils/working files/vmf_convert.py: -------------------------------------------------------------------------------- 1 | # cmd command: python vmf_convert.py "C:\path\to\vmf\file.vmf" 2 | 3 | import re, sys, os 4 | 5 | INPUT_FILE_EXT = '.vmf' 6 | # this leads to the root of the game folder, i.e. dota 2 beta/content/dota_addons/, make sure to remember the final slash!! 7 | PATH_TO_GAME_CONTENT_ROOT = "" 8 | PATH_TO_CONTENT_ROOT = "" 9 | 10 | print('Source 2 .vmf Prepper! EXPERIMENTAL!! By caseytube via Github') 11 | print('Converts .vmf files to be ready for Source 2 by fixing materials') 12 | print('--------------------------------------------------------------------------------------------------------') 13 | 14 | filename = sys.argv[1] 15 | convertedFilename = filename.replace('.vmf', '') + 'Converted.vmf' 16 | if not os.path.exists(filename): 17 | print("input file doesn't exist") 18 | quit() 19 | 20 | print('Importing', os.path.basename(filename)) 21 | 22 | with open(convertedFilename, 'w') as convFile: 23 | with open(filename, 'r') as vmfFile: 24 | for line in vmfFile.readlines(): 25 | splitLine = line.replace('"', '').replace("'", "").split() 26 | last = len(splitLine) - 1 27 | 28 | if "uaxis" in line: 29 | oldVar = splitLine[last] 30 | print(oldVar) 31 | newVar = float(oldVar) * 32 32 | print(newVar) 33 | newLine = line.replace(str(oldVar), str(newVar)) 34 | convFile.write(newLine) 35 | elif "vaxis" in line: 36 | oldVar = splitLine[last] 37 | print(oldVar) 38 | newVar = float(oldVar) * 32 39 | print(newVar) 40 | newLine = line.replace(str(oldVar), str(newVar)) 41 | convFile.write(newLine) 42 | else: 43 | convFile.write(line) --------------------------------------------------------------------------------