WIP: MaterialX addon #104594

Closed
Bogdan Nagirniak wants to merge 34 commits from BogdanNagirniak/blender-addons:materialx-addon into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
4 changed files with 40 additions and 33 deletions
Showing only changes of commit dfa1db9e31 - Show all commits

View File

@ -56,14 +56,14 @@ def get_mx_node_cls(mx_node):
raise KeyError(f"Unable to find MxNode class for {mx_node}") raise KeyError(f"Unable to find MxNode class for {mx_node}")
def params_set(node, out_type): def params_set(node, out_type):
return {f"in_{p.getName()}:{p.getType()}" for p in node.getInputs()} | \ return {f"in_{p.getName()}:{p.getType()}" for p in node.getActiveInputs()} | \
{out_type} {out_type}
node_params_set = params_set(mx_node, mx_node.getType()) node_params_set = params_set(mx_node, mx_node.getType())
for cls in classes: for cls in classes:
for nodedef, data_type in cls.get_nodedefs(): for nodedef, data_type in cls.get_nodedefs():
nd_outputs = nodedef.getOutputs() nd_outputs = nodedef.getActiveOutputs()
nd_params_set = params_set(nodedef, 'multioutput' if len(nd_outputs) > 1 else nd_params_set = params_set(nodedef, 'multioutput' if len(nd_outputs) > 1 else
nd_outputs[0].getType()) nd_outputs[0].getType())
if node_params_set.issubset(nd_params_set): if node_params_set.issubset(nd_params_set):

View File

@ -145,21 +145,21 @@ def nodedef_data_type(nodedef):
node_name = nodedef.getNodeString() node_name = nodedef.getNodeString()
if nd_name.startswith('rpr_'): if nd_name.startswith('rpr_'):
return nodedef.getOutputs()[0].getType() return nodedef.getActiveOutputs()[0].getType()
m = re.fullmatch(rf'ND_{node_name}_(.+)', nd_name) m = re.fullmatch(rf'ND_{node_name}_(.+)', nd_name)
if m: if m:
return m[1] return m[1]
return nodedef.getOutputs()[0].getType() return nodedef.getActiveOutputs()[0].getType()
def generate_data_type(nodedef): def generate_data_type(nodedef):
outputs = nodedef.getOutputs() outputs = nodedef.getActiveOutputs()
if len(outputs) != 1: if len(outputs) != 1:
return f"{{'multitypes': {{'{nodedef.getName()}': None, 'nodedef_name': '{nodedef.getName()}'}}}}" return f"{{'multitypes': {{'{nodedef.getName()}': None, 'nodedef_name': '{nodedef.getName()}'}}}}"
return f"{{'{nodedef.getOutputs()[0].getType()}': {{'{nodedef.getName()}': None, 'nodedef_name': '{nodedef.getName()}'}}}}" return f"{{'{nodedef.getActiveOutputs()[0].getType()}': {{'{nodedef.getName()}': None, 'nodedef_name': '{nodedef.getName()}'}}}}"
def input_prop_name(nd_type, name): def input_prop_name(nd_type, name):
@ -204,7 +204,7 @@ class {class_name}(MxNode):
""") """)
ui_folders = [] ui_folders = []
for mx_param in [*nodedef.getParameters(), *nodedef.getInputs()]: for mx_param in [*nodedef.getParameters(), *nodedef.getActiveInputs()]:
f = mx_param.getAttribute("uifolder") f = mx_param.getAttribute("uifolder")
if f and f not in ui_folders: if f and f not in ui_folders:
ui_folders.append(f) ui_folders.append(f)
@ -242,11 +242,11 @@ class {class_name}(MxNode):
nd_type = nodedef_data_type(nd) nd_type = nodedef_data_type(nd)
code_strings.append("") code_strings.append("")
for input in nd.getInputs(): for input in nd.getActiveInputs():
prop_code = generate_property_code(input, category) prop_code = generate_property_code(input, category)
code_strings.append(f" {input_prop_name(nd_type, input.getName())}: {prop_code}") code_strings.append(f" {input_prop_name(nd_type, input.getName())}: {prop_code}")
for output in nd.getOutputs(): for output in nd.getActiveOutputs():
prop_code = generate_property_code(output, category) prop_code = generate_property_code(output, category)
code_strings.append(f" {output_prop_name(nd_type, output.getName())}: {prop_code}") code_strings.append(f" {output_prop_name(nd_type, output.getName())}: {prop_code}")

