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.
5 changed files with 967 additions and 11 deletions
Showing only changes of commit 23bc2562fc - Show all commits

View File

@ -23,13 +23,16 @@ ADDON_ALIAS = "materialx"
import bpy import bpy
from . import preferences from . import (
from . import node_tree preferences,
from . import nodes node_tree,
from . import matlib nodes,
matlib,
material,
)
from . import logging from . import logging
log = logging.Log("") log = logging.Log("__init__")
def register(): def register():
@ -38,11 +41,13 @@ def register():
bpy.utils.register_class(node_tree.MxNodeTree) bpy.utils.register_class(node_tree.MxNodeTree)
nodes.register() nodes.register()
matlib.register() matlib.register()
material.register()
def unregister(): def unregister():
log("unregister") log("unregister")
matlib.unregister() matlib.unregister()
nodes.unregister() nodes.unregister()
material.unregister()
bpy.utils.unregister_class(node_tree.MxNodeTree) bpy.utils.unregister_class(node_tree.MxNodeTree)
bpy.utils.unregister_class(preferences.AddonPreferences) bpy.utils.unregister_class(preferences.AddonPreferences)

View File

@ -0,0 +1,61 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022, AMD
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,
ui.MATERIAL_OP_duplicate_mat_mx_node_tree,
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_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,
ui.MATERIAL_PT_dev,
])
def register():
ui.register()
properties.register()
register_classes()
def unregister():
ui.unregister()
properties.unregister()
unregister_classes()

View File

@ -0,0 +1,119 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022, AMD
import traceback
import bpy
import MaterialX as mx
from ..node_tree import MxNodeTree
from ..utils import MX_LIBS_DIR
from ..utils import logging, get_temp_file, MaterialXProperties
log = logging.Log('material.properties')
class MaterialProperties(MaterialXProperties):
bl_type = bpy.types.Material
def update_mx_node_tree(self, context):
self.update()
mx_node_tree: bpy.props.PointerProperty(type=MxNodeTree, update=update_mx_node_tree)
@property
def output_node(self):
material = self.id_data
if not material.node_tree:
return None
return next((node for node in material.node_tree.nodes if
# TODO add implementation
# node.bl_idname == ShaderNodeOutputMaterial.__name__ and
node.is_active_output), None)
def export(self, obj: bpy.types.Object) -> [mx.Document, None]:
if self.mx_node_tree:
return self.mx_node_tree.export()
material = self.id_data
output_node = self.output_node
if not output_node:
return None
doc = mx.createDocument()
# TODO add implementation
# node_parser = ShaderNodeOutputMaterial(doc, material, output_node, obj)
# if not node_parser.export():
# return None
return doc
def update(self, is_depsgraph=False):
"""
Main update callback function, which notifies that material was updated from both:
depsgraph or MaterialX node tree
"""
if is_depsgraph and self.mx_node_tree:
return
material = self.id_data
# usd_node_tree.material_update(material)
# ViewportEngineScene.material_update(material)
def convert_shader_to_mx(self, obj: bpy.types.Object = None):
mat = self.id_data
output_node = self.output_node
if not output_node:
log.warn("Incorrect node tree to export: output node doesn't exist")
return False
mx_node_tree = bpy.data.node_groups.new(f"MX_{mat.name}", type=MxNodeTree.bl_idname)
if obj:
doc = self.export(obj)
else:
doc = mx.createDocument()
# TODO add implementation
# node_parser = ShaderNodeOutputMaterial(doc, mat, output_node, obj)
# if not node_parser.export():
# return False
if not doc:
log.warn("Incorrect node tree to export", mx_node_tree)
return False
mtlx_file = get_temp_file(".mtlx",
f'{mat.name}_{self.mx_node_tree.name if self.mx_node_tree else ""}')
mx.writeToXmlFile(doc, str(mtlx_file))
search_path = mx.FileSearchPath(str(mtlx_file.parent))
search_path.append(str(MX_LIBS_DIR))
try:
mx.readFromXmlFile(doc, str(mtlx_file), searchPath=search_path)
mx_node_tree.import_(doc, mtlx_file)
self.mx_node_tree = mx_node_tree
except Exception as e:
log.error(traceback.format_exc(), mtlx_file)
return False
return True
def depsgraph_update(depsgraph):
if not depsgraph.updates:
return
# Undo operation sends modified object with other stuff (scene, collection, etc...)
mat = next((upd.id for upd in depsgraph.updates if isinstance(upd.id, bpy.types.Material)), None)
if mat:
mat.hdusd.update(True)
register, unregister = bpy.utils.register_classes_factory((
MaterialProperties,
))

