3

I am searching of ways to serialize Blender Cycles materials to files using Python. But as of now I've found no such ways.

Maybe by writing the data of the materials to files but how can I do that? Even the slightest of contributions are appreciated.

If only there was a way to serialize materials to JSON/Binary/XML.

Perhaps someone here can lead me to light.

Maybe in the way Blender stores the materials?

Farhan Anam
  • 203
  • 1
  • 9
  • To read the values and connections this should be a great starting point. – p2or Dec 14 '15 at 15:29
  • There's a node exporter if i recall correctly.try to look it up – Chebhou Dec 14 '15 at 16:07
  • 2
    @Chebhou This one: http://blenderartists.org/forum/showthread.php?346359-Addon-Export-node-groups? – p2or Dec 14 '15 at 16:20
  • Meanwhile, I have found one way, but that's not what I asked in the question - To save the materials in a blender file and write an add-on to import only the materials from it. But I do hope someone pops with a better script only way – Farhan Anam Dec 15 '15 at 08:34
  • I can serialize and deserialize any Blender material configuration in JSON. It is possible for me to save my materials in a FreeCAD file and restore all these materials in Blender from this FreeCAD file. for more information see this thread on the FreeCAD forum – psilocybe Dec 10 '23 at 09:41

3 Answers3

6

There is the rna_xml module, which produces pretty verbose xml

from rna_xml import rna2xml

rna2xml(root_node="MyRootName",
        root_rna=bpy.data.materials['Material'].node_tree)

produces (not full output). There is also an rna2xml routine. To output to a file use rna2xml(fw=f.write, ...) where f is a file.

<MyRootName>
  <inputs>
  </inputs>
  <links>
    <NodeLink is_valid="TRUE"
              from_node="ShaderNodeBsdfDiffuse::Diffuse BSDF"
              to_node="ShaderNodeOutputMaterial::Material Output"
              is_hidden="FALSE">
      <from_socket>
        <NodeSocketShader name="BSDF"
                          identifier="BSDF"
                          is_output="TRUE"
                          hide="FALSE"
                          enabled="TRUE"
                          link_limit="4095"
                          is_linked="TRUE"
                          show_expanded="FALSE"
                          hide_value="FALSE"
                          node="ShaderNodeBsdfDiffuse::Diffuse BSDF"
                          type="SHADER"
                          bl_idname="NodeSocketShader">
        </NodeSocketShader>
      </from_socket>
      <to_socket>
        <NodeSocketShader name="Surface"
                          identifier="Surface"
                          is_output="FALSE"
                          hide="FALSE"
                          enabled="TRUE"
                          link_limit="1"
                          is_linked="TRUE"
                          show_expanded="FALSE"
                          hide_value="FALSE"
                          node="ShaderNodeOutputMaterial::Material Output"
                          type="SHADER"
                          bl_idname="NodeSocketShader">
        </NodeSocketShader>
      </to_socket>
    </NodeLink>
  </links>
  <nodes>
    <ShaderNodeOutputMaterial type="OUTPUT_MATERIAL"
                              location="300 300"
                              width="140"
                              width_hidden="42"
                              height="100"
                              dimensions="178.889 116.333"
                              name="Material Output"
                              label=""
                              parent="NONE"
                              use_custom_color="FALSE"
                              color="0.608 0.608 0.608"
                              select="TRUE"
                              show_options="TRUE"
                              show_preview="FALSE"
                              hide="FALSE"
                              mute="FALSE"
                              show_texture="FALSE"
                              shading_compatibility="{NEW_SHADING}"
                              bl_idname="ShaderNodeOutputMaterial"
                              bl_label="Material Output"
                              bl_description=""
                              bl_icon="NONE"
                              bl_static_type="OUTPUT_MATERIAL"
                              bl_width_default="140"
                              bl_width_min="100"
                              bl_width_max="320"
                              bl_height_default="100"
                              bl_height_min="30"
                              bl_height_max="30"
                              is_active_output="TRUE">
      <inputs>
        <NodeSocketShader name="Surface"
                          identifier="Surface"
                          is_output="FALSE"
                          hide="FALSE"
batFINGER
  • 84,216
  • 10
  • 108
  • 233
1

I can serialize and deserialize any Blender material configuration in JSON. It is possible for me to save my materials in a FreeCAD file and restore all these materials in Blender from this FreeCAD file.