View File

@ -10,6 +10,8 @@ from .. import utils
from .. import logging from .. import logging
log = logging.Log("nodes.node") log = logging.Log("nodes.node")
mtlx_documents = {}
class MxNodeInputSocket(bpy.types.NodeSocket): class MxNodeInputSocket(bpy.types.NodeSocket):
bl_idname = utils.with_prefix('MxNodeInputSocket') bl_idname = utils.with_prefix('MxNodeInputSocket')
@ -20,7 +22,7 @@ class MxNodeInputSocket(bpy.types.NodeSocket):
return return
nd = node.nodedef nd = node.nodedef
nd_input = nd.getInput(self.name) nd_input = nd.getActiveInput(self.name)
nd_type = nd_input.getType() nd_type = nd_input.getType()
uiname = utils.get_attr(nd_input, 'uiname', utils.title_str(nd_input.getName())) uiname = utils.get_attr(nd_input, 'uiname', utils.title_str(nd_input.getName()))
@ -36,7 +38,7 @@ class MxNodeInputSocket(bpy.types.NodeSocket):
def draw_color(self, context, node): def draw_color(self, context, node):
return utils.get_socket_color(node.nodedef.getInput(self.name).getType() return utils.get_socket_color(node.nodedef.getActiveInput(self.name).getType()
if is_mx_node_valid(node) else 'undefined') if is_mx_node_valid(node) else 'undefined')
@ -49,16 +51,16 @@ class MxNodeOutputSocket(bpy.types.NodeSocket):
return return
nd = node.nodedef nd = node.nodedef
mx_output = nd.getOutput(self.name) mx_output = nd.getActiveOutput(self.name)
uiname = utils.get_attr(mx_output, 'uiname', utils.title_str(mx_output.getName())) uiname = utils.get_attr(mx_output, 'uiname', utils.title_str(mx_output.getName()))
uitype = utils.title_str(mx_output.getType()) uitype = utils.title_str(mx_output.getType())
if uiname.lower() == uitype.lower() or len(nd.getOutputs()) == 1: if uiname.lower() == uitype.lower() or len(nd.getActiveOutputs()) == 1:
layout.label(text=uitype) layout.label(text=uitype)
else: else:
layout.label(text=f"{uiname}: {uitype}") layout.label(text=f"{uiname}: {uitype}")
def draw_color(self, context, node): def draw_color(self, context, node):
return utils.get_socket_color(node.nodedef.getOutput(self.name).getType() return utils.get_socket_color(node.nodedef.getActiveOutput(self.name).getType()
if is_mx_node_valid(node) else 'undefined') if is_mx_node_valid(node) else 'undefined')
@ -80,9 +82,14 @@ class MxNode(bpy.types.ShaderNode):
def get_nodedef(cls, data_type): def get_nodedef(cls, data_type):
if not cls._data_types[data_type]['nd']: if not cls._data_types[data_type]['nd']:
# loading nodedefs # loading nodedefs
if cls._file_path not in mtlx_documents:
doc = mx.createDocument() doc = mx.createDocument()
search_path = mx.FileSearchPath(str(utils.MX_LIBS_DIR)) search_path = mx.FileSearchPath(str(utils.MX_LIBS_DIR))
mx.readFromXmlFile(doc, str(utils.ADDON_ROOT_DIR / cls._file_path), searchPath=search_path) mx.readFromXmlFile(doc, str(utils.MX_LIBS_DIR / cls._file_path), searchPath=search_path)
mtlx_documents[cls._file_path] = doc
doc = mtlx_documents[cls._file_path]
for val in cls._data_types.values(): for val in cls._data_types.values():
val['nd'] = doc.getNodeDef(val['nd_name']) val['nd'] = doc.getNodeDef(val['nd_name'])
@ -100,7 +107,7 @@ class MxNode(bpy.types.ShaderNode):
@property @property
def mx_node_path(self): def mx_node_path(self):
nd = self.nodedef nd = self.nodedef
if '/' in self.name or utils.is_shader_type(nd.getOutputs()[0].getType()): if '/' in self.name or utils.is_shader_type(nd.getActiveOutputs()[0].getType()):
return self.name return self.name
return f"NG/{self.name}" return f"NG/{self.name}"
@ -126,8 +133,8 @@ class MxNode(bpy.types.ShaderNode):
for link in nodetree.links: for link in nodetree.links:
if hasattr(link.from_socket.node, 'nodedef') and hasattr(link.to_socket.node, 'nodedef'): if hasattr(link.from_socket.node, 'nodedef') and hasattr(link.to_socket.node, 'nodedef'):
socket_from_type = link.from_socket.node.nodedef.getOutput(link.from_socket.name).getType() socket_from_type = link.from_socket.node.nodedef.getActiveOutput(link.from_socket.name).getType()
socket_to_type = link.to_socket.node.nodedef.getInput(link.to_socket.name).getType() socket_to_type = link.to_socket.node.nodedef.getActiveInput(link.to_socket.name).getType()
if socket_to_type != socket_from_type: if socket_to_type != socket_from_type:
link.is_valid = False link.is_valid = False
@ -140,7 +147,7 @@ class MxNode(bpy.types.ShaderNode):
nodedef = self.nodedef nodedef = self.nodedef
for i, nd_input in enumerate(utils.get_nodedef_inputs(nodedef, False)): for i, nd_input in enumerate(utils.get_nodedef_inputs(nodedef, False)):
self.inputs[i].name = nd_input.getName() self.inputs[i].name = nd_input.getName()
for i, nd_output in enumerate(nodedef.getOutputs()): for i, nd_output in enumerate(nodedef.getActiveOutputs()):
self.outputs[i].name = nd_output.getName() self.outputs[i].name = nd_output.getName()
def init(self, context): def init(self, context):
@ -149,7 +156,7 @@ class MxNode(bpy.types.ShaderNode):
for nd_input in utils.get_nodedef_inputs(nodedef, False): for nd_input in utils.get_nodedef_inputs(nodedef, False):
self.create_input(nd_input) self.create_input(nd_input)
for nd_output in nodedef.getOutputs(): for nd_output in nodedef.getActiveOutputs():
self.create_output(nd_output) self.create_output(nd_output)
if self._ui_folders: if self._ui_folders:
@ -264,7 +271,7 @@ class MxNode(bpy.types.ShaderNode):
mx_param = mx_node.addInput(nd_input.getName(), nd_type) mx_param = mx_node.addInput(nd_input.getName(), nd_type)
utils.set_param_value(mx_param, val, nd_type) utils.set_param_value(mx_param, val, nd_type)
if len(nodedef.getOutputs()) > 1: if len(nodedef.getActiveOutputs()) > 1:
mx_node.setType('multioutput') mx_node.setType('multioutput')
return mx_node, nd_output return mx_node, nd_output
@ -340,10 +347,10 @@ class MxNode(bpy.types.ShaderNode):
return getattr(self, self._input_prop_name(name)) return getattr(self, self._input_prop_name(name))
def get_nodedef_input(self, in_key: [str, int]): def get_nodedef_input(self, in_key: [str, int]):
return self.nodedef.getInput(self.inputs[in_key].name) return self.nodedef.getActiveInput(self.inputs[in_key].name)
def get_nodedef_output(self, out_key: [str, int]): def get_nodedef_output(self, out_key: [str, int]):
return self.nodedef.getOutput(self.outputs[out_key].name) return self.nodedef.getActiveOutput(self.outputs[out_key].name)
def set_input_value(self, in_key, value): def set_input_value(self, in_key, value):
setattr(self, self._input_prop_name(self.inputs[in_key].name), value) setattr(self, self._input_prop_name(self.inputs[in_key].name), value)

