Last active
April 29, 2025 02:32
-
-
Save rickomax/3a8d7b9f46e951422e33371832b8a205 to your computer and use it in GitHub Desktop.
This Python script converts FBX textures/materials to the s&box game format. Converting materials to VMAT, and extracting textures to the selected folder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#This Python script converts FBX textures/materials to the s&box game format. Converting materials to VMAT, and extracting textures to the selected folder | |
#This script expects all the textures to be located at the same folder as the FBX file | |
import fbx | |
import os | |
import shutil | |
import tkinter as tk | |
from tkinter import filedialog | |
# Initialize tkinter | |
root = tk.Tk() | |
root.withdraw() # Hide the main window | |
# Select FBX file | |
fbx_file = filedialog.askopenfilename(title="Select FBX File", filetypes=[("FBX files", "*.fbx")]) | |
if not fbx_file: | |
print("No FBX file selected.") | |
exit(1) | |
# Select output directory for VMAT files | |
output_dir = filedialog.askdirectory(title="Select Output Directory for VMAT Files") | |
if not output_dir: | |
print("No output directory selected.") | |
exit(1) | |
# Initialize FBX SDK | |
manager = fbx.FbxManager.Create() | |
importer = fbx.FbxImporter.Create(manager, "") | |
if not importer.Initialize(fbx_file): | |
print("Failed to initialize FBX importer.") | |
exit(1) | |
scene = fbx.FbxScene.Create(manager, "") | |
if not importer.Import(scene): | |
print("Failed to import FBX scene.") | |
exit(1) | |
# Get the directory of the FBX file | |
fbx_dir = os.path.dirname(fbx_file) | |
# Function to copy texture to output folder with relative path | |
def copy_texture(texture_path, output_dir, fbx_dir): | |
if not texture_path: | |
return None | |
rel_texture_path = os.path.basename(texture_path) | |
abs_texture_path = os.path.join(fbx_dir, rel_texture_path) | |
output_texture_path = os.path.join(output_dir, "textures", rel_texture_path) | |
output_texture_dir = os.path.dirname(output_texture_path) | |
os.makedirs(output_texture_dir, exist_ok=True) | |
shutil.copy2(abs_texture_path, output_texture_path) | |
return rel_texture_path.replace("\\", "/") | |
# Process each material in the scene | |
material_count = scene.GetMaterialCount() | |
for i in range(material_count): | |
material = scene.GetMaterial(i) | |
material_name = material.GetName() | |
# Create VMAT file path | |
vmat_file_path = os.path.join(output_dir, f"materials/{material_name}.vmat") | |
output_material_dir = os.path.dirname(vmat_file_path) | |
os.makedirs(output_material_dir, exist_ok=True) | |
# Create and write to the .vmat file | |
with open(vmat_file_path, "w") as vmat_file: | |
vmat_file.write("// THIS FILE IS AUTO-GENERATED\n\n") | |
vmat_file.write("Layer0\n{\n") | |
vmat_file.write('\tshader "shaders/complex.shader"\n\n') | |
# Ambient Occlusion - using defaults | |
vmat_file.write('\t//---- Ambient Occlusion ----\n') | |
vmat_file.write('\tg_flAmbientOcclusionDirectDiffuse "0.000"\n') | |
vmat_file.write('\tg_flAmbientOcclusionDirectSpecular "0.000"\n') | |
vmat_file.write('\tTextureAmbientOcclusion "materials/default/default_ao.tga"\n') | |
# Color - map diffuse color and texture | |
diffuse_color = material.Diffuse.Get() | |
color_str = "[%.6f %.6f %.6f 1.000000]" % (diffuse_color[0], diffuse_color[1], diffuse_color[2]) | |
vmat_file.write('\n\t//---- Color ----\n') | |
vmat_file.write('\tg_flModelTintAmount "1.000"\n') | |
vmat_file.write(f'\tg_vColorTint "{color_str}"\n') | |
diffuse_texture = material.Diffuse.GetSrcObject(0) | |
if diffuse_texture: | |
texture_path = diffuse_texture.GetRelativeFileName() | |
rel_texture_path = copy_texture(texture_path, output_dir, fbx_dir) | |
if rel_texture_path: | |
vmat_file.write(f'\tTextureColor "textures/{rel_texture_path}"\n') | |
else: | |
vmat_file.write('\tTextureColor "materials/default/default_color.tga"\n') | |
else: | |
vmat_file.write('\tTextureColor "materials/default/default_color.tga"\n') | |
# Fade - using default | |
vmat_file.write('\n\t//---- Fade ----\n') | |
vmat_file.write('\tg_flFadeExponent "1.000"\n') | |
# Fog - using default | |
vmat_file.write('\n\t//---- Fog ----\n') | |
vmat_file.write('\tg_bFogEnabled "1"\n') | |
# Metalness - using default | |
vmat_file.write('\n\t//---- Metalness ----\n') | |
vmat_file.write('\tg_flMetalness "0.000"\n') | |
# Normal - map bump/normal map texture | |
bump_prop = material.FindProperty("Bump") | |
if bump_prop.IsValid(): | |
normal_texture = bump_prop.GetSrcObject(0) | |
if normal_texture: | |
texture_path = normal_texture.GetRelativeFileName() | |
rel_texture_path = copy_texture(texture_path, output_dir, fbx_dir) | |
if rel_texture_path: | |
vmat_file.write('\n\t//---- Normal ----\n') | |
vmat_file.write(f'\tTextureNormal "textures/{rel_texture_path}"\n') | |
else: | |
vmat_file.write('\n\t//---- Normal ----\n') | |
vmat_file.write('\tTextureNormal "materials/default/default_normal.tga"\n') | |
else: | |
vmat_file.write('\n\t//---- Normal ----\n') | |
vmat_file.write('\tTextureNormal "materials/default/default_normal.tga"\n') | |
else: | |
vmat_file.write('\n\t//---- Normal ----\n') | |
vmat_file.write('\tTextureNormal "materials/default/default_normal.tga"\n') | |
# Roughness - compute from shininess, use default texture | |
shininess_prop = material.FindProperty("Shininess") | |
if shininess_prop.IsValid(): | |
shininess_prop = fbx.FbxPropertyDouble1( shininess_prop ) | |
shininess = shininess_prop.Get() | |
roughness = 1.0 - min(shininess / 100.0, 1.0) | |
else: | |
roughness = 1.0 # Default to maximum roughness | |
vmat_file.write('\n\t//---- Roughness ----\n') | |
vmat_file.write(f'\tg_flRoughnessScaleFactor "{roughness:.3f}"\n') | |
vmat_file.write('\tTextureRoughness "materials/default/default_rough.tga"\n') | |
# Texture Coordinates - using defaults | |
vmat_file.write('\n\t//---- Texture Coordinates ----\n') | |
vmat_file.write('\tg_nScaleTexCoordUByModelScaleAxis "0"\n') | |
vmat_file.write('\tg_nScaleTexCoordVByModelScaleAxis "0"\n') | |
vmat_file.write('\tg_vTexCoordOffset "[0.000 0.000]"\n') | |
vmat_file.write('\tg_vTexCoordScale "[1.000 1.000]"\n') | |
vmat_file.write('\tg_vTexCoordScrollSpeed "[0.000 0.000]"\n') | |
vmat_file.write("}\n") | |
# Clean up FBX SDK resources | |
manager.Destroy() | |
print("VMAT files generated successfully.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment