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.
20 changed files with 119 additions and 383 deletions
Showing only changes of commit cc589bc916 - Show all commits

View File

@ -46,14 +46,14 @@ def register():
register_classes() register_classes()
nodes.register() nodes.register()
matlib.register()
material.register() material.register()
matlib.register()
def unregister(): def unregister():
log("unregister") log("unregister")
material.unregister()
matlib.unregister() matlib.unregister()
material.unregister()
nodes.unregister() nodes.unregister()
unregister_classes() unregister_classes()

View File

@ -1,87 +1,38 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022, AMD # Copyright 2022, AMD
from nodeitems_utils import ( from .. import logging
NodeCategory, log = logging.Log("bl_nodes")
NodeItem,
register_node_categories,
unregister_node_categories, from . import (
color,
converter,
input,
output,
shader,
texture,
vector,
) )
from nodeitems_builtins import ( node_parser_classes = (
ShaderNodeCategory, output.ShaderNodeOutputMaterial,
color.ShaderNodeInvert,
color.ShaderNodeMixRGB,
converter.ShaderNodeMath,
input.ShaderNodeValue,
input.ShaderNodeRGB,
shader.ShaderNodeAddShader,
shader.ShaderNodeMixShader,
shader.ShaderNodeEmission,
shader.ShaderNodeBsdfGlass,
shader.ShaderNodeBsdfDiffuse,
shader.ShaderNodeBsdfPrincipled,
texture.ShaderNodeTexImage,
vector.ShaderNodeNormalMap,
) )
from .. import utils
class CompatibleShaderNodeCategory(NodeCategory):
""" Appear with an active USD plugin in Material shader editor only """
@classmethod
def poll(cls, context):
return context.space_data.tree_type == 'ShaderNodeTree'
# add nodes here once they are supported
node_categories = [
CompatibleShaderNodeCategory(utils.with_prefix("SHADER_NODE_CATEGORY_INPUT", '_', True), "Input", items=[
NodeItem('ShaderNodeRGB'),
NodeItem('ShaderNodeValue'),
], ),
CompatibleShaderNodeCategory(utils.with_prefix("SHADER_NODE_CATEGORY_OUTPUT", '_', True), "Output", items=[
NodeItem('ShaderNodeOutputMaterial'),
], ),
CompatibleShaderNodeCategory(utils.with_prefix("SHADER_NODE_CATEGORY_SHADERS", '_', True), "Shader", items=[
NodeItem('ShaderNodeBsdfDiffuse'),
NodeItem('ShaderNodeBsdfGlass'),
NodeItem('ShaderNodeEmission'),
NodeItem('ShaderNodeBsdfPrincipled'),
]),
CompatibleShaderNodeCategory(utils.with_prefix("SHADER_NODE_CATEGORY_TEXTURE", '_', True), "Texture", items=[
NodeItem('ShaderNodeTexImage'),
], ),
CompatibleShaderNodeCategory(utils.with_prefix("SHADER_NODE_CATEGORY_COLOR", '_', True), "Color", items=[
NodeItem('ShaderNodeInvert'),
NodeItem('ShaderNodeMixRGB'),
], ),
CompatibleShaderNodeCategory(utils.with_prefix("SHADER_NODE_CATEGORY_CONVERTER", '_', True), "Converter", items=[
NodeItem('ShaderNodeMath'),
], ),
CompatibleShaderNodeCategory(utils.with_prefix("SHADER_NODE_CATEGORY_VECTOR", '_', True), "Vector", items=[
NodeItem('ShaderNodeNormalMap'),
], ),
CompatibleShaderNodeCategory(utils.with_prefix("SHADER_NODE_CATEGORY_LAYOUT", '_', True), "Layout", items=[
NodeItem('NodeFrame'),
NodeItem('NodeReroute'),
], ),
]
# some nodes are hidden from plugins by Cycles itself(like Material Output), some we could not support.
# thus we'll hide 'em all to show only selected set of supported Blender nodes
# custom HdUSD_CompatibleShaderNodeCategory will be used instead
# def hide_cycles_and_eevee_poll(method):
# @classmethod
# def func(cls, context):
# return not context.scene.render.engine == 'HdUSD' and method(context)
# return func
old_shader_node_category_poll = None
def register():
# hide Cycles/Eevee menu
# global old_shader_node_category_poll
# old_shader_node_category_poll = ShaderNodeCategory.poll
# ShaderNodeCategory.poll = hide_cycles_and_eevee_poll(ShaderNodeCategory.poll)
# use custom menu
register_node_categories(utils.with_prefix("NODES", '_', True), node_categories)
def unregister():
# restore Cycles/Eevee menu
# if old_shader_node_category_poll and ShaderNodeCategory.poll is not old_shader_node_category_poll:
# ShaderNodeCategory.poll = old_shader_node_category_poll
# remove custom menu
unregister_node_categories(utils.with_prefix("NODES", '_', True))

View File

@ -1,10 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022, AMD # Copyright 2022, AMD
from ..node_parser import NodeParser from .node_parser import NodeParser
from . import log
from ... import logging
log = logging.Log("bl_nodes.nodes.color")
class ShaderNodeInvert(NodeParser): class ShaderNodeInvert(NodeParser):

View File

@ -1,10 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022, AMD # Copyright 2022, AMD
from ..node_parser import NodeParser from .node_parser import NodeParser
from . import log
from ... import logging
log = logging.Log("bl_nodes.nodes.converter")
class ShaderNodeMath(NodeParser): class ShaderNodeMath(NodeParser):

View File

@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022, AMD # Copyright 2022, AMD
from ..node_parser import NodeParser from .node_parser import NodeParser
class ShaderNodeValue(NodeParser): class ShaderNodeValue(NodeParser):

View File

@ -9,6 +9,7 @@ import MaterialX as mx
from .. import utils from .. import utils
from ..utils import pass_node_reroute from ..utils import pass_node_reroute
from ..nodes import get_mx_node_cls from ..nodes import get_mx_node_cls
from .. import logging from .. import logging
log = logging.Log("bl_nodes.node_parser") log = logging.Log("bl_nodes.node_parser")
@ -265,8 +266,8 @@ class NodeParser:
@staticmethod @staticmethod
def get_node_parser_cls(bl_idname): def get_node_parser_cls(bl_idname):
""" Returns NodeParser class for node_idname or None if not found """ """ Returns NodeParser class for node_idname or None if not found """
from . import nodes from . import node_parser_classes
return getattr(nodes, bl_idname, None) return next((cls for cls in node_parser_classes if cls.__name__ == bl_idname), None)
# INTERNAL FUNCTIONS # INTERNAL FUNCTIONS
def _export_node(self, node, out_key, to_socket, group_node=None): def _export_node(self, node, out_key, to_socket, group_node=None):

View File

@ -1,10 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022, AMD
from .input import *
from .output import *
from .shader import *
from .texture import *
from .color import *
from .converter import *
from .vector import *

View File

@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022, AMD # Copyright 2022, AMD
from ..node_parser import NodeParser, Id from .node_parser import NodeParser, Id
class ShaderNodeOutputMaterial(NodeParser): class ShaderNodeOutputMaterial(NodeParser):

View File

@ -3,10 +3,8 @@
import math import math
from ..node_parser import NodeParser from .node_parser import NodeParser
from . import log
from ... import logging
log = logging.Log("bl_nodes.nodes.shader")
SSS_MIN_RADIUS = 0.0001 SSS_MIN_RADIUS = 0.0001

View File

@ -1,8 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022, AMD # Copyright 2022, AMD
from ..node_parser import NodeParser from .node_parser import NodeParser
from ...utils import cache_image_file from ..utils import cache_image_file
TEXTURE_ERROR_COLOR = (1.0, 0.0, 1.0) # following Cycles color for wrong Texture nodes TEXTURE_ERROR_COLOR = (1.0, 0.0, 1.0) # following Cycles color for wrong Texture nodes

View File

@ -1,10 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022, AMD # Copyright 2022, AMD
from ..node_parser import NodeParser from .node_parser import NodeParser
from . import log
from ... import logging
log = logging.Log("bl_nodes.nodes.vector")
DEFAULT_SPACE = 'OBJECT' DEFAULT_SPACE = 'OBJECT'

View File

@ -11,7 +11,7 @@ FORMAT_STR = "%(asctime)s %(levelname)s %(name)s [%(thread)d]: %(message)s"
# root logger for the addon # root logger for the addon
logger = logging.getLogger(ADDON_ALIAS) logger = logging.getLogger(ADDON_ALIAS)
logger.setLevel('DEBUG') logger.setLevel('INFO')
# file_handler = logging.handlers.RotatingFileHandler(PLUGIN_ROOT_DIR / 'usdhydra.log', # file_handler = logging.handlers.RotatingFileHandler(PLUGIN_ROOT_DIR / 'usdhydra.log',
# mode='w', encoding='utf-8', delay=True, # mode='w', encoding='utf-8', delay=True,

View File

