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.
7 changed files with 89 additions and 35 deletions
Showing only changes of commit 61dd62cb6d - Show all commits

View File

@ -32,13 +32,13 @@ class Id:
class NodeItem:
"""This class is a wrapper used for doing operations on MaterialX nodes, floats, and tuples"""
def __init__(self, id: Id, ng: [mx.Document, mx.NodeGraph], data: [tuple, float, mx.Node]):
def __init__(self, id: Id, ng: [mx.Document, mx.NodeGraph], data: [tuple, float, mx.Node], prefix=''):
self.id = id
self.nodegraph = ng
self.data = data
self.nodedef = None
if isinstance(data, mx.Node):
MxNode_cls, _ = get_mx_node_cls(data)
MxNode_cls, _ = get_mx_node_cls(data, prefix)
self.nodedef = MxNode_cls.get_nodedef(self.type)
def node_item(self, value):
@ -370,10 +370,14 @@ class NodeParser:
return self.get_input_default(in_key)
def create_node(self, node_name, nd_type, inputs=None):
def create_node(self, node_name, nd_type, *, prefix='', inputs=None):
nodegraph = utils.get_nodegraph_by_path(self.doc, self.nodegraph_path, True)
node = nodegraph.addNode(node_name, f"{node_name}_{self.id()}", nd_type)
node_item = NodeItem(self.id, nodegraph, node)
node_item = NodeItem(self.id, nodegraph, node, prefix)
mx_type = node_item.nodedef.getType()
if mx_type != nd_type:
node.setType(mx_type)
if inputs:
node_item.set_inputs(inputs)

View File

