0

Apparently there is quenform who does not want my solution so I give the solution by asking a question...

However it is a question which was until now without an answer...

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
  • 1
    Hi, thanks for the post. While answering your own question is possible and even encouraged, it should be done respecting the site structure. Could you break this up into two posts, rephrasing the problem to read like an actual question or expose a problem, while the solution is written as separate answer in the proper section below? Maybe add a short description of the steps accompanied by a few images illustrating the workflow and final results. See How do I write a good answer? – Duarte Farrajota Ramos Dec 10 '23 at 10:34
  • I think that those looking for a solution to this problem will be satisfied with this answer, which is certainly poorly formulated but which has the merit of being the only one. – psilocybe Dec 10 '23 at 11:29
  • 1
    I'm not questioning the quality of the answer which seems fine to me, just asking that you edit it to respect the site structure and post it in the actual answer section below. – Duarte Farrajota Ramos Dec 10 '23 at 11:31
  • 1
    If you give the same answer six times to different questions, then this is obviously a duplicate. Perhaps it would be possible if you didn't write all the answers in the same way, but were more specific about the questions asked. Thank you for your understanding! – quellenform Dec 10 '23 at 11:44
  • I took the trouble to try to give answers to everyone who asks the question of how to serialize and deserialize Blender material. You have deleted all these answers. If you have a better answer give it there – psilocybe Dec 10 '23 at 13:46
  • @quellenform If I gave the same answer six times it was: 1 - six questions asking how to serialize and deserialize blender material, exactly the same question. 2 - there is only one answer that answers these six questions and it is the same answer, I do not have six solutions to propose. 3 - I'm not here to write prose... In any case, given how long it took you to delete everything, I don't think you took the time to read what my solution was. It seems simpler to say duplicate. I wonder what you're for – psilocybe Dec 10 '23 at 15:53
  • As a moderator, it is my job to filter duplicates, among other things, and I am not here to debate btw. Your posts were reported several times by the system, as duplicates are not permitted according to the site rules. They have therefore been removed. If the questions are so similar that one and the same answer is the solution, then the questions are duplicates and should be closed as such, and it would be helpful of you to report them as such instead of posting content multiple times. Please read the rules of this Q&A platform carefully before posting here. Thank you for your understanding. – quellenform Dec 10 '23 at 17:24
  • However, everything is half as bad as it looks: Simply write your answer to the question to which your answer best fits and which best solves the problem. Mark all other similar questions to which your answer could fit as duplicates or refer to your post from there in the comments. This will help keep the site tidy and help others find your solution to the problem. – quellenform Dec 10 '23 at 17:28
  • Well then do your job, because it's really six duplicate questions that I tried to answer, I'm not here to manage the organization of your site but just to provide technical solutions when I can... – psilocybe Dec 10 '23 at 17:55
  • Thank you for your feedback! Just for a better understanding: We moderators work here on a voluntary, unpaid and honorary basis. This is neither "my organization" nor "my site". In order for things to run smoothly here, we are dependent on committed people who help us to steer all the content in the right direction and provide added value for others. Every unnecessary discussion and nagging creates work that is not necessary. – quellenform Dec 10 '23 at 18:42

0 Answers0