774
materialx/material/ui.py Normal file
View File

@ -0,0 +1,774 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022, AMD
from pathlib import Path
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 ..utils import pass_node_reroute, title_str, mx_properties
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_label = "New"
def execute(self, context):
mat = context.material
mx_node_tree = bpy.data.node_groups.new(f"MX_{mat.name}", type=MxNodeTree.bl_idname)
mx_node_tree.create_basic_nodes()
mx_properties(mat).mx_node_tree = mx_node_tree
return {"FINISHED"}
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_label = ""
def execute(self, context):
bpy.ops.material.new()
bpy.ops.materialx.material_duplicate_mx_node_tree()
return {"FINISHED"}
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_label = ""
def execute(self, context):
mat = context.object.active_material
mx_node_tree = mx_properties(mat).mx_node_tree
if mx_node_tree:
mx_properties(mat).mx_node_tree = mx_node_tree.copy()
return {"FINISHED"}
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_label = "Convert to MaterialX"
def execute(self, context):
if not mx_properties(context.material).convert_shader_to_mx(context.object):
return {'CANCELLED'}
return {"FINISHED"}
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_label = ""
mx_node_tree_name: bpy.props.StringProperty(default="")
def execute(self, context):
mx_properties(context.material).mx_node_tree = bpy.data.node_groups[self.mx_node_tree_name]
return {"FINISHED"}
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_label = ""
def execute(self, context):
mx_properties(context.material).mx_node_tree = None
return {"FINISHED"}
class MATERIAL_MT_mx_node_tree(bpy.types.Menu):
bl_idname = "MATERIAL_MT_mx_node_tree"
bl_label = "MX Nodetree"
def draw(self, context):
layout = self.layout
node_groups = bpy.data.node_groups
for ng in node_groups:
if ng.bl_idname != utils.with_prefix('MxNodeTree'):
continue
row = layout.row()
row.enabled = bool(ng.output_node)
op = row.operator(MATERIAL_OP_link_mx_node_tree.bl_idname,
text=ng.name, icon='MATERIAL')
op.mx_node_tree_name = ng.name
class MATERIAL_PT_material(MATERIALX_Panel):
bl_label = ""
bl_context = "material"
@classmethod
def poll(cls, context):
return context.material
def draw(self, context):
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.menu(MATERIAL_MT_mx_node_tree.bl_idname, text="", icon='MATERIAL')
if mat_materialx.mx_node_tree:
row.prop(mat_materialx.mx_node_tree, 'name', text="")
row.operator(MATERIAL_OP_convert_shader_to_mx.bl_idname, icon='FILE_TICK', text="")
row.operator(MATERIAL_OP_duplicate_mx_node_tree.bl_idname, icon='DUPLICATE')
row.operator(MATERIAL_OP_unlink_mx_node_tree.bl_idname, icon='X')
else:
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_label = ""
new_node_name: bpy.props.StringProperty()
input_num: bpy.props.IntProperty()
current_node_name: bpy.props.StringProperty()
def execute(self, context):
layout = self.layout
node_tree = mx_properties(context.material).mx_node_tree
current_node = mx_properties(context.material).mx_node_tree.nodes[self.current_node_name]
input = current_node.inputs[self.input_num]
link = next((link for link in input.links), None) if input.is_linked else None
linked_node_name = link.from_node.bl_idname if link else None
if linked_node_name:
if linked_node_name != self.new_node_name:
bpy.ops.materialx.material_remove_node(input_node_name=link.from_node.name)
else:
return {"FINISHED"}
new_node = node_tree.nodes.new(self.new_node_name)
new_node.location = (current_node.location[0] - NODE_LAYER_SEPARATION_WIDTH,
current_node.location[1])
node_tree.links.new(new_node.outputs[0], current_node.inputs[self.input_num])
return {"FINISHED"}
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_label = ""
input_num: bpy.props.IntProperty()
current_node_name: bpy.props.StringProperty()
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_popup(self, width=600)
def draw(self, context):
from ..nodes import mx_node_classes
MAX_COLUMN_ITEMS = 34
split = self.layout.split()
cat = ""
i = 0
col = None
for cls in mx_node_classes:
if cls.category in ("PBR", "material"):
continue
if not col or i >= MAX_COLUMN_ITEMS:
i = 0
col = split.column()
col.emboss = 'PULLDOWN_MENU'
if cat != cls.category:
cat = cls.category
col.label(text=title_str(cat), icon='NODE')
i += 1
row = col.row()
row.alignment = 'LEFT'
op = row.operator(MATERIAL_OP_link_mx_node.bl_idname, text=cls.bl_label)
op.new_node_name = cls.bl_idname
op.input_num = self.input_num
op.current_node_name = self.current_node_name
i += 1
input = mx_properties(context.material).mx_node_tree.nodes[self.current_node_name].inputs[self.input_num]
if input.is_linked:
link = input.links[0]
col = split.column()
col.emboss = 'PULLDOWN_MENU'
col.label(text="Link")
row = col.row()
row.alignment = 'LEFT'
op = row.operator(MATERIAL_OP_remove_node.bl_idname)
op.input_node_name = link.from_node.name
row = col.row()
row.alignment = 'LEFT'
op = row.operator(MATERIAL_OP_disconnect_node.bl_idname)
op.output_node_name = link.to_node.name
op.input_num = self.input_num
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_label = ""
input_num: bpy.props.IntProperty()
new_node_name: bpy.props.StringProperty()
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_popup(self, width=300)
def draw(self, context):
from ..nodes import mx_node_classes
split = self.layout.split()
col = split.column()
col.emboss = 'PULLDOWN_MENU'
col.label(text="PBR", icon='NODE')
output_node = mx_properties(context.material).mx_node_tree.output_node
for cls in mx_node_classes:
if cls.category != "PBR":
continue
row = col.row()
row.alignment = 'LEFT'
op = row.operator(MATERIAL_OP_link_mx_node.bl_idname, text=cls.bl_label)
op.new_node_name = cls.bl_idname
op.input_num = self.input_num
op.current_node_name = output_node.name
input = output_node.inputs[self.input_num]
if input.is_linked:
link = input.links[0]
col = split.column()
col.emboss = 'PULLDOWN_MENU'
col.label(text="Link")
row = col.row()
row.alignment = 'LEFT'
op = row.operator(MATERIAL_OP_remove_node.bl_idname)
op.input_node_name = link.from_node.name
row = col.row()
row.alignment = 'LEFT'
op = row.operator(MATERIAL_OP_disconnect_node.bl_idname)
op.output_node_name = link.to_node.name
op.input_num = self.input_num
class MATERIAL_OP_remove_node(bpy.types.Operator):
"""Remove linked node"""
bl_idname = utils.with_prefix("material_remove_node")
bl_label = "Remove"
input_node_name: bpy.props.StringProperty()
def remove_nodes(self, context, node):
for input in node.inputs:
if input.is_linked:
for link in input.links:
self.remove_nodes(context, link.from_node)
mx_properties(context.material).mx_node_tree.nodes.remove(node)
def execute(self, context):
node_tree = mx_properties(context.material).mx_node_tree
input_node = node_tree.nodes[self.input_node_name]
self.remove_nodes(context, input_node)
return {'FINISHED'}
class MATERIAL_OP_disconnect_node(bpy.types.Operator):
"""Disconnect linked node"""
bl_idname = utils.with_prefix("material_disconnect_node")
bl_label = "Disconnect"
output_node_name: bpy.props.StringProperty()
input_num: bpy.props.IntProperty()
def execute(self, context):
node_tree = mx_properties(context.material).mx_node_tree
output_node = node_tree.nodes[self.output_node_name]
links = output_node.inputs[self.input_num].links
link = next((link for link in links), None)
if link:
node_tree.links.remove(link)
return {'FINISHED'}
class MATERIAL_PT_material_settings_surface(MATERIALX_ChildPanel):
bl_label = "surfaceshader"
bl_parent_id = 'MATERIAL_PT_material'
@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='Surface')
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_material_settings_displacement(MATERIALX_ChildPanel):
bl_label = "displacementshader"
bl_parent_id = 'MATERIAL_PT_material'
@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)
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"
bl_options = {'DEFAULT_CLOSED'}
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):
bl_idname = utils.with_prefix("material_export_mx_file")
bl_label = "Export MaterialX"
bl_description = "Export material as MaterialX node tree to .mtlx file"
# region properties
filename_ext = ".mtlx"
filepath: bpy.props.StringProperty(
name="File Path",
description="File path used for exporting material as MaterialX node tree to .mtlx file",
maxlen=1024,
subtype="FILE_PATH"
)
filter_glob: bpy.props.StringProperty(
default="*.mtlx",
options={'HIDDEN'},
)
is_export_deps: bpy.props.BoolProperty(
name="Include dependencies",
description="Export used MaterialX dependencies",
default=False
)
is_export_textures: bpy.props.BoolProperty(
name="Export bound textures",
description="Export bound textures to corresponded folder",
default=True
)
is_clean_texture_folder: bpy.props.BoolProperty(
name="Сlean texture folder",
description="Сlean texture folder before export",
default=False
)
is_clean_deps_folders: bpy.props.BoolProperty(
name="Сlean dependencies folders",
description="Сlean MaterialX dependencies folders before export",
default=False
)
texture_dir_name: bpy.props.StringProperty(
name="Folder name",
description="Texture folder name used for exporting files",
default='textures',
maxlen=1024,
)
is_create_new_folder: bpy.props.BoolProperty(
name="Create new folder",
description="Create new folder for material",
default=True
)
# endregion
def execute(self, context):
materialx_prop = mx_properties(context.material)
if not materialx_prop.convert_shader_to_mx():
return {'CANCELLED'}
doc = mx_properties(context.material).export(None)
if not doc:
return {'CANCELLED'}
if self.is_create_new_folder:
self.filepath = str(Path(self.filepath).parent / context.material.name_full / Path(self.filepath).name)
utils.export_mx_to_file(doc, self.filepath,
mx_node_tree=materialx_prop.mx_node_tree,
is_export_deps=self.is_export_deps,
is_export_textures=self.is_export_textures,
texture_dir_name=self.texture_dir_name,
is_clean_texture_folder=self.is_clean_texture_folder,
is_clean_deps_folders=self.is_clean_deps_folders)
bpy.data.node_groups.remove(materialx_prop.mx_node_tree)
return {'FINISHED'}
def draw(self, context):
self.layout.prop(self, 'is_create_new_folder')
self.layout.prop(self, 'is_export_deps')
col = self.layout.column(align=False)
col.prop(self, 'is_export_textures')
row = col.row()
row.enabled = self.is_export_textures
row.prop(self, 'texture_dir_name', text='')
class MATERIAL_OP_export_mx_console(bpy.types.Operator):
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"
def execute(self, context):
doc = mx_properties(context.material).export(context.object)
if not doc:
return {'CANCELLED'}
print(mx.writeToXmlString(doc))
return {'FINISHED'}
class MATERIAL_PT_tools(MATERIALX_Panel):
bl_label = "MaterialX Tools"
bl_space_type = "NODE_EDITOR"
bl_region_type = "UI"
bl_category = "Tool"
@classmethod
def poll(cls, context):
tree = context.space_data.edit_tree
return tree and tree.bl_idname == bpy.types.ShaderNodeTree.__name__
def draw(self, context):
layout = self.layout
layout.operator(MATERIAL_OP_convert_shader_to_mx.bl_idname, icon='FILE_TICK')
layout.operator(MATERIAL_OP_export_mx_file.bl_idname, text="Export MaterialX to file", icon='EXPORT')
class MATERIAL_PT_dev(MATERIALX_ChildPanel):
bl_label = "Dev"
bl_parent_id = 'MATERIAL_PT_tools'
bl_space_type = "NODE_EDITOR"
bl_region_type = "UI"
@classmethod
def poll(cls, context):
return config.show_dev_settings
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
bpy.types.NODE_HT_header.remove(update_material_ui)
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()
bpy.types.NODE_HT_header.append(update_material_ui)
# update for material ui according to MaterialX nodetree header changes
def update_material_ui(self, context):
obj = context.active_object
if not obj:
return
mat = obj.active_material
if not mat:
return
space = context.space_data
if space.tree_type != utils.with_prefix('MxNodeTree'):
return
ui_mx_node_tree = mx_properties(mat).mx_node_tree
editor_node_tree = space.node_tree
if editor_node_tree != ui_mx_node_tree and not space.pin and editor_node_tree:
mx_properties(mat).mx_node_tree = editor_node_tree
def register():
# set update for material ui according to MaterialX nodetree header changes
bpy.types.NODE_HT_header.append(update_material_ui)
def unregister():
# remove update for material ui according to MaterialX nodetree header changes
bpy.types.NODE_HT_header.remove(update_material_ui)

View File

@ -186,10 +186,7 @@ class MxNode(bpy.types.ShaderNode):
layout1.prop(self, self._input_prop_name(name)) layout1.prop(self, self._input_prop_name(name))
def draw_node_view(self, context, layout): def draw_node_view(self, context, layout):
return from ..material.ui import MATERIAL_OP_invoke_popup_input_nodes
# TODO: enable implementation
from ...ui.material import USDHYDRA_MATERIAL_OP_invoke_popup_input_nodes
layout.use_property_split = True layout.use_property_split = True
layout.use_property_decorate = True layout.use_property_decorate = True
self.draw_buttons(context, layout) self.draw_buttons(context, layout)
@ -228,7 +225,7 @@ class MxNode(bpy.types.ShaderNode):
box.scale_y = 0.5 box.scale_y = 0.5
box.emboss = 'NONE_OR_STATUS' box.emboss = 'NONE_OR_STATUS'
op = box.operator(USDHYDRA_MATERIAL_OP_invoke_popup_input_nodes.bl_idname, icon='HANDLETYPE_AUTO_CLAMP_VEC') op = box.operator(MATERIAL_OP_invoke_popup_input_nodes.bl_idname, icon='HANDLETYPE_AUTO_CLAMP_VEC')
op.input_num = i op.input_num = i
op.current_node_name = self.name op.current_node_name = self.name
@ -257,7 +254,7 @@ class MxNode(bpy.types.ShaderNode):
box.scale_x = 0.7 box.scale_x = 0.7
box.scale_y = 0.5 box.scale_y = 0.5
op = box.operator(USDHYDRA_MATERIAL_OP_invoke_popup_input_nodes.bl_idname, op = box.operator(MATERIAL_OP_invoke_popup_input_nodes.bl_idname,
icon='HANDLETYPE_AUTO_CLAMP_VEC') icon='HANDLETYPE_AUTO_CLAMP_VEC')
op.input_num = i op.input_num = i
op.current_node_name = self.name op.current_node_name = self.name