View File

@ -74,7 +74,7 @@ def set_param_value(mx_param, val, nd_type, nd_output=None):
if nd_output: if nd_output:
mx_output_name += f'_{nd_output.getName()}' mx_output_name += f'_{nd_output.getName()}'
mx_output = val_nodegraph.getOutput(mx_output_name) mx_output = val_nodegraph.getActiveOutput(mx_output_name)
if not mx_output: if not mx_output:
mx_output = val_nodegraph.addOutput(mx_output_name, val.getType()) mx_output = val_nodegraph.addOutput(mx_output_name, val.getType())
mx_output.setNodeName(node_name) mx_output.setNodeName(node_name)
@ -182,7 +182,7 @@ def parse_value_str(val_str, mx_type, *, first_only=False, is_enum=False):
def get_nodedef_inputs(nodedef, uniform=None): def get_nodedef_inputs(nodedef, uniform=None):
for nd_input in nodedef.getInputs(): for nd_input in nodedef.getActiveInputs():
if (uniform is True and nd_input.getAttribute('uniform') != 'true') or \ if (uniform is True and nd_input.getAttribute('uniform') != 'true') or \
(uniform is False and nd_input.getAttribute('uniform') == 'true'): (uniform is False and nd_input.getAttribute('uniform') == 'true'):
continue continue
@ -523,7 +523,7 @@ def import_materialx_from_file(node_tree, doc: mx.Document, file_path):
new_mx_nodegraph = next(ng for ng in doc.getNodeGraphs() new_mx_nodegraph = next(ng for ng in doc.getNodeGraphs()
if ng.getNodeDefString() == nodedef.getName()) if ng.getNodeDefString() == nodedef.getName())
mx_output = new_mx_nodegraph.getOutput(mx_output_name) mx_output = new_mx_nodegraph.getActiveOutput(mx_output_name)
node_name = mx_output.getNodeName() node_name = mx_output.getNodeName()
new_mx_node = new_mx_nodegraph.getNode(node_name) new_mx_node = new_mx_nodegraph.getNode(node_name)
@ -534,9 +534,9 @@ def import_materialx_from_file(node_tree, doc: mx.Document, file_path):
node.data_type = data_type node.data_type = data_type
nodedef = node.nodedef nodedef = node.nodedef
for mx_input in mx_node.getInputs(): for mx_input in mx_node.getActiveInputs():
input_name = mx_input.getName() input_name = mx_input.getName()
nd_input = nodedef.getInput(input_name) nd_input = nodedef.getActiveInput(input_name)
if nd_input.getAttribute('uniform') == 'true': if nd_input.getAttribute('uniform') == 'true':
node.set_param_value(input_name, parse_value( node.set_param_value(input_name, parse_value(
node, mx_input.getValue(), mx_input.getType(), file_prefix)) node, mx_input.getValue(), mx_input.getType(), file_prefix))
@ -563,7 +563,7 @@ def import_materialx_from_file(node_tree, doc: mx.Document, file_path):
new_node = import_node(new_mx_node) new_node = import_node(new_mx_node)
out_name = mx_input.getAttribute('output') out_name = mx_input.getAttribute('output')
if len(new_node.nodedef.getOutputs()) > 1 and out_name: if len(new_node.nodedef.getActiveOutputs()) > 1 and out_name:
new_node_output = new_node.outputs[out_name] new_node_output = new_node.outputs[out_name]
else: else:
new_node_output = new_node.outputs[0] new_node_output = new_node.outputs[0]
@ -575,7 +575,7 @@ def import_materialx_from_file(node_tree, doc: mx.Document, file_path):
if new_nodegraph_name: if new_nodegraph_name:
mx_output_name = mx_input.getAttribute('output') mx_output_name = mx_input.getAttribute('output')
new_mx_nodegraph = mx_nodegraph.getNodeGraph(new_nodegraph_name) new_mx_nodegraph = mx_nodegraph.getNodeGraph(new_nodegraph_name)
mx_output = new_mx_nodegraph.getOutput(mx_output_name) mx_output = new_mx_nodegraph.getActiveOutput(mx_output_name)
node_name = mx_output.getNodeName() node_name = mx_output.getNodeName()
new_mx_node = new_mx_nodegraph.getNode(node_name) new_mx_node = new_mx_nodegraph.getNode(node_name)
new_node = import_node(new_mx_node, mx_output_name) new_node = import_node(new_mx_node, mx_output_name)
@ -583,7 +583,7 @@ def import_materialx_from_file(node_tree, doc: mx.Document, file_path):
continue continue
out_name = mx_output.getAttribute('output') out_name = mx_output.getAttribute('output')
if len(new_node.nodedef.getOutputs()) > 1 and out_name: if len(new_node.nodedef.getActiveOutputs()) > 1 and out_name:
new_node_output = new_node.outputs[out_name] new_node_output = new_node.outputs[out_name]
else: else:
new_node_output = new_node.outputs[0] new_node_output = new_node.outputs[0]