@ -3,27 +3,12 @@
import bpy import bpy
class MATERIALX_Panel(bpy.types.Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = 'render'
class MATERIALX_ChildPanel(bpy.types.Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_parent_id = ''
from . import ( from . import (
ui, ui,
properties properties
) )
register_classes, unregister_classes = bpy.utils.register_classes_factory([ register_classes, unregister_classes = bpy.utils.register_classes_factory([
ui.MATERIAL_PT_context,
ui.MATERIAL_PT_preview,
ui.MATERIAL_OP_new_mx_node_tree, ui.MATERIAL_OP_new_mx_node_tree,
ui.MATERIAL_OP_duplicate_mx_node_tree, ui.MATERIAL_OP_duplicate_mx_node_tree,
ui.MATERIAL_OP_convert_shader_to_mx, ui.MATERIAL_OP_convert_shader_to_mx,
@ -31,17 +16,14 @@ register_classes, unregister_classes = bpy.utils.register_classes_factory([
ui.MATERIAL_OP_link_mx_node_tree, ui.MATERIAL_OP_link_mx_node_tree,
ui.MATERIAL_OP_unlink_mx_node_tree, ui.MATERIAL_OP_unlink_mx_node_tree,
ui.MATERIAL_MT_mx_node_tree, ui.MATERIAL_MT_mx_node_tree,
ui.MATERIAL_PT_material, ui.MATERIAL_PT_materialx,
ui.MATERIAL_PT_material_settings_surface, ui.MATERIAL_PT_materialx_surfaceshader,
ui.MATERIAL_PT_materialx_displacementshader,
ui.MATERIAL_OP_link_mx_node, ui.MATERIAL_OP_link_mx_node,
ui.MATERIAL_OP_invoke_popup_input_nodes, ui.MATERIAL_OP_invoke_popup_input_nodes,
ui.MATERIAL_OP_invoke_popup_shader_nodes, ui.MATERIAL_OP_invoke_popup_shader_nodes,
ui.MATERIAL_OP_remove_node, ui.MATERIAL_OP_remove_node,
ui.MATERIAL_OP_disconnect_node, ui.MATERIAL_OP_disconnect_node,
ui.MATERIAL_PT_material_settings_displacement,
ui.MATERIAL_PT_output_surface,
ui.MATERIAL_PT_output_displacement,
ui.MATERIAL_PT_output_volume,
ui.MATERIAL_OP_export_mx_file, ui.MATERIAL_OP_export_mx_file,
ui.MATERIAL_OP_export_mx_console, ui.MATERIAL_OP_export_mx_console,
ui.MATERIAL_PT_tools, ui.MATERIAL_PT_tools,

View File

@ -7,7 +7,7 @@ import bpy
import MaterialX as mx import MaterialX as mx
from ..node_tree import MxNodeTree from ..node_tree import MxNodeTree
from ..bl_nodes.nodes import ShaderNodeOutputMaterial from ..bl_nodes.output import ShaderNodeOutputMaterial
from ..utils import MX_LIBS_DIR from ..utils import MX_LIBS_DIR
from ..utils import logging, get_temp_file, MaterialXProperties from ..utils import logging, get_temp_file, MaterialXProperties
@ -30,8 +30,7 @@ class MaterialProperties(MaterialXProperties):
return None return None
return next((node for node in material.node_tree.nodes if return next((node for node in material.node_tree.nodes if
# TODO add implementation node.bl_idname == ShaderNodeOutputMaterial.__name__ and
# node.bl_idname == ShaderNodeOutputMaterial.__name__ and
node.is_active_output), None) node.is_active_output), None)
def export(self, obj: bpy.types.Object) -> [mx.Document, None]: def export(self, obj: bpy.types.Object) -> [mx.Document, None]:

View File

@ -8,97 +8,19 @@ import MaterialX as mx
import bpy import bpy
from bpy_extras.io_utils import ExportHelper from bpy_extras.io_utils import ExportHelper
from . import MATERIALX_Panel, MATERIALX_ChildPanel
from ..node_tree import MxNodeTree, NODE_LAYER_SEPARATION_WIDTH from ..node_tree import MxNodeTree, NODE_LAYER_SEPARATION_WIDTH
from ..nodes.node import is_mx_node_valid from ..nodes.node import is_mx_node_valid
from .. import utils from .. import utils
from ..preferences import addon_preferences
from ..utils import pass_node_reroute, title_str, mx_properties from ..utils import pass_node_reroute, title_str, mx_properties
from ..preferences import addon_preferences
from ..utils import logging from ..utils import logging
log = logging.Log(tag='material.ui') log = logging.Log(tag='material.ui')
class MATERIAL_PT_context(MATERIALX_Panel):
bl_label = ""
bl_context = "material"
bl_options = {'HIDE_HEADER'}
@classmethod
def poll(cls, context):
if context.active_object and context.active_object.type == 'GPENCIL':
return False
else:
return context.material or context.object
def draw(self, context):
layout = self.layout
material = context.material
object = context.object
slot = context.material_slot
space = context.space_data
if object:
is_sortable = len(object.material_slots) > 1
rows = 1
if is_sortable:
rows = 4
row = layout.row()
row.template_list("MATERIAL_UL_matslots", "", object, "material_slots", object,
"active_material_index", rows=rows)
col = row.column(align=True)
col.operator("object.material_slot_add", icon='ADD', text="")
col.operator("object.material_slot_remove", icon='REMOVE', text="")
col.menu("MATERIAL_MT_context_menu", icon='DOWNARROW_HLT', text="")
if is_sortable:
col.separator()
col.operator("object.material_slot_move", icon='TRIA_UP', text="").direction = 'UP'
col.operator("object.material_slot_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
if object.mode == 'EDIT':
row = layout.row(align=True)
row.operator("object.material_slot_assign", text="Assign")
row.operator("object.material_slot_select", text="Select")
row.operator("object.material_slot_deselect", text="Deselect")
split = layout.split(factor=0.65)
if object:
split.template_ID(object, "active_material", new=utils.with_prefix("material_duplicate_mat_mx_node_tree"))
row = split.row()
if slot:
row.prop(slot, "link", text="")
else:
row.label()
elif material:
split.template_ID(space, "pin_id")
split.separator()
class MATERIAL_PT_preview(MATERIALX_Panel):
bl_label = "Preview"
bl_context = "material"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return context.material
def draw(self, context):
self.layout.template_preview(context.material)
class MATERIAL_OP_new_mx_node_tree(bpy.types.Operator): class MATERIAL_OP_new_mx_node_tree(bpy.types.Operator):
"""Create new MaterialX node tree for selected material""" """Create new MaterialX node tree for selected material"""
bl_idname = utils.with_prefix("material_new_mx_node_tree") bl_idname = utils.with_prefix('material_new_mx_node_tree')
bl_label = "New" bl_label = "New"
def execute(self, context): def execute(self, context):
@ -112,7 +34,7 @@ class MATERIAL_OP_new_mx_node_tree(bpy.types.Operator):
class MATERIAL_OP_duplicate_mat_mx_node_tree(bpy.types.Operator): class MATERIAL_OP_duplicate_mat_mx_node_tree(bpy.types.Operator):
"""Create duplicates of Material and MaterialX node tree for selected material""" """Create duplicates of Material and MaterialX node tree for selected material"""
bl_idname = utils.with_prefix("material_duplicate_mat_mx_node_tree") bl_idname = utils.with_prefix('material_duplicate_mat_mx_node_tree')
bl_label = "" bl_label = ""
def execute(self, context): def execute(self, context):
@ -123,7 +45,7 @@ class MATERIAL_OP_duplicate_mat_mx_node_tree(bpy.types.Operator):
class MATERIAL_OP_duplicate_mx_node_tree(bpy.types.Operator): class MATERIAL_OP_duplicate_mx_node_tree(bpy.types.Operator):
"""Create duplicate of MaterialX node tree for selected material""" """Create duplicate of MaterialX node tree for selected material"""
bl_idname = utils.with_prefix("material_duplicate_mx_node_tree") bl_idname = utils.with_prefix('material_duplicate_mx_node_tree')
bl_label = "" bl_label = ""
def execute(self, context): def execute(self, context):
@ -138,7 +60,7 @@ class MATERIAL_OP_duplicate_mx_node_tree(bpy.types.Operator):
class MATERIAL_OP_convert_shader_to_mx(bpy.types.Operator): class MATERIAL_OP_convert_shader_to_mx(bpy.types.Operator):
"""Converts standard shader node tree to MaterialX node tree for selected material""" """Converts standard shader node tree to MaterialX node tree for selected material"""
bl_idname = utils.with_prefix("material_convert_shader_to_mx") bl_idname = utils.with_prefix('material_convert_shader_to_mx')
bl_label = "Convert to MaterialX" bl_label = "Convert to MaterialX"
def execute(self, context): def execute(self, context):
@ -150,7 +72,7 @@ class MATERIAL_OP_convert_shader_to_mx(bpy.types.Operator):
class MATERIAL_OP_link_mx_node_tree(bpy.types.Operator): class MATERIAL_OP_link_mx_node_tree(bpy.types.Operator):
"""Link MaterialX node tree to selected material""" """Link MaterialX node tree to selected material"""
bl_idname = utils.with_prefix("material_link_mx_node_tree") bl_idname = utils.with_prefix('material_link_mx_node_tree')
bl_label = "" bl_label = ""
mx_node_tree_name: bpy.props.StringProperty(default="") mx_node_tree_name: bpy.props.StringProperty(default="")
@ -162,7 +84,7 @@ class MATERIAL_OP_link_mx_node_tree(bpy.types.Operator):
class MATERIAL_OP_unlink_mx_node_tree(bpy.types.Operator): class MATERIAL_OP_unlink_mx_node_tree(bpy.types.Operator):
"""Unlink MaterialX node tree from selected material""" """Unlink MaterialX node tree from selected material"""
bl_idname = utils.with_prefix("material_unlink_mx_node_tree") bl_idname = utils.with_prefix('material_unlink_mx_node_tree')
bl_label = "" bl_label = ""
def execute(self, context): def execute(self, context):
@ -171,7 +93,7 @@ class MATERIAL_OP_unlink_mx_node_tree(bpy.types.Operator):
class MATERIAL_MT_mx_node_tree(bpy.types.Menu): class MATERIAL_MT_mx_node_tree(bpy.types.Menu):
bl_idname = "MATERIAL_MT_mx_node_tree" bl_idname = utils.with_prefix('MATERIAL_MT_mx_node_tree', '_', True)
bl_label = "MX Nodetree" bl_label = "MX Nodetree"
def draw(self, context): def draw(self, context):
@ -189,8 +111,11 @@ class MATERIAL_MT_mx_node_tree(bpy.types.Menu):
op.mx_node_tree_name = ng.name op.mx_node_tree_name = ng.name
class MATERIAL_PT_material(MATERIALX_Panel): class MATERIAL_PT_materialx(bpy.types.Panel):
bl_label = "" bl_idname = utils.with_prefix("MATERIAL_PT_materialx", '_', True)
bl_label = "MaterialX"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "material" bl_context = "material"
@classmethod @classmethod
@ -201,12 +126,7 @@ class MATERIAL_PT_material(MATERIALX_Panel):
mat_materialx = mx_properties(context.material) mat_materialx = mx_properties(context.material)
layout = self.layout layout = self.layout
split = layout.row(align=True).split(factor=0.4) row = layout.row(align=True)
row = split.column()
row.alignment = 'RIGHT'
row.label(text="MaterialX")
row = split.row()
row = row.row(align=True)
row.menu(MATERIAL_MT_mx_node_tree.bl_idname, text="", icon='MATERIAL') row.menu(MATERIAL_MT_mx_node_tree.bl_idname, text="", icon='MATERIAL')
if mat_materialx.mx_node_tree: if mat_materialx.mx_node_tree:
@ -219,14 +139,10 @@ class MATERIAL_PT_material(MATERIALX_Panel):
row.operator(MATERIAL_OP_convert_shader_to_mx.bl_idname, icon='FILE_TICK', text="Convert") row.operator(MATERIAL_OP_convert_shader_to_mx.bl_idname, icon='FILE_TICK', text="Convert")
row.operator(MATERIAL_OP_new_mx_node_tree.bl_idname, icon='ADD', text="") row.operator(MATERIAL_OP_new_mx_node_tree.bl_idname, icon='ADD', text="")
def draw_header(self, context):
layout = self.layout
layout.label(text=f"Material: {context.material.name}")
class MATERIAL_OP_link_mx_node(bpy.types.Operator): class MATERIAL_OP_link_mx_node(bpy.types.Operator):
"""Link MaterialX node""" """Link MaterialX node"""
bl_idname = utils.with_prefix("material_link_mx_node") bl_idname = utils.with_prefix('material_link_mx_node')
bl_label = "" bl_label = ""
new_node_name: bpy.props.StringProperty() new_node_name: bpy.props.StringProperty()
@ -259,7 +175,7 @@ class MATERIAL_OP_link_mx_node(bpy.types.Operator):
class MATERIAL_OP_invoke_popup_input_nodes(bpy.types.Operator): class MATERIAL_OP_invoke_popup_input_nodes(bpy.types.Operator):
"""Open panel with nodes to link""" """Open panel with nodes to link"""
bl_idname = utils.with_prefix("material_invoke_popup_input_nodes") bl_idname = utils.with_prefix('material_invoke_popup_input_nodes')
bl_label = "" bl_label = ""
input_num: bpy.props.IntProperty() input_num: bpy.props.IntProperty()
@ -324,7 +240,7 @@ class MATERIAL_OP_invoke_popup_input_nodes(bpy.types.Operator):
class MATERIAL_OP_invoke_popup_shader_nodes(bpy.types.Operator): class MATERIAL_OP_invoke_popup_shader_nodes(bpy.types.Operator):
"""Open panel with shader nodes to link""" """Open panel with shader nodes to link"""
bl_idname = utils.with_prefix("material_invoke_popup_shader_nodes") bl_idname = utils.with_prefix('material_invoke_popup_shader_nodes')
bl_label = "" bl_label = ""
input_num: bpy.props.IntProperty() input_num: bpy.props.IntProperty()
@ -378,7 +294,7 @@ class MATERIAL_OP_invoke_popup_shader_nodes(bpy.types.Operator):
class MATERIAL_OP_remove_node(bpy.types.Operator): class MATERIAL_OP_remove_node(bpy.types.Operator):
"""Remove linked node""" """Remove linked node"""
bl_idname = utils.with_prefix("material_remove_node") bl_idname = utils.with_prefix('material_remove_node')
bl_label = "Remove" bl_label = "Remove"
input_node_name: bpy.props.StringProperty() input_node_name: bpy.props.StringProperty()
@ -402,7 +318,7 @@ class MATERIAL_OP_remove_node(bpy.types.Operator):
class MATERIAL_OP_disconnect_node(bpy.types.Operator): class MATERIAL_OP_disconnect_node(bpy.types.Operator):
"""Disconnect linked node""" """Disconnect linked node"""
bl_idname = utils.with_prefix("material_disconnect_node") bl_idname = utils.with_prefix('material_disconnect_node')
bl_label = "Disconnect" bl_label = "Disconnect"
output_node_name: bpy.props.StringProperty() output_node_name: bpy.props.StringProperty()
@ -420,9 +336,13 @@ class MATERIAL_OP_disconnect_node(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class MATERIAL_PT_material_settings_surface(MATERIALX_ChildPanel): class MATERIAL_PT_materialx_output(bpy.types.Panel):
bl_label = "surfaceshader" bl_label = ""
bl_parent_id = 'MATERIAL_PT_material' bl_parent_id = MATERIAL_PT_materialx.bl_idname
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
out_key = ""
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -437,7 +357,7 @@ class MATERIAL_PT_material_settings_surface(MATERIALX_ChildPanel):
layout.label(text="No output node") layout.label(text="No output node")
return return
input = output_node.inputs[self.bl_label] input = output_node.inputs[self.out_key]
link = next((link for link in input.links if link.is_valid), None) link = next((link for link in input.links if link.is_valid), None)
split = layout.split(factor=0.4) split = layout.split(factor=0.4)
@ -450,7 +370,7 @@ class MATERIAL_PT_material_settings_surface(MATERIALX_ChildPanel):
box.scale_x = 0.7 box.scale_x = 0.7
box.scale_y = 0.5 box.scale_y = 0.5
op = box.operator(MATERIAL_OP_invoke_popup_shader_nodes.bl_idname, icon='HANDLETYPE_AUTO_CLAMP_VEC') op = box.operator(MATERIAL_OP_invoke_popup_shader_nodes.bl_idname, icon='HANDLETYPE_AUTO_CLAMP_VEC')
op.input_num = output_node.inputs.find(self.bl_label) op.input_num = output_node.inputs.find(self.out_key)
if link and is_mx_node_valid(link.from_node): if link and is_mx_node_valid(link.from_node):
row.prop(link.from_node, 'name', text="") row.prop(link.from_node, 'name', text="")
@ -462,7 +382,6 @@ class MATERIAL_PT_material_settings_surface(MATERIALX_ChildPanel):
row.label(icon='BLANK1') row.label(icon='BLANK1')
if not link: if not link:
layout.label(text="No input node")
return return
if not is_mx_node_valid(link.from_node): if not is_mx_node_valid(link.from_node):
@ -477,102 +396,23 @@ class MATERIAL_PT_material_settings_surface(MATERIALX_ChildPanel):
link.from_node.draw_node_view(context, layout) link.from_node.draw_node_view(context, layout)
class MATERIAL_PT_material_settings_displacement(MATERIALX_ChildPanel): class MATERIAL_PT_materialx_surfaceshader(MATERIAL_PT_materialx_output):
bl_label = "displacementshader" bl_idname = utils.with_prefix('MATERIAL_PT_materialx_surfaceshader', '_', True)
bl_parent_id = 'MATERIAL_PT_material' bl_label = "Surface Shader"
@classmethod out_key = 'surfaceshader'
def poll(cls, context):
return bool(mx_properties(context.material).mx_node_tree)
def draw(self, context):
layout = self.layout
node_tree = mx_properties(context.material).mx_node_tree
output_node = node_tree.output_node
if not output_node:
layout.label(text="No output node")
return
input = output_node.inputs[self.bl_label]
link = next((link for link in input.links if link.is_valid), None)
split = layout.split(factor=0.4)
row = split.row(align=True)
row.alignment = 'RIGHT'
row.label(text='Displacement')
row = split.row(align=True)
box = row.box()
box.scale_x = 0.7
box.scale_y = 0.5
op = box.operator(MATERIAL_OP_invoke_popup_shader_nodes.bl_idname, icon='HANDLETYPE_AUTO_CLAMP_VEC')
op.input_num = output_node.inputs.find(self.bl_label)
if link and is_mx_node_valid(link.from_node):
row.prop(link.from_node, 'name', text="")
else:
box = row.box()
box.scale_y = 0.5
box.label(text='None')
row.label(icon='BLANK1')
if not link:
layout.label(text="No input node")
return
if not is_mx_node_valid(link.from_node):
layout.label(text="Unsupported node")
return
link = pass_node_reroute(link)
if not link:
return
layout.separator()
link.from_node.draw_node_view(context, layout)
class MATERIAL_PT_output_node(MATERIALX_ChildPanel): class MATERIAL_PT_materialx_displacementshader(MATERIAL_PT_materialx_output):
bl_label = "" bl_idname = utils.with_prefix('MATERIAL_PT_materialx_sdisplacementshader', '_', True)
bl_parent_id = 'MATERIAL_PT_material' bl_label = "Displacement Shader"
@classmethod
def poll(cls, context):
return not bool(mx_properties(context.material).mx_node_tree)
def draw(self, context):
layout = self.layout
node_tree = context.material.node_tree
output_node = mx_properties(context.material).output_node
if not output_node:
layout.label(text="No output node")
return
input = output_node.inputs[self.bl_label]
layout.template_node_view(node_tree, output_node, input)
class MATERIAL_PT_output_surface(MATERIAL_PT_output_node):
bl_label = "Surface"
class MATERIAL_PT_output_displacement(MATERIAL_PT_output_node):
bl_label = "Displacement"
bl_options = {'DEFAULT_CLOSED'} bl_options = {'DEFAULT_CLOSED'}
out_key = 'displacementshader'
class MATERIAL_PT_output_volume(MATERIAL_PT_output_node):
bl_label = "Volume"
bl_options = {'DEFAULT_CLOSED'}
class MATERIAL_OP_export_mx_file(bpy.types.Operator, ExportHelper): class MATERIAL_OP_export_mx_file(bpy.types.Operator, ExportHelper):
bl_idname = utils.with_prefix("material_export_mx_file") bl_idname = utils.with_prefix('material_export_mx_file')
bl_label = "Export MaterialX" bl_label = "Export MaterialX"
bl_description = "Export material as MaterialX node tree to .mtlx file" bl_description = "Export material as MaterialX node tree to .mtlx file"
@ -659,7 +499,7 @@ class MATERIAL_OP_export_mx_file(bpy.types.Operator, ExportHelper):
class MATERIAL_OP_export_mx_console(bpy.types.Operator): class MATERIAL_OP_export_mx_console(bpy.types.Operator):
bl_idname = utils.with_prefix("material_export_mx_console") bl_idname = utils.with_prefix('material_export_mx_console')
bl_label = "Export MaterialX to Console" bl_label = "Export MaterialX to Console"
bl_description = "Export material as MaterialX node tree to console" bl_description = "Export material as MaterialX node tree to console"
@ -672,7 +512,8 @@ class MATERIAL_OP_export_mx_console(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class MATERIAL_PT_tools(MATERIALX_Panel): class MATERIAL_PT_tools(bpy.types.Panel):
bl_idname = utils.with_prefix('MATERIAL_PT_tools', '_', True)
bl_label = "MaterialX Tools" bl_label = "MaterialX Tools"
bl_space_type = "NODE_EDITOR" bl_space_type = "NODE_EDITOR"
bl_region_type = "UI" bl_region_type = "UI"
@ -691,50 +532,18 @@ class MATERIAL_PT_tools(MATERIALX_Panel):
layout.operator(MATERIAL_OP_export_mx_file.bl_idname, text="Export MaterialX to file", icon='EXPORT') layout.operator(MATERIAL_OP_export_mx_file.bl_idname, text="Export MaterialX to file", icon='EXPORT')
class MATERIAL_PT_dev(MATERIALX_ChildPanel): class MATERIAL_PT_dev(bpy.types.Panel):
bl_idname = utils.with_prefix('MATERIAL_PT_dev', '_', True)
bl_label = "Dev" bl_label = "Dev"
bl_parent_id = 'MATERIAL_PT_tools' bl_parent_id = MATERIAL_PT_tools.bl_idname
bl_space_type = "NODE_EDITOR" bl_space_type = "NODE_EDITOR"
bl_region_type = "UI" bl_region_type = "UI"
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return addon_preferences().dev_tools preferences = addon_preferences()
return preferences.dev_tools if preferences else True
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.operator(MATERIAL_OP_export_mx_console.bl_idname) layout.operator(MATERIAL_OP_export_mx_console.bl_idname)
def depsgraph_update(depsgraph):
context = bpy.context
mx_node_tree = None
if hasattr(context, 'object') and context.object and context.object.active_material:
mx_node_tree = mx_properties(context.object.active_material).mx_node_tree
# trying to show MaterialX area with node tree or Shader area
screen = context.screen
if not hasattr(screen, 'areas'):
return
for window in context.window_manager.windows:
for area in window.screen.areas:
if not mx_node_tree:
if area.ui_type != utils.with_prefix('MxNodeTree'):
continue
area.ui_type = 'ShaderNodeTree'
continue
if area.ui_type not in (utils.with_prefix('MxNodeTree'), 'ShaderNodeTree'):
continue
space = next(s for s in area.spaces if s.type == 'NODE_EDITOR')
if space.pin or space.shader_type != 'OBJECT':
continue
area.ui_type = utils.with_prefix('MxNodeTree')
space.node_tree = mx_node_tree
mx_node_tree.update_links()

View File

@ -84,12 +84,16 @@ class MATLIB_OP_load_package(bpy.types.Operator):
class MATLIB_PT_matlib(bpy.types.Panel): class MATLIB_PT_matlib(bpy.types.Panel):
bl_idname = utils.with_prefix("MATLIB_PT_matlib", '_', True) bl_idname = utils.with_prefix("MATLIB_PT_matlib", '_', True)
bl_label = "Material Library" bl_label = "MaterialX Library"
bl_context = "material" bl_context = "material"
bl_region_type = 'WINDOW' bl_region_type = 'WINDOW'
bl_space_type = 'PROPERTIES' bl_space_type = 'PROPERTIES'
bl_options = {'DEFAULT_CLOSED'} bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return context.material
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
matlib_prop = utils.mx_properties(context.window_manager).matlib matlib_prop = utils.mx_properties(context.window_manager).matlib

View File

@ -253,10 +253,10 @@ class MxNodeTree(bpy.types.ShaderNodeTree):
def update_(self): def update_(self):
self.update_links() self.update_links()
# TODO: Uncomment for material in bpy.data.materials:
# for material in bpy.data.materials: if utils.mx_properties(material).mx_node_tree and \
# if material.hdusd.mx_node_tree and material.hdusd.mx_node_tree.name == self.name: utils.mx_properties(material).mx_node_tree.name == self.name:
# material.hdusd.update() utils.mx_properties(material).update()
for window in bpy.context.window_manager.windows: for window in bpy.context.window_manager.windows:
for area in window.screen.areas: for area in window.screen.areas:

View File

@ -195,7 +195,8 @@ class NODES_PT_dev(bpy.types.Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return addon_preferences().dev_tools preferences = addon_preferences()
return preferences.dev_tools if preferences else True
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout

View File

@ -37,4 +37,8 @@ class AddonPreferences(bpy.types.AddonPreferences):
def addon_preferences(): def addon_preferences():
if ADDON_ALIAS not in bpy.context.preferences.addons:
return None
return bpy.context.preferences.addons[ADDON_ALIAS].preferences return bpy.context.preferences.addons[ADDON_ALIAS].preferences

View File

@ -28,6 +28,8 @@ MATLIB_FOLDER = "matlib"
MATLIB_DIR = ADDON_DATA_DIR / MATLIB_FOLDER MATLIB_DIR = ADDON_DATA_DIR / MATLIB_FOLDER
MATLIB_URL = "https://api.matlib.gpuopen.com/api" MATLIB_URL = "https://api.matlib.gpuopen.com/api"
TEMP_FOLDER = "bl-materialx"
SUPPORTED_FORMATS = {".png", ".jpeg", ".jpg", ".hdr", ".tga", ".bmp"} SUPPORTED_FORMATS = {".png", ".jpeg", ".jpg", ".hdr", ".tga", ".bmp"}
DEFAULT_FORMAT = ".hdr" DEFAULT_FORMAT = ".hdr"
BLENDER_DEFAULT_FORMAT = "HDR" BLENDER_DEFAULT_FORMAT = "HDR"
@ -331,7 +333,7 @@ def export_mx_to_file(doc, filepath, *, mx_node_tree=None, is_export_deps=False,
def temp_dir(): def temp_dir():
d = Path(tempfile.gettempdir()) / "blender-mx" d = Path(tempfile.gettempdir()) / TEMP_FOLDER
if not d.is_dir(): if not d.is_dir():
log("Creating temp dir", d) log("Creating temp dir", d)
d.mkdir() d.mkdir()