3
$\begingroup$

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?

$\endgroup$
5
  • $\begingroup$ To read the values and connections this should be a great starting point. $\endgroup$ Commented Dec 14, 2015 at 15:29
  • $\begingroup$ There's a node exporter if i recall correctly.try to look it up $\endgroup$ Commented Dec 14, 2015 at 16:07
  • 2
    $\begingroup$ @Chebhou This one: blenderartists.org/forum/…? $\endgroup$ Commented Dec 14, 2015 at 16:20
  • $\begingroup$ 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 $\endgroup$ Commented Dec 15, 2015 at 8:34
  • $\begingroup$ 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 $\endgroup$ Commented Dec 10, 2023 at 9:41

3 Answers 3

6
$\begingroup$

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" 
$\endgroup$
1
  • $\begingroup$ Warning: You can't currently use xml2rna to deserialize most materials because of this bug $\endgroup$ Commented Jan 18, 2022 at 17:57
1
$\begingroup$

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 *********************************************") 
$\endgroup$
0
0
$\begingroup$

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.

$\endgroup$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.