Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines
(9 edits)

Well done 3dmodelscc0.

Here if anyone wants glb files of 3dmodelscc0:

 BoQsc/cc0-guns-explosives-pack-glb: https://3dmodelscc0.itch.io/free-cc0-guns-explosives-pack



Most of the models seem to have missing material configuration.
Here is some way on how to fix it and export as .glb files.
Tested on Godot 4.5 and Blender
"C:\Program Files (x86)\Steam\steamapps\common\Blender\blender.exe" --background --python guns_explosives_batch_convert.py
import bpy
import os
source_dir = r"C:\Users\Windows10_new\Downloads\Guns&Explosives (1)\Guns&Explosives"
target_dir = r"C:\Users\Windows10_new\Downloads\Guns&Explosives (1)_glb"
def get_texture_file(folder, prefix, type_keywords):
    for f in os.listdir(folder):
        f_lower = f.lower()
        if f_lower.endswith(('.png', '.jpg', '.jpeg', '.tga', '.tif')):
            if not prefix or f_lower.startswith(prefix.lower()):
                if any(tk in f_lower for tk in type_keywords):
                    return os.path.join(folder, f)
    return None
for root, dirs, files in os.walk(source_dir):
    for file in files:
        if file.lower().endswith('.fbx'):
            fbx_path = os.path.join(root, file)
            
            rel_path = os.path.relpath(root, source_dir)
            out_folder = os.path.join(target_dir, rel_path)
            os.makedirs(out_folder, exist_ok=True)
            
            glb_filename = os.path.splitext(file)[0] + ".glb"
            glb_path = os.path.join(out_folder, glb_filename)
            
            print(f"\n--- Processing: {file} ---")
            
            bpy.ops.wm.read_factory_settings(use_empty=True)
            bpy.ops.import_scene.fbx(filepath=fbx_path)
            
            png_files = [f for f in files if f.lower().endswith('.png')]
            prefixes = []
            for f in png_files:
                if '_' in f:
                    prefixes.append(f.split('_')[0])
                else:
                    prefixes.append(os.path.splitext(f)[0])
            prefixes = list(set(prefixes))
            # Helper function to build the PBR nodes cleanly
            def build_material_nodes(mat, current_prefix):
                mat.use_nodes = True
                nodes = mat.node_tree.nodes
                links = mat.node_tree.links
                nodes.clear()
                
                bsdf = nodes.new(type='ShaderNodeBsdfPrincipled')
                bsdf.location = (0, 0)
                output = nodes.new(type='ShaderNodeOutputMaterial')
                output.location = (300, 0)
                links.new(bsdf.outputs['BSDF'], output.inputs['Surface'])
                
                def add_texture(filepath, color_space, loc, link_input):
                    if filepath and os.path.exists(filepath):
                        # Load image once to prevent glTF export warnings
                        filename = os.path.basename(filepath)
                        if filename in bpy.data.images:
                            img = bpy.data.images[filename]
                        else:
                            img = bpy.data.images.load(filepath)
                            
                        img.colorspace_settings.name = color_space
                        node = nodes.new('ShaderNodeTexImage')
                        node.image = img
                        node.location = loc
                        if link_input == 'Emission Color' and 'Emission Color' not in bsdf.inputs:
                            link_input = 'Emission'
                        links.new(node.outputs['Color'], bsdf.inputs[link_input])
                        return node
                    return None
                t_base = get_texture_file(root, current_prefix, ['base_color', 'base_colo', 'albedo'])
                t_metal = get_texture_file(root, current_prefix, ['metallic', 'metal'])
                t_rough = get_texture_file(root, current_prefix, ['roughness', 'rough'])
                t_norm = get_texture_file(root, current_prefix, ['normal', 'norm'])
                t_emiss = get_texture_file(root, current_prefix, ['emissive', 'emission'])
                t_opac = get_texture_file(root, current_prefix, ['opacity', 'alpha'])
                add_texture(t_base, 'sRGB', (-300, 200), 'Base Color')
                add_texture(t_metal, 'Non-Color', (-300, -50), 'Metallic')
                add_texture(t_rough, 'Non-Color', (-300, -300), 'Roughness')
                add_texture(t_emiss, 'sRGB', (-300, -750), 'Emission Color')
                if t_opac and os.path.exists(t_opac):
                    add_texture(t_opac, 'Non-Color', (-300, -450), 'Alpha')
                    mat.blend_method = 'BLEND' 
                if t_norm and os.path.exists(t_norm):
                    filename = os.path.basename(t_norm)
                    if filename in bpy.data.images:
                        img = bpy.data.images[filename]
                    else:
                        img = bpy.data.images.load(t_norm)
                    img.colorspace_settings.name = 'Non-Color'
                    tex_node = nodes.new('ShaderNodeTexImage')
                    tex_node.image = img
                    tex_node.location = (-600, -600)
                    
                    norm_node = nodes.new('ShaderNodeNormalMap')
                    norm_node.location = (-300, -600)
                    
                    links.new(tex_node.outputs['Color'], norm_node.inputs['Color'])
                    links.new(norm_node.outputs['Normal'], bsdf.inputs['Normal'])
            # --- THE FIX: BRUTE FORCE VS SMART MODE ---
            if len(prefixes) == 1:
                # BRUTE FORCE: For simple models (Pipe Bomb, AT Mine, Claymore, etc.)
                master_mat = bpy.data.materials.new(name=f"{prefixes[0]}_MasterMat")
                build_material_nodes(master_mat, prefixes[0])
                
                for obj in bpy.context.scene.objects:
                    if obj.type == 'MESH':
                        obj.data.materials.clear() # Delete buggy FBX materials
                        obj.data.materials.append(master_mat) # Force our clean material onto everything
            else:
                # SMART MODE: For complex models (Molotov)
                processed_mats = []
                for obj in bpy.context.scene.objects:
                    if obj.type == 'MESH':
                        if not obj.material_slots:
                            obj.data.materials.append(bpy.data.materials.new(name="Fallback"))
                        
                        for i, slot in enumerate(obj.material_slots):
                            if not slot.material:
                                slot.material = bpy.data.materials.new(name=f"Fallback_{i}")
                                
                            mat = slot.material
                            if mat.name in processed_mats:
                                continue
                                
                            processed_mats.append(mat.name)
                            
                            current_prefix = ""
                            for p in prefixes:
                                if p.lower() in mat.name.lower() or p.lower() in obj.name.lower():
                                    current_prefix = p
                                    break
                            if not current_prefix:
                                current_prefix = prefixes[0]
                                
                            build_material_nodes(mat, current_prefix)
            bpy.ops.export_scene.gltf(filepath=glb_path, export_format='GLB')
            print(f"Successfully exported to: {glb_path}")
print("\nBATCH CONVERSION V4 COMPLETE!")