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()
nodes.register()
matlib.register()
material.register()
matlib.register()
def unregister():
log("unregister")
material.unregister()
matlib.unregister()
material.unregister()
nodes.unregister()
unregister_classes()

View File

@ -1,87 +1,38 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022, AMD
from nodeitems_utils import (
NodeCategory,
NodeItem,
register_node_categories,
unregister_node_categories,
from .. import logging
log = logging.Log("bl_nodes")
from . import (
color,
converter,
input,
output,
shader,
texture,
vector,
)
from nodeitems_builtins import (
ShaderNodeCategory,
node_parser_classes = (
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
# Copyright 2022, AMD
from ..node_parser import NodeParser
from ... import logging
log = logging.Log("bl_nodes.nodes.color")
from .node_parser import NodeParser
from . import log
class ShaderNodeInvert(NodeParser):

View File

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

View File

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

View File

@ -9,6 +9,7 @@ import MaterialX as mx
from .. import utils
from ..utils import pass_node_reroute
from ..nodes import get_mx_node_cls
from .. import logging
log = logging.Log("bl_nodes.node_parser")
@ -265,8 +266,8 @@ class NodeParser:
@staticmethod
def get_node_parser_cls(bl_idname):
""" Returns NodeParser class for node_idname or None if not found """
from . import nodes
return getattr(nodes, bl_idname, None)
from . import node_parser_classes
return next((cls for cls in node_parser_classes if cls.__name__ == bl_idname), None)
# INTERNAL FUNCTIONS
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
# Copyright 2022, AMD
from ..node_parser import NodeParser, Id
from .node_parser import NodeParser, Id
class ShaderNodeOutputMaterial(NodeParser):

View File

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

View File

@ -1,8 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022, AMD
from ..node_parser import NodeParser
from ...utils import cache_image_file
from .node_parser import NodeParser
from ..utils import cache_image_file
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
# 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'

View File

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

View File

@ -3,27 +3,12 @@
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 (
ui,
properties
)
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_duplicate_mx_node_tree,
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_unlink_mx_node_tree,
ui.MATERIAL_MT_mx_node_tree,
ui.MATERIAL_PT_material,
ui.MATERIAL_PT_material_settings_surface,
ui.MATERIAL_PT_materialx,
ui.MATERIAL_PT_materialx_surfaceshader,
ui.MATERIAL_PT_materialx_displacementshader,
ui.MATERIAL_OP_link_mx_node,
ui.MATERIAL_OP_invoke_popup_input_nodes,
ui.MATERIAL_OP_invoke_popup_shader_nodes,
ui.MATERIAL_OP_remove_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_console,
ui.MATERIAL_PT_tools,

View File

@ -7,7 +7,7 @@ import bpy
import MaterialX as mx
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 logging, get_temp_file, MaterialXProperties
@ -30,8 +30,7 @@ class MaterialProperties(MaterialXProperties):
return None
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)
def export(self, obj: bpy.types.Object) -> [mx.Document, None]:

View File

@ -8,97 +8,19 @@ import MaterialX as mx
import bpy
from bpy_extras.io_utils import ExportHelper
from . import MATERIALX_Panel, MATERIALX_ChildPanel
from ..node_tree import MxNodeTree, NODE_LAYER_SEPARATION_WIDTH
from ..nodes.node import is_mx_node_valid
from .. import utils
from ..preferences import addon_preferences
from ..utils import pass_node_reroute, title_str, mx_properties
from ..preferences import addon_preferences
from ..utils import logging
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):
"""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"
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):
"""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 = ""
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):
"""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 = ""
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):
"""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"
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):
"""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 = ""
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):
"""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 = ""
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):
bl_idname = "MATERIAL_MT_mx_node_tree"
bl_idname = utils.with_prefix('MATERIAL_MT_mx_node_tree', '_', True)
bl_label = "MX Nodetree"
def draw(self, context):
@ -189,8 +111,11 @@ class MATERIAL_MT_mx_node_tree(bpy.types.Menu):
op.mx_node_tree_name = ng.name
class MATERIAL_PT_material(MATERIALX_Panel):
bl_label = ""
class MATERIAL_PT_materialx(bpy.types.Panel):
bl_idname = utils.with_prefix("MATERIAL_PT_materialx", '_', True)
bl_label = "MaterialX"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "material"
@classmethod
@ -201,12 +126,7 @@ class MATERIAL_PT_material(MATERIALX_Panel):
mat_materialx = mx_properties(context.material)
layout = self.layout
split = layout.row(align=True).split(factor=0.4)
row = split.column()
row.alignment = 'RIGHT'
row.label(text="MaterialX")
row = split.row()
row = row.row(align=True)
row = layout.row(align=True)
row.menu(MATERIAL_MT_mx_node_tree.bl_idname, text="", icon='MATERIAL')
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_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):
"""Link MaterialX node"""
bl_idname = utils.with_prefix("material_link_mx_node")
bl_idname = utils.with_prefix('material_link_mx_node')
bl_label = ""
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):
"""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 = ""
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):
"""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 = ""
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):
"""Remove linked node"""
bl_idname = utils.with_prefix("material_remove_node")
bl_idname = utils.with_prefix('material_remove_node')
bl_label = "Remove"
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):
"""Disconnect linked node"""
bl_idname = utils.with_prefix("material_disconnect_node")
bl_idname = utils.with_prefix('material_disconnect_node')
bl_label = "Disconnect"
output_node_name: bpy.props.StringProperty()
@ -420,9 +336,13 @@ class MATERIAL_OP_disconnect_node(bpy.types.Operator):
return {'FINISHED'}
class MATERIAL_PT_material_settings_surface(MATERIALX_ChildPanel):
bl_label = "surfaceshader"
bl_parent_id = 'MATERIAL_PT_material'
class MATERIAL_PT_materialx_output(bpy.types.Panel):
bl_label = ""
bl_parent_id = MATERIAL_PT_materialx.bl_idname
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
out_key = ""
@classmethod
def poll(cls, context):
@ -437,7 +357,7 @@ class MATERIAL_PT_material_settings_surface(MATERIALX_ChildPanel):
layout.label(text="No output node")
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)
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_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)
op.input_num = output_node.inputs.find(self.out_key)
if link and is_mx_node_valid(link.from_node):
row.prop(link.from_node, 'name', text="")
@ -462,7 +382,6 @@ class MATERIAL_PT_material_settings_surface(MATERIALX_ChildPanel):
row.label(icon='BLANK1')
if not link:
layout.label(text="No input node")
return
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)
class MATERIAL_PT_material_settings_displacement(MATERIALX_ChildPanel):
bl_label = "displacementshader"
bl_parent_id = 'MATERIAL_PT_material'
class MATERIAL_PT_materialx_surfaceshader(MATERIAL_PT_materialx_output):
bl_idname = utils.with_prefix('MATERIAL_PT_materialx_surfaceshader', '_', True)
bl_label = "Surface Shader"
@classmethod
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)
out_key = 'surfaceshader'
class MATERIAL_PT_output_node(MATERIALX_ChildPanel):
bl_label = ""
bl_parent_id = 'MATERIAL_PT_material'
@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"
class MATERIAL_PT_materialx_displacementshader(MATERIAL_PT_materialx_output):
bl_idname = utils.with_prefix('MATERIAL_PT_materialx_sdisplacementshader', '_', True)
bl_label = "Displacement Shader"
bl_options = {'DEFAULT_CLOSED'}
class MATERIAL_PT_output_volume(MATERIAL_PT_output_node):
bl_label = "Volume"
bl_options = {'DEFAULT_CLOSED'}
out_key = 'displacementshader'
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_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):
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_description = "Export material as MaterialX node tree to console"
@ -672,7 +512,8 @@ class MATERIAL_OP_export_mx_console(bpy.types.Operator):
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_space_type = "NODE_EDITOR"
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')
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_parent_id = 'MATERIAL_PT_tools'
bl_parent_id = MATERIAL_PT_tools.bl_idname
bl_space_type = "NODE_EDITOR"
bl_region_type = "UI"
@classmethod
def poll(cls, context):
return addon_preferences().dev_tools
preferences = addon_preferences()
return preferences.dev_tools if preferences else True
def draw(self, context):
layout = self.layout
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):
bl_idname = utils.with_prefix("MATLIB_PT_matlib", '_', True)
bl_label = "Material Library"
bl_label = "MaterialX Library"
bl_context = "material"
bl_region_type = 'WINDOW'
bl_space_type = 'PROPERTIES'
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return context.material
def draw(self, context):
layout = self.layout
matlib_prop = utils.mx_properties(context.window_manager).matlib

View File

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

View File

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

View File

@ -37,4 +37,8 @@ class AddonPreferences(bpy.types.AddonPreferences):
def addon_preferences():
if ADDON_ALIAS not in bpy.context.preferences.addons:
return None
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_URL = "https://api.matlib.gpuopen.com/api"
TEMP_FOLDER = "bl-materialx"
SUPPORTED_FORMATS = {".png", ".jpeg", ".jpg", ".hdr", ".tga", ".bmp"}
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():
d = Path(tempfile.gettempdir()) / "blender-mx"
d = Path(tempfile.gettempdir()) / TEMP_FOLDER
if not d.is_dir():
log("Creating temp dir", d)
d.mkdir()