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: class NodeItem:
"""This class is a wrapper used for doing operations on MaterialX nodes, floats, and tuples""" """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.id = id
self.nodegraph = ng self.nodegraph = ng
self.data = data self.data = data
self.nodedef = None self.nodedef = None
if isinstance(data, mx.Node): 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) self.nodedef = MxNode_cls.get_nodedef(self.type)
def node_item(self, value): def node_item(self, value):
@ -370,10 +370,14 @@ class NodeParser:
return self.get_input_default(in_key) 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) 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 = 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: if inputs:
node_item.set_inputs(inputs) node_item.set_inputs(inputs)

View File

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

View File

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

View File

@ -22,9 +22,9 @@ class ShaderNodeTexImage(NodeParser):
return image_error_result return image_error_result
# TODO use Vector input for UV # 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, 'file': img_path,
'texcoord': uv, 'texcoord': uv,
}) })

View File

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

View File

@ -47,17 +47,19 @@ def unregister():
unregister_sockets() unregister_sockets()
def get_mx_node_cls(mx_node): def get_mx_node_cls(mx_node, prefix=''):
node_name = mx_node.getCategory() node_name = mx_node.getCategory()
suffix = f'_{node_name}' suffix = f'_{node_name}'
if prefix:
suffix = prefix + suffix
classes = tuple(cls for cls in mx_node_classes if cls.__name__.endswith(suffix)) classes = tuple(cls for cls in mx_node_classes if cls.__name__.endswith(suffix))
if not classes: if not classes:
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.getActiveInputs()} | \ 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()) 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) gen_code_dir.mkdir(exist_ok=True)
files = [ 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"), ('USD', "USD", utils.MX_LIBS_DIR / "bxdf/usd_preview_surface.mtlx"),
('STD', None, utils.MX_LIBS_DIR / "stdlib/stdlib_defs.mtlx"), ('STD', None, utils.MX_LIBS_DIR / "stdlib/stdlib_defs.mtlx"),
('PBR', "PBR", utils.MX_LIBS_DIR / "pbrlib/pbrlib_defs.mtlx"), ('PBR', "PBR", utils.MX_LIBS_DIR / "pbrlib/pbrlib_defs.mtlx"),