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.
2 changed files with 57 additions and 84 deletions
Showing only changes of commit 641fdcd5d4 - Show all commits

View File

@ -10,7 +10,6 @@ import bpy
from bpy_extras.io_utils import ImportHelper, ExportHelper from bpy_extras.io_utils import ImportHelper, ExportHelper
from . import utils from . import utils
from .utils import import_materialx_from_file, export
from .preferences import addon_preferences from .preferences import addon_preferences
from .utils import logging from .utils import logging
@ -39,7 +38,7 @@ class MATERIALX_OP_import_file(bpy.types.Operator, ImportHelper):
search_path.append(str(utils.MX_LIBS_DIR)) search_path.append(str(utils.MX_LIBS_DIR))
try: try:
mx.readFromXmlFile(doc, str(mtlx_file)) mx.readFromXmlFile(doc, str(mtlx_file))
import_materialx_from_file(mx_node_tree, doc, mtlx_file) utils.import_materialx_from_file(mx_node_tree, doc, mtlx_file)
except Exception as e: except Exception as e:
log.error(traceback.format_exc(), mtlx_file) log.error(traceback.format_exc(), mtlx_file)
@ -53,7 +52,6 @@ class MATERIALX_OP_export_file(bpy.types.Operator, ExportHelper):
bl_label = "Export to File" bl_label = "Export to File"
bl_description = "Export material as MaterialX node tree to .mtlx file" bl_description = "Export material as MaterialX node tree to .mtlx file"
# region properties
filename_ext = ".mtlx" filename_ext = ".mtlx"
filepath: bpy.props.StringProperty( filepath: bpy.props.StringProperty(
@ -66,52 +64,47 @@ class MATERIALX_OP_export_file(bpy.types.Operator, ExportHelper):
default="*.mtlx", default="*.mtlx",
options={'HIDDEN'}, options={'HIDDEN'},
) )
is_export_deps: bpy.props.BoolProperty( export_textures: bpy.props.BoolProperty(
name="Include dependencies", name="Export Textures",
description="Export used MaterialX dependencies",
default=False
)
is_export_textures: bpy.props.BoolProperty(
name="Export textures",
description="Export bound textures to corresponded folder", description="Export bound textures to corresponded folder",
default=True default=True
) )
is_clean_texture_folder: bpy.props.BoolProperty(
name="Сlean texture folder",
description="Сlean texture folder before export",
default=False
)
texture_dir_name: bpy.props.StringProperty( texture_dir_name: bpy.props.StringProperty(
name="Folder name", name="Folder Name",
description="Texture folder name used for exporting files", description="Texture folder name used for exporting files",
default='textures', default='textures',
maxlen=1024, maxlen=1024,
) )
# endregion export_deps: bpy.props.BoolProperty(
name="Export Dependencies",
description="Export MaterialX library dependencies",
default=True
)
def execute(self, context): def execute(self, context):
doc = export(context.material, None) doc = utils.export(context.material, None)
if not doc: if not doc:
return {'CANCELLED'} return {'CANCELLED'}
utils.export_mx_to_file(doc, self.filepath, utils.export_to_file(doc, self.filepath,
mx_node_tree=None, export_textures=self.export_textures,
# is_export_deps=self.is_export_deps, texture_dir_name=self.texture_dir_name,
is_export_textures=self.is_export_textures, export_deps=self.export_deps,
texture_dir_name=self.texture_dir_name) copy_deps=self.export_deps)
log.info(f"Succesfully exported material '{context.material.name}' into {self.filepath}")
return {'FINISHED'} return {'FINISHED'}
def draw(self, context): def draw(self, context):
# self.layout.prop(self, 'is_export_deps')
col = self.layout.column(align=False) col = self.layout.column(align=False)
col.prop(self, 'is_export_textures') col.prop(self, 'export_textures')
row = col.row() row = col.row()
row.enabled = self.is_export_textures row.enabled = self.export_textures
row.prop(self, 'texture_dir_name', text='') row.prop(self, 'texture_dir_name', text='')
self.layout.prop(self, 'export_deps')
class MATERIALX_OP_export_console(bpy.types.Operator): class MATERIALX_OP_export_console(bpy.types.Operator):
bl_idname = utils.with_prefix('materialx_export_console') bl_idname = utils.with_prefix('materialx_export_console')
@ -119,7 +112,7 @@ class MATERIALX_OP_export_console(bpy.types.Operator):
bl_description = "Export material as MaterialX node tree to console" bl_description = "Export material as MaterialX node tree to console"
def execute(self, context): def execute(self, context):
doc = export(context.material, context.object) doc = utils.export(context.material, context.object)
if not doc: if not doc:
return {'CANCELLED'} return {'CANCELLED'}

View File

