Simple exporter script - need some Python advice

Viewed 4 times

0

I am trying to script a simple exporter that would help me get material data to a webGL engine from Blender 2.8. I am able to get default values of roughness, metallic and transparency. For now this is how it looks:


import bpy

materials_list = []

for mat in  bpy.data.materials:    
    mat_dict = {}
    mat_dict["name"] = mat.name
    mat_dict["roughness"] = mat.node_tree.nodes.get("Principled BSDF").inputs["Roughness"].default_value
    mat_dict["metallic"] = mat.node_tree.nodes.get("Principled BSDF").inputs["Metallic"].default_value
    mat_dict["opacity"] = 1 - mat.node_tree.nodes.get("Principled BSDF").inputs["Transmission"].default_value
    materials_list.append(mat_dict)    
  

print(materials_list)

I am can’t find a way to check if the Principled BSDF has textures attached controlling this values and save their paths. How can I extend this script to get the texture information?

Here how I would do it, perhaps there is another way but it works.

updated

Hi @const, thank you for your answer! Unfortunatelly it doesn’t work on my side. Gives me an error:
KeyError : 'bpy_prop_collection[key]: key “Material” not found. Not sure how to interpret it.

Anyway I got some ideas from your code and tried to incorporate it into my script but with no success. Here is what I tried:

import bpy

materials_list = []

for mat in  bpy.data.materials:    
    mat_dict = {}
    prinBSDF = mat.node_tree.nodes["Principled BSDF"]

    mat_dict["name"] = mat.name

    if prinBSDF.inputs["Roughness"].type == 'TEX_IMAGE':
        mat_dict["roughness"] = prinBSDF.inputs["Roughness"].image.filepath
    else:
        mat_dict["roughness"] = prinBSDF.inputs["Roughness"].default_value
        
    materials_list.append(mat_dict)    
  

print(materials_list)

but I get an error "AttributeError: ‘NodeSocketFloatFactor’ object has no attribute ‘image’.

Do you have any idea what is missing here? Or am I completely wrong about it?

Code updated here as well…

Hi @const, thank you very much for your time! It all seems very logical for me but unfortunately doesn’t work. It still collects only default values and ignores the bitmaps. I checked a simplified version of the script to ignore default value and force writing textures and I get an error "AttributeError: ‘NodeSocketFloatFactor’ object has no attribute ‘image’ ". Do you have any idea how to move forward with it?

This time I tried to get access to the data types more properly. It would be fun though if Python allowed direct access, less code to type.


import bpy

def get_materials():
    # a list of dictionaries
    matlist = []
            
    # iterating all of the materials
    for mat in  bpy.data.materials:    
        
        # get the bsdf node
        bsdf = None
        if mat.node_tree != None: # should have node tree
            for n in mat.node_tree.nodes.keys(): # searching all nodes
                node = mat.node_tree.nodes[n]
                if node.type == 'BSDF_PRINCIPLED': # node type should be bdsf
                    bsdf = node # setting the node
                    break # exit node tree
        
        # bsdf not found, skipping
        if bsdf == None:
            continue
                
        # dictionary
        d = {} # create a dictionary
        d["name"] = mat.name # set material name

        # get roughness socket
        roughness = bsdf.inputs.get("Roughness")

        # ensure input node is an image
        if len(roughness.links) == 1 and roughness.links[0].from_node.type == 'TEX_IMAGE':
            d["roughness"] = roughness.links[0].from_node.image.filepath
        else:
            d["roughness"] = roughness.default_value


        # list
        matlist.append(d) # appending to list
        
    # return
    return matlist

m = get_materials()
print(m)

I tried also some tests in various cases. With an image and then without one. You should see the same results.

2020-07-06 12_42_14-D__programs_graphics_blender28_blender.exe

Thank you very much @const! Now it is working.

I faced another challenge trying to collect information about lights in the scene. I can easily get all properties of the lights but can’t find a way to get location and rotation of all instances of the lights. How should I approach this?

For now this is how it looks like:

import bpy
import math


def get_lights():
    
    lights_list = []
    
    for light in bpy.data.lights:
        
        light_dict = {}
        
        light_dict['type'] = light.type
        
        light_dict['name'] = light.name
        
        if light.type == 'SPOT':
            light_dict['angle'] = int(light.spot_size*180/math.pi)
        

        if light.type == 'SPOT' or light.type == 'POINT':
            light_dict['size'] = light.shadow_soft_size
            
        light_dict['strength'] = light.energy
        
        light_color = []
        light_color.append(round(light.color.r, 2))
        light_color.append(round(light.color.g, 2))
        light_color.append(round(light.color.b, 2))
        light_dict['color'] = light_color
        
        
        lights_list.append(light_dict)
        
    return lights_list

l = get_lights()
print(l)

To get location and rotation you can do this

>>> bpy.context.active_object.location
Vector((4.076245307922363, 1.0054539442062378, 5.903861999511719))

>>> bpy.context.active_object.rotation_euler
Euler((0.6503279805183411, 0.055217113345861435, 1.8663908243179321), 'XYZ')

Note that the rotation is in radians, so it means that 360deg = 2*Pi.