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