@ -25,10 +25,6 @@ MX_ADDON_LIBS_DIR = ADDON_ROOT_DIR / MX_LIBS_FOLDER
NODE_CLASSES_FOLDER = "materialx_nodes" NODE_CLASSES_FOLDER = "materialx_nodes"
NODE_CLASSES_DIR = ADDON_DATA_DIR / NODE_CLASSES_FOLDER NODE_CLASSES_DIR = ADDON_DATA_DIR / NODE_CLASSES_FOLDER
MATLIB_FOLDER = "matlib"
MATLIB_DIR = ADDON_DATA_DIR / MATLIB_FOLDER
MATLIB_URL = "https://api.matlib.gpuopen.com/api"
TEMP_FOLDER = "bl-materialx" TEMP_FOLDER = "bl-materialx"
NODE_LAYER_SEPARATION_WIDTH = 280 NODE_LAYER_SEPARATION_WIDTH = 280
@ -246,76 +242,60 @@ def get_socket_color(mx_type):
return (0.63, 0.63, 0.63, 1.0) return (0.63, 0.63, 0.63, 1.0)
def export_mx_to_file(doc, filepath, *, mx_node_tree=None, is_export_deps=False, def export_to_file(doc, filepath, *, export_textures=False, texture_dir_name='textures',
is_export_textures=False, texture_dir_name='textures', export_deps=False, copy_deps=False):
is_clean_texture_folder=True, is_clean_deps_folders=True):
root_dir = Path(filepath).parent root_dir = Path(filepath).parent
root_dir.mkdir(parents=True, exist_ok=True)
if not os.path.isdir(root_dir): if export_textures:
Path(root_dir).mkdir(parents=True, exist_ok=True)
if is_export_deps and mx_node_tree:
mx_libs_dir = root_dir / MX_LIBS_FOLDER
if os.path.isdir(mx_libs_dir) and is_clean_deps_folders:
shutil.rmtree(mx_libs_dir)
# we need to export every deps only once
unique_paths = set(node._file_path for node in mx_node_tree.nodes)
for mtlx_path in unique_paths:
# defining paths
source_path = MX_LIBS_DIR.parent / mtlx_path
full_dest_path = root_dir / mtlx_path
rel_dest_path = full_dest_path.relative_to(root_dir / MX_LIBS_FOLDER)
dest_path = root_dir / rel_dest_path
Path(dest_path.parent).mkdir(parents=True, exist_ok=True)
shutil.copy(source_path, dest_path)
mx.prependXInclude(doc, str(rel_dest_path))
if is_export_textures:
texture_dir = root_dir / texture_dir_name texture_dir = root_dir / texture_dir_name
if os.path.isdir(texture_dir) and is_clean_texture_folder:
shutil.rmtree(texture_dir)
image_paths = set() image_paths = set()
mx_input_files = (v for v in doc.traverseTree() if isinstance(v, mx.Input) and v.getType() == 'filename')
for mx_input in mx_input_files:
texture_dir.mkdir(parents=True, exist_ok=True)
i = 0 val = mx_input.getValue()
if not val:
input_files = (v for v in doc.traverseTree() if isinstance(v, mx.Input) and v.getType() == 'filename') log.warn(f"Skipping wrong {mx_input.getType()} input value. Expected: path, got {val}")
for mx_input in input_files:
if not os.path.isdir(texture_dir):
Path(texture_dir).mkdir(parents=True, exist_ok=True)
mx_value = mx_input.getValue()
if not mx_value:
log.warn(f"Skipping wrong {mx_input.getType()} input value. Expected: path, got {mx_value}")
continue continue
source_path = Path(mx_value) source_path = Path(val)
if not os.path.isfile(source_path): if not source_path.is_file():
log.warn("Image is missing", source_path) log.warn("Image is missing", source_path)
continue continue
if source_path in image_paths:
continue
dest_path = texture_dir / source_path.name dest_path = texture_dir / source_path.name
if source_path not in image_paths: if source_path not in image_paths:
image_paths.update([source_path]) image_paths.add(source_path)
dest_path = texture_dir / source_path.name
if os.path.isfile(dest_path):
i += 1
dest_path = texture_dir / f"{source_path.stem}_{i}{source_path.suffix}"
else:
dest_path = texture_dir / f"{source_path.stem}{source_path.suffix}"
shutil.copy(source_path, dest_path) shutil.copy(source_path, dest_path)
log(f"Export file {source_path} to {dest_path}: completed successfully") log(f"Export file {source_path} to {dest_path}: completed successfully")
rel_dest_path = dest_path.relative_to(root_dir) rel_dest_path = dest_path.relative_to(root_dir)
mx_input.setValue(str(rel_dest_path), mx_input.getType()) mx_input.setValue(rel_dest_path.as_posix(), mx_input.getType())
mx.writeToXmlFile(doc, filepath) if export_deps:
from .nodes import get_mx_node_cls
deps_files = {get_mx_node_cls(mx_node)[0]._file_path
for mx_node in (it for it in doc.traverseTree() if isinstance(it, mx.Node))}
for deps_file in deps_files:
deps_file = Path(deps_file)
if copy_deps:
rel_path = deps_file.relative_to(deps_file.parent.parent)
dest_path = root_dir / rel_path
dest_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(deps_file, dest_path)
deps_file = rel_path
mx.prependXInclude(doc, str(deps_file))
mx.writeToXmlFile(doc, str(filepath))
log(f"Export MaterialX to {filepath}: completed successfully") log(f"Export MaterialX to {filepath}: completed successfully")