@ -23,7 +23,7 @@ class ShaderNodeOutputMaterial(NodeParser):
return None
result = self.create_node('surfacematerial', 'material', {
result = self.create_node('surfacematerial', 'material', inputs={
'surfaceshader': surface,
})

View File

@ -31,6 +31,10 @@ def enabled(val):
return True
def get_node_type(node):
return node.getType() if isinstance(node, mx.Node) else node.nodedef.getType()
class ShaderNodeBsdfPrincipled(NodeParser):
nodegraph_path = ""
@ -89,7 +93,7 @@ class ShaderNodeBsdfPrincipled(NodeParser):
tangent = self.get_input_link('Tangent')
# CREATING STANDARD SURFACE
result = self.create_node('standard_surface', 'surfaceshader', {
result = self.create_node('standard_surface', 'surfaceshader', prefix='BXDF', inputs={
'base': 1.0,
'base_color': base_color,
'diffuse_roughness': roughness,
@ -162,7 +166,7 @@ class ShaderNodeBsdfDiffuse(NodeParser):
# Also tried burley_diffuse_bsdf and oren_nayar_diffuse_bsdf here, but Blender crashes with them
# CREATING STANDARD SURFACE
result = self.create_node('standard_surface', 'surfaceshader', {
result = self.create_node('standard_surface', 'surfaceshader', prefix='BXDF', inputs={
'base_color': color,
'diffuse_roughness': 1.0 - roughness,
'normal': normal,
@ -179,7 +183,7 @@ class ShaderNodeBsdfGlass(NodeParser):
normal = self.get_input_link('Normal')
# CREATING STANDARD SURFACE
result = self.create_node('standard_surface', 'surfaceshader', {
result = self.create_node('standard_surface', 'surfaceshader', prefix='BXDF', inputs={
'base': 0.0,
'normal': normal,
'specular': 1.0,
@ -200,7 +204,7 @@ class ShaderNodeEmission(NodeParser):
nodegraph_path = ""
def export(self):
result = self.create_node('standard_surface', 'surfaceshader')
result = self.create_node('standard_surface', 'surfaceshader', prefix='BXDF')
color = self.get_input_value('Color')
strength = self.get_input_value('Strength')
@ -222,23 +226,46 @@ class ShaderNodeMixShader(NodeParser):
shader1 = self.get_input_link(1)
shader2 = self.get_input_link(2)
mix = None
if shader1 is None and shader2 is None:
return None
if shader1 is None:
return shader2
if shader2 is None:
return shader1
mix = self.create_node('mix', get_node_type(shader1).lower(), prefix='PBR', inputs={
'fg': shader1,
'mix': factor
})
result = self.create_node('STD_mix', 'surfaceshader', {
'fg': shader1,
'bg': shader2,
'mix': factor
if shader1 is None:
mix = self.create_node('mix', get_node_type(shader2).lower(), prefix='PBR', inputs={
'bg': shader2,
'mix': factor
})
if shader1 is not None and shader2 is not None:
shader1_type = get_node_type(shader1)
shader2_type = get_node_type(shader2)
if shader1_type != shader2_type:
log.warn(f'Types of input shaders must be the same.'
f' First shader type: {shader1_type}, second shader type: {shader2_type}')
return None
mix = self.create_node('mix', shader1_type.lower(), prefix='PBR', inputs={
'fg': shader1,
'bg': shader2,
'mix': factor
})
if not mix:
return None
result = self.create_node('surface', 'surfaceshader', prefix='PBR', inputs={
mix.nodedef.getType().lower(): mix,
'opacity': 1.0
})
log.warn(f"Known issue: node doesn't work correctly with {result.nodedef.getName()}", self.material, self.node)
return result
@ -249,20 +276,41 @@ class ShaderNodeAddShader(NodeParser):
shader1 = self.get_input_link(0)
shader2 = self.get_input_link(1)
add = None
if shader1 is None and shader2 is None:
return None
if shader1 is None:
return shader2
if shader2 is None:
return shader1
add = self.create_node('add', get_node_type(shader1).lower(), prefix='PBR', inputs={
'in1': shader1
})
result = self.create_node('STD_add', 'surfaceshader', {
'in1': shader1,
'in2': shader2
if shader1 is None:
add = self.create_node('add', get_node_type(shader2).lower(), prefix='PBR', inputs={
'in2': shader2
})
if shader1 is not None and shader2 is not None:
shader1_type = get_node_type(shader1)
shader2_type = get_node_type(shader2)
if shader1_type != shader2_type:
log.warn(f'Types of input shaders must be the same.'
f' First shader type: {shader1_type}, second shader type: {shader2_type}')
return None
add = self.create_node('add', shader1_type.lower(), prefix='PBR', inputs={
'in1': shader1,
'in2': shader2
})
if not add:
return None
result = self.create_node('surface', 'surfaceshader', prefix='PBR', inputs={
add.nodedef.getType().lower(): add,
'opacity': 1.0
})
log.warn(f"Known issue: node doesn't work correctly with {result.nodedef.getName()}", self.material, self.node)
return result

View File

@ -22,9 +22,9 @@ class ShaderNodeTexImage(NodeParser):
return image_error_result
# TODO use Vector input for UV
uv = self.create_node('texcoord', 'vector2', {})
uv = self.create_node('texcoord', 'vector2')
result = self.create_node('image', self.out_type, {
result = self.create_node('image', self.out_type, inputs={
'file': img_path,
'texcoord': uv,
})

View File

@ -23,7 +23,7 @@ class ShaderNodeNormalMap(NodeParser):
log.warn("Ignoring unsupported UV Map", space, self.node, self.material,
"No UV Map will be used")
result = self.create_node('normalmap', 'vector3', {
result = self.create_node('normalmap', 'vector3', inputs={
'in': color ,
'scale': strength,
'space': space.lower(),

View File

@ -47,17 +47,19 @@ def unregister():
unregister_sockets()
def get_mx_node_cls(mx_node):
def get_mx_node_cls(mx_node, prefix=''):
node_name = mx_node.getCategory()
suffix = f'_{node_name}'
if prefix:
suffix = prefix + suffix
classes = tuple(cls for cls in mx_node_classes if cls.__name__.endswith(suffix))
if not classes:
raise KeyError(f"Unable to find MxNode class for {mx_node}")
def params_set(node, out_type):
return {f"in_{p.getName()}:{p.getType()}" for p in node.getActiveInputs()} | \
{out_type}
{out_type.lower()}
node_params_set = params_set(mx_node, mx_node.getType())

View File

@ -314,7 +314,7 @@ def generate_basic_classes():
gen_code_dir.mkdir(exist_ok=True)
files = [
('PBR', "PBR", utils.MX_LIBS_DIR / "bxdf/standard_surface.mtlx"),
('BXDF', "PBR", utils.MX_LIBS_DIR / "bxdf/standard_surface.mtlx"),
('USD', "USD", utils.MX_LIBS_DIR / "bxdf/usd_preview_surface.mtlx"),
('STD', None, utils.MX_LIBS_DIR / "stdlib/stdlib_defs.mtlx"),
('PBR', "PBR", utils.MX_LIBS_DIR / "pbrlib/pbrlib_defs.mtlx"),