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 *********************************************")