Skip to content

Instantly share code, notes, and snippets.

@moorage
Last active March 1, 2024 08:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save moorage/02099837cfb9b1f8279b76c79f348325 to your computer and use it in GitHub Desktop.
Save moorage/02099837cfb9b1f8279b76c79f348325 to your computer and use it in GitHub Desktop.
Extract BSDF values from Blender in Python
import bpy
import colorsys
import json
# https://blender.stackexchange.com/a/80047/58635
def _convert_rgb_to_hsv(red, green, blue):
return colorsys.rgb_to_hsv(*_linear_to_srgb(red, green, blue))
# https://blender.stackexchange.com/a/80047/58635
def _linear_to_srgb(r, g, b):
def linear(c):
a = .055
if c <= .0031308:
return 12.92 * c
else:
return (1+a) * c**(1/2.4) - a
return tuple(linear(c) for c in (r, g, b))
def exportMaterials(obj=None):
''' find the material we want'''
if obj:
# list mats for selected objects
for mat in obj.data.materials:
if pbsdf != None:
print("-------",mat.name,"-------")
print(json.dumps(nodeToJSON(mat)))
print("-------")
else:
# list all materials in scene
for mat in bpy.data.materials:
pbsdf = nodeToJSON(mat)
if pbsdf != None:
print("-------",mat.name,"-------")
print(json.dumps(nodeToJSON(mat)))
print("-------")
def nodeToJSON(mat):
'''converts principled materials nodes to a set of list/dictionaries'''
principledNodeCount = 0
volumeNodeCount = 0
pbsdf = {"Name": mat.name}
for node in mat.node_tree.nodes:
if node.type == 'BSDF_PRINCIPLED':
principledNodeCount += 1
base_color_rgb=[1,1,1]
base_color_rgb[0] = node.inputs[0].default_value[0]
base_color_rgb[1] = node.inputs[0].default_value[1]
base_color_rgb[2] = node.inputs[0].default_value[2]
pbsdf["BaseColorHue"], pbsdf["BaseColorSaturation"], pbsdf["BaseColorValue"] = _convert_rgb_to_hsv(*base_color_rgb)
pbsdf["BaseColorAlpha"] = node.inputs[0].default_value[3]
pbsdf["Subsurface"] = node.inputs[1].default_value
pbsdf["SubsurfaceRadiusX"] = node.inputs[2].default_value[0]
pbsdf["SubsurfaceRadiusY"] = node.inputs[2].default_value[1]
pbsdf["SubsurfaceRadiusZ"] = node.inputs[2].default_value[2]
subsurface_color_rgb = [1,1,1]
subsurface_color_rgb[0] = node.inputs[3].default_value[0]
subsurface_color_rgb[1] = node.inputs[3].default_value[1]
subsurface_color_rgb[2] = node.inputs[3].default_value[2]
pbsdf["SubsurfaceColorHue"], pbsdf["SubsurfaceColorSaturation"], pbsdf["SubsurfaceColorValue"] = _convert_rgb_to_hsv(*subsurface_color_rgb)
pbsdf["SubsurfaceColorAlpha"] = node.inputs[3].default_value[3]
pbsdf["Metallic"] = node.inputs[4].default_value
pbsdf["Specular"] = node.inputs[5].default_value
pbsdf["SpecularTint"] = node.inputs[6].default_value
pbsdf["Roughness"] = node.inputs[7].default_value
pbsdf["Anisotropic"] = node.inputs[8].default_value
pbsdf["AnisotropicRotation"] = node.inputs[9].default_value
pbsdf["Sheen"] = node.inputs[10].default_value
pbsdf["SheenTint"] = node.inputs[11].default_value
pbsdf["Clearcoat"]= node.inputs[12].default_value
pbsdf["ClearcoatRoughness"] = node.inputs[13].default_value
pbsdf["IndexOfRefraction"] = node.inputs[14].default_value
pbsdf["Transmission"] = node.inputs[15].default_value
elif node.type == 'CUSTOM' and node.name == 'Principled Volume':
volumeNodeCount += 1
pbsdf['HasPBSDFVolume'] = "true"
vol_color_rgb = [1,1,1]
vol_color_rgb[0] = node.inputs[0].default_value[0]
vol_color_rgb[1] = node.inputs[0].default_value[1]
vol_color_rgb[2] = node.inputs[0].default_value[2]
pbsdf["VolumeColorHue"], pbsdf["VolumeColorSaturation"], pbsdf["VolumeColorValue"] = _convert_rgb_to_hsv(*vol_color_rgb)
pbsdf["VolumeColorAlpha"] = node.inputs[0].default_value[3]
pbsdf["VolumeDensity"] = node.inputs[2].default_value
pbsdf["VolumeAnisotropy"] = node.inputs[4].default_value
vol_absorb_rgb = [1,1,1]
vol_absorb_rgb[0] = node.inputs[5].default_value[0]
vol_absorb_rgb[1] = node.inputs[5].default_value[1]
vol_absorb_rgb[2] = node.inputs[5].default_value[2]
pbsdf["VolumeAbsorptionColorHue"], pbsdf["VolumeAbsorptionColorSaturation"], pbsdf["VolumeAbsorptionColorValue"] = _convert_rgb_to_hsv(*vol_absorb_rgb)
pbsdf["VolumeAbsorptionColorAlpha"] = node.inputs[5].default_value[3]
pbsdf["VolumeEmissionStrength"] = node.inputs[6].default_value
vol_emiss_rgb = [1,1,1]
vol_emiss_rgb[0] = node.inputs[7].default_value[0]
vol_emiss_rgb[1] = node.inputs[7].default_value[1]
vol_emiss_rgb[2] = node.inputs[7].default_value[2]
pbsdf["VolumeEmissionColorHue"], pbsdf["VolumeEmissionColorSaturation"], pbsdf["VolumeEmissionColorValue"] = _convert_rgb_to_hsv(*vol_emiss_rgb)
pbsdf["VolumeEmissionColorAlpha"] = node.inputs[7].default_value[3]
pbsdf["VolumeBlackbodyIntensity"] = node.inputs[8].default_value
vol_bbtint_rgb = [1,1,1]
vol_bbtint_rgb[0] = node.inputs[9].default_value[0]
vol_bbtint_rgb[1] = node.inputs[9].default_value[1]
vol_bbtint_rgb[2] = node.inputs[9].default_value[2]
pbsdf["VolumeBlackbodyTintHue"], pbsdf["VolumeBlackbodyTintSaturation"], pbsdf["VolumeBlackbodyTintValue"] = _convert_rgb_to_hsv(*vol_bbtint_rgb)
pbsdf["VolumeBlackbodyTintAlpha"] = node.inputs[9].default_value[3]
pbsdf["VolumeTemperature"] = node.inputs[10].default_value
if principledNodeCount > 1:
print("Warning: More than one principled Node for material", mat.name)
if volumeNodeCount > 1:
print("Warning: More than one volume Node for material", mat.name)
if principledNodeCount == 0:
return None
return pbsdf
# test code
# exportMaterials(bpy.context.active_object) # exports material(s) for active obj
exportMaterials() # exports materials for entire scene
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment