2

I have a model from Source Filmmaker that has a bunch of different textures. It's all imported and ready to be textured using Blender nodes, but since there are so many objects (over 40) in this particular model, I figure it would be a waste of time to manually reshade/texture every single material which could take several hours.

Instead, I could just write a simple script that could read the texture VMT files (from SFM) which define what each texture file is for (diffuse, bumpmap, etc.) in batch and create all the Blender shaders nodes automagically once the .blend file is there.

The snag is .blend is a binary file, and I can't read it or edit it in a text editor. Is there any way at all to create Blender nodes programmatically (i.e. not inside the Blender GUI but perhaps importing other things or using addons?)

  • 1
    It's definitely possible but realistically you would have to do it from your other software if it has a scripting API similar to blender's – Gorgious Oct 23 '20 at 07:30
  • Where it happens is not an issue, VMT files are plain text and I can already get all the information I need from them (texture names, what type of texture, what material gets what textures etc.). My snag is getting the info into Blender. Is there some API reference for creating Blender nodes in python? I tried googling to no avail, I only get info on Nodes that allow you to add your own scripts, but my idea here is to auto-generate existing nodes like principled BSDF, setting Alpha blend types, etc. – pridestalker Oct 23 '20 at 07:34
  • 1
    Oh, alright then if you have a plain text node tree that should only be as difficult as translating it into blender API statements. Could you upload an example of such file ? For example using https://pasteall.org/ . FYI if you want to recreate a node tree in blender you have to do it in 2 steps. First : Create the nodes, then create the links between the different outputs / inputs. For instance (example file from my github) : https://github.com/Gorgious56/Material2Script/blob/main/example_generated_script.py – Gorgious Oct 23 '20 at 07:36
  • That example file from github is perfect! Could you possibly link me to a reference of those commands? I'm just failing to word this correctly in Google for some reason. – pridestalker Oct 23 '20 at 07:42
  • 1
    As always in python if you want to know all the attributes or properties of an object, use print(dir(obj)) . See Nodes, Sockets, and Node Types. You would also have to add images inside the blender memory which is related to another data manager (bpy.data.images). If you would like to throw an example of base file, I can write an answer with more extensive pointers – Gorgious Oct 23 '20 at 07:49
  • You've already been incredibly helpful! As for what I'm working from it really is incredibly simple, most of these lines are essentially assigning image names to type. For example, "$basetexture" "Sandals_dif" in the VMT means in Blender I would create an image texture, load Sandals_dif.png, and then attach that to the base color in Principled BSDF. I'm already capable of generating code in python, I was just really struggling on finding WHAT to generate, which your example file is a goldmine of info for. Thank you!

    I will look into the images data manager as well.

    – pridestalker Oct 23 '20 at 07:56
  • 1
    As a final help I updated a new file on my github with all the existing shader nodes and their relevant properties. Have fun ! – Gorgious Oct 23 '20 at 08:12
  • 1
    Thank you both! I just finished my python script which slurps the project directory for Valve SMT files and converts the info inside of them to Blender nodes. It works PERFECTLY and I have saved so much time on retexturing these models! – pridestalker Oct 23 '20 at 09:33

1 Answers1

1

Gorgious very helpfully pointed me towards the API documents I was struggling to find as well as an example script generating node trees in Blender. In essence my question boiled down to "where are the relevant API docs, I can't find them", and they provided exactly that in the comments of my question.

Nodes: https://docs.blender.org/api/current/bpy.types.Node.html Node Sockets: https://docs.blender.org/api/current/bpy.types.Node.html Node Types: https://docs.blender.org/api/current/bpy.types.ShaderNode.html

EDIT: I have completed my script and it works! It's not general-purpose so I am hesitant to share it, but here you are. The VMT files can be present anywhere either at the same level or in some subfolder relative to the .blend file. Additionally, you need to have used VTFEdit to batch convert all the texture files (vtf) to PNG's. If you prefer TGA, just edit my script and replace PNG with TGA. Also, the actual Shader node tree is visually a mess, because I didn't do any actual placement of the nodes and they all overlap, so it's very utilitarian.

Currently, I only went for the basic properties, being $basetexture, $bumpmap, and $translucent (which sets the material blend mode to Alpha Blend).

import bpy
import os

source_dir = os.path.dirname(bpy.data.filepath)

for r, d, f in os.walk(source_dir): for file in f: if 'vmt' in file:
mat = open(os.path.join(r, file), 'r') material = {} material['cancel'] = False

        lines = [l.lstrip().rstrip().replace('"', '') for l in mat.readlines()
                    if 'basetexture' in l or 'bumpmap' in l or 'translucent' in l]

        material['alpha'] = False

        for l in lines:
            if 'translucent' in l        :
                material['alpha'] = True
            else:        
                img = l.split('/')[-1] + '.png'
                found = False
                for ro, do, fo in os.walk(source_dir):
                    for fi in fo:
                        if img.lower() == fi.lower():
                            img = os.path.join(ro, fi)
                            found = True

                if 'basetexture' in l:
                    material['base'] = img
                if 'bumpmap' in l:
                    material['bump'] = img

                if found == False:
                    material['cancel'] = True

        mat.close()

        if material['cancel'] == False:
            # Begin constructing node tree for this material                
            material_name = file.split('.vmt')[0]

            new_mat = bpy.data.materials.get(material_name)
            if not new_mat:
                new_mat = bpy.data.materials.new(material_name)

            if material['alpha'] == True:
                new_mat.blend_method = 'BLEND'

            new_mat.use_nodes = True
            node_tree = new_mat.node_tree

            nodes = node_tree.nodes
            nodes.clear()

            links = node_tree.links
            links.clear()

            master_node = nodes.new(type='ShaderNodeBsdfPrincipled')

            if 'base' in material:
                bt_node = nodes.new(type='ShaderNodeTexImage')
                bt_node.image = bpy.data.images.load(material['base'])
                links.new(master_node.inputs['Base Color'], bt_node.outputs['Color'])
                links.new(master_node.inputs['Alpha'], bt_node.outputs['Alpha'])

            if 'bump' in material:
                bmi_node = nodes.new(type='ShaderNodeTexImage')
                bmi_node.image = bpy.data.images.load(material['bump'])
                bm_node = nodes.new(type='ShaderNodeBump')

                links.new(bm_node.inputs['Height'], bt_node.outputs['Color'])
                links.new(master_node.inputs['Normal'], bm_node.outputs['Normal'])

            out_node = nodes.new(type='ShaderNodeOutputMaterial')

            links.new(master_node.outputs['BSDF'], out_node.inputs[0])