Serialize:

    # browse all materiels in the Blender scene
    for bmat in bpy.data.materials:
        if not bmat.use_nodes:
            continue
            msg = f"Blender material: {bmat.name}, don't use node, skipped...\n"
            warnings.append(msg)
            App.Console.PrintMessage(msg)
            continue
        if bmat.name in materials and materials[bmat.name].Material.get(root):
            msg = f"FreeCAD material: {bmat.name}, already has a Blender configuration, skipped...\n"
            warnings.append(msg)
            App.Console.PrintMessage(msg)
            continue
    matdata = {}
    for node in bmat.node_tree.nodes:
        links = {}
        inputs = {}
        outputs = {}
        sockets = {}

        if node.inputs:
            link, input = _getInputData(warnings, bmat, node)
            links.update(link)
            inputs.update(input)

        if node.outputs:
            output = _getOutputs(warnings, bmat, node)
            outputs.update(output)

        sockets = _getSocketProperties(warnings, node, bmat.name, node.name, [])

        matdata[node.name] = {'Type':     node.__class__.__name__,
                              'Sockets':  sockets,
                              'Link':     links,
                              'Inputs':   inputs,
                              'Outputs':  outputs}

    if matdata:
        if bmat.name in materials:
            mat = materials[bmat.name]
        else:
            mat = makeMaterial(bmat.name)
            materials[bmat.name] = mat
        temp = mat.Material.copy()
        temp[root] = json.dumps(tuple(matdata.keys()))
        for node, data in matdata.items():
            temp[root + '.' + node] = json.dumps(data)
        mat.Material = temp

def _getInputData(warnings, bmat, node): links = {} inputs = {} for input in node.inputs: for link in input.links: links[input.name] = (link.from_node.name, link.from_socket.name) if input.type == 'VALUE': v = input.default_value value = v elif input.type == 'VECTOR': v = input.default_value value = (v[0], v[1], v[2]) elif input.type == 'RGBA': v = input.default_value value = (v[0], v[1], v[2], v[3]) elif input.type == 'SHADER': continue else: msg = f"FreeCAD material {bmat.name} on node {node.name} can't read input: {input.name} type {input.type} is not supported, skipping...\n" warnings.append(msg) App.Console.PrintMessage(msg) continue App.Console.PrintMessage(f"Material Name: {bmat.name} Node: {node.name} Input: {input.name} - Value: {value}\n") inputs[input.name] = value return links, inputs

def _getOutputs(warnings, bmat, node): outputs = {} for output in node.outputs: if output.type == 'VALUE': v = output.default_value value = v elif output.type == 'VECTOR': v = output.default_value value = (v[0], v[1], v[2]) elif output.type == 'RGBA': v = output.default_value value = (v[0], v[1], v[2], v[3]) elif output.type == 'SHADER': continue else: msg = f"FreeCAD material {bmat.name} on node {node.name} can't read output: {output.name} type {output.type} is not supported, skipping...\n" warnings.append(msg) App.Console.PrintMessage(msg) continue App.Console.PrintMessage(f"Material Name: {bmat.name} Node: {node.name} output: {output.name} - Value: {value}\n") outputs[output.name] = value return outputs

def getSocketProperties(warnings, obj, mat, node, properties): data = {} skipping = ('rna_type', 'inputs', 'outputs', 'dimensions', 'type', 'is_hidden', 'from_node', 'from_socket', 'to_node', 'to_socket') for p in dir(obj): if p not in skipping and not (p.startswith('') or p.startswith('bl_')): v = getattr(obj, p) if v is None or isinstance(v, bpy.types.bpy_func): continue elif isinstance(v, (str, int, float, bool)): value = v elif isinstance(v, mathutils.Color): value = {'r': v.r, 'g': v.g, 'b': v.b} elif isinstance(v, mathutils.Euler): value = {'x': v.x, 'y': v.y, 'z': v.z, 'order': v.order} elif isinstance(v, mathutils.Vector): if len(v) == 2: value = {'x': v.x, 'y': v.y} elif len(v) == 3: value = {'x': v.x, 'y': v.y, 'z': v.z} elif len(v) == 4: value = {'x': v.x, 'y': v.y, 'z': v.z, 'w': v.w} elif isinstance(v, bpy.types.bpy_prop_array): value = [] for d in v: value.append(d) elif isinstance(v, bpy.types.bpy_prop_collection): value = {} properties.append(p) for k, d in v.items(): value[k] = _getSocketProperties(warnings, d, mat, node, properties) elif isinstance(v, bpy.types.TexMapping): properties.append(p) value = _getSocketProperties(warnings, v, mat, node, properties) elif isinstance(v, bpy.types.ColorRamp): properties.append(p) value = _getSocketProperties(warnings, v, mat, node, properties) elif isinstance(v, bpy.types.ColorMapping): properties.append(p) value = _getSocketProperties(warnings, v, mat, node, properties) else: msg = f"FreeCAD material {mat} on node {node} can't read property: {p} type {type(v)} is not supported, skipping...\n" warnings.append(msg) App.Console.PrintMessage(msg) continue data[p] = value properties.append(p) App.Console.PrintMessage(f"Node socket: {node} property: {'.'.join(properties)} value: {str(value)}\n") return data

Deserialize:

def _setMaterialNodes(bmat, mat, root):
    links = {}
    sockets = {}
    inputs = {}
    outputs = {}
    data = mat.Material.get(root)
    print("_setMaterialNodes() 1 data: %s" % data)
    if data:
        nodes = json.loads(data)
        for name in nodes:
            link, socket, input, output = _createNode(bmat, mat, root, name)
            links.update(link)
            sockets.update(socket)
            inputs.update(input)
            outputs.update(output)
        for node, link in links.items():
            bnode = bmat.node_tree.nodes[node]
            _setLinks(bmat, bnode, link)
        for node, socket in sockets.items():
            bnode = bmat.node_tree.nodes[node]
            _setSockets(bnode, socket)
        for node, input in inputs.items():
            bnode = bmat.node_tree.nodes[node]
            _setInputs(bnode, input)
        for node, output in outputs.items():
            bnode = bmat.node_tree.nodes[node]
            _setOutputs(bnode, output)

def _createNode(bmat, mat, root, name): links = {} sockets = {} inputs = {} outputs = {} data = mat.Material.get(root + '.' + name) if data: node = json.loads(data) print(f"Create Node socket {name} of type {node['Type']}") bnode = bmat.node_tree.nodes.new(type=node['Type']) bnode.name = name links[bnode.name] = node['Link'] sockets[bnode.name] = node['Sockets'] inputs[bnode.name] = node['Inputs'] outputs[bnode.name] = node['Outputs'] return links, sockets, inputs, outputs

def _setLinks(bmat, bnode, links): for input, outputs in links.items(): _setLink(bmat, bnode, input, *outputs)

def _setLink(bmat, bnode, input, node2, output): bmat.node_tree.links.new(bmat.node_tree.nodes[node2].outputs[output], bnode.inputs[input])

def _setSockets(obj, sockets): for property, value in sockets.items(): if isinstance(value, dict): if property.isnumeric(): _setSockets(obj[int(property)], value) else: _setSockets(getattr(obj, property), value) else: setattr(obj, property, value)

def _setInputs(bnode, inputs): for input, value in inputs.items(): binput = bnode.inputs.get(input) if binput: binput.default_value = value else: print("_setInputs() ERROR *********************************************")

def _setOutputs(bnode, outputs): for output, value in outputs.items(): boutput = bnode.outputs.get(output) if boutput: boutput.default_value = value else: print("_setOutputs() ERROR *********************************************")

psilocybe
  • 11
  • 4
  • I think this is the right place for your answer, as it is specifically related to the topic of materials and JSON. The other questions are partly about XML and/or more than materials or have already been marked as solved in a different way. Thank you for your patience and +1 for your contribution. – quellenform Dec 11 '23 at 00:19
0

As mentioned by @batFINGER you can use rna2xml to serialize node trees to an xml file. Then if you want to deserialize the node tree you can use rna2xml.xml2rna function.

Here is a snippet of code describing the whole procedure.Be aware though that you might get Read-only attributes error when importing the xml, in that case you can simply delete the attributes of the root node that pose a problem.

Cheers

EDIT: Actually this blender module is useful to serialize structures that have a pre-determined content but it is in no way useful to serialize whole materials and node trees from scratch. One easy way to get around that issue is to have another scene in the same .blend file with all the materials. Those can be accessed across all scenes with bpy.data.materials[<key>]. One could decide to augment the RNA serializer offered by Blender but I decided that it would take too much time for my current project.

paascal
  • 121
  • 5