diff --git a/.gitignore b/.gitignore index 3412679..e82c88e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ auto-install package.py +/ffxiv_mmd_tools_helper/ext/ffxiv-tex-converter \ No newline at end of file diff --git a/ffxiv_mmd_tools_helper/__init__.py b/ffxiv_mmd_tools_helper/__init__.py index 06959bc..49c4439 100644 --- a/ffxiv_mmd_tools_helper/__init__.py +++ b/ffxiv_mmd_tools_helper/__init__.py @@ -1,7 +1,7 @@ bl_info = { "name": "FFXIV MMD Tools Helper", "author": "wikid24", - "version": (0, 869), + "version": (0, 870), "blender": (2, 80, 0), "location": "View3D > Sidebar > FFXIV MMD Tools Helper", "description": "Fork of MMDToolsHelper for FFXIV Models & updated Blender to be compatible with 2.8+", @@ -31,6 +31,7 @@ def register_wrap(cls): __bl_classes.append(cls) return cls + if "bpy" in locals(): if bpy.app.version < (2, 71, 0): import imp as importlib @@ -65,8 +66,7 @@ def register_wrap(cls): importlib.reload(shaders_colorsetter) importlib.reload(shaders_mektools) importlib.reload(shaders) - importlib.reload(toon_textures_to_node_editor_shader) - importlib.reload(toon_modifier) + importlib.reload(tex_converter) importlib.reload(panels) else: @@ -104,8 +104,7 @@ def register_wrap(cls): from . import shaders_colorsetter from . import shaders_mektools from . import shaders - from . import toon_textures_to_node_editor_shader - from . import toon_modifier + from . import tex_converter from . import panels if bpy.app.version < (2, 80, 0): @@ -114,11 +113,13 @@ def register_wrap(cls): logging.basicConfig(format='%(message)s', level=logging.DEBUG) +#register all classes found in python files def register(): for cls in __bl_classes: bpy.utils.register_class(cls) - print(__name__, 'registed %d classes'%len(__bl_classes)) + print(__name__, 'registered %d classes'%len(__bl_classes)) +#unregister all classes found in python files def unregister(): for cls in reversed(__bl_classes): bpy.utils.unregister_class(cls) diff --git a/ffxiv_mmd_tools_helper/addon_preferences.py b/ffxiv_mmd_tools_helper/addon_preferences.py index 13b3e43..9cfafcf 100644 --- a/ffxiv_mmd_tools_helper/addon_preferences.py +++ b/ffxiv_mmd_tools_helper/addon_preferences.py @@ -4,6 +4,7 @@ from bpy.types import AddonPreferences from bpy.props import StringProperty + @register_wrap class FFXIV_MMDAddonPreferences(bpy.types.AddonPreferences): bl_idname = __package__ @@ -13,22 +14,10 @@ class FFXIV_MMDAddonPreferences(bpy.types.AddonPreferences): name="TexTools 'Saved' Folder", description=("Directory path to TexTools 'Saved' Folder. This is normally where TexTools saves all model & texture files upon export"), subtype='DIR_PATH', - ) - - def draw(self, context): - layout = self.layout - layout.prop(self, "textools_saved_folder") - - """ - if not self.textools_saved_folder: - # If the property is not set, set a default value here - user_profile = os.environ.get('USERPROFILE') - if user_profile: - documents_folder = os.path.join(user_profile, 'Documents', 'TexTools', 'Saved') - self.textools_saved_folder = documents_folder - """ + ) - # def update_textools_saved_folder(self, context): + + def update_textools_saved_folder(self): if not self.textools_saved_folder: # If the property is not set, set a default value here user_profile = os.environ.get('USERPROFILE') @@ -43,3 +32,18 @@ def draw(self, context): else: # Fallback to a different location if the folder doesn't exist self.textools_saved_folder = "" # Set your desired fallback path here + + + def draw(self, context): + #print(bpy.context.preferences.addons.get('ffxiv_mmd_tools_helper').preferences.textools_saved_folder) + if self.textools_saved_folder=='': + self.update_textools_saved_folder() + + layout = self.layout + layout.prop(self, "textools_saved_folder") + + + + + + \ No newline at end of file diff --git a/ffxiv_mmd_tools_helper/ext/compiler_instructions.txt b/ffxiv_mmd_tools_helper/ext/compiler_instructions.txt new file mode 100644 index 0000000..616b140 --- /dev/null +++ b/ffxiv_mmd_tools_helper/ext/compiler_instructions.txt @@ -0,0 +1,10 @@ +#1) download all files from https://github.com/emarron/ffxiv-tex-converter +#2) delete folders '.idea','notes' +#3) delete files '.gitignore','README.md','LICENSE' +#4) copy folders/files from C:\Users\%userprofile%\AppData\Local\Programs\Python\Python311\Lib\site-packages to 'ffxiv-tex-converter' folder +# .\kaitaistruct.py +# .\colorama (folder) +# .\tqdm (folder) +#5) rename 'run.py' to '__main__.py' +#6) run this in powershell in ext folder: python -m zipapp ffxiv-tex-converter +# will create a ffxiv-tex-converter.pyz file that can be used \ No newline at end of file diff --git a/ffxiv_mmd_tools_helper/ext/ffxiv-tex-converter.pyz b/ffxiv_mmd_tools_helper/ext/ffxiv-tex-converter.pyz new file mode 100644 index 0000000..ec8d1e1 Binary files /dev/null and b/ffxiv_mmd_tools_helper/ext/ffxiv-tex-converter.pyz differ diff --git a/ffxiv_mmd_tools_helper/import_ffxiv_model.py b/ffxiv_mmd_tools_helper/import_ffxiv_model.py index e223420..c5e9645 100644 --- a/ffxiv_mmd_tools_helper/import_ffxiv_model.py +++ b/ffxiv_mmd_tools_helper/import_ffxiv_model.py @@ -448,26 +448,7 @@ def rename_ffxiv_mesh(obj): def main(context): - - - if bpy.context.scene.selected_ffxiv_test_model == "import_nala": - #bpy.context.view_layer.objects.active = model.findArmature(bpy.context.active_object) - filepath='C:\\Users\\wikid\\OneDrive\\Documents\\TexTools\\Saved\\FullModel\\Nala V3\\Nala V3.fbx' - import_ffxiv_model(context,filepath) - - elif bpy.context.scene.selected_ffxiv_test_model == "import_nala_deluxe": - #bpy.context.view_layer.objects.active = model.findArmature(bpy.context.active_object) - filepath='C:\\Users\\wikid\\OneDrive\\Documents\\TexTools\\Saved\\FullModel\\Nala V3\\Nala V3.fbx' - import_ffxiv_model(context,filepath) - miscellaneous_tools.fix_object_axis() - bones_renamer.main(context) - miscellaneous_tools.correct_root_center() - miscellaneous_tools.correct_groove() - miscellaneous_tools.correct_waist() - miscellaneous_tools.correct_waist_cancel() - add_foot_leg_ik.main(context) - else: - import_ffxiv_model(context,get_test_model_file_path(bpy.context.scene.selected_ffxiv_test_model)) + import_ffxiv_model(context,get_test_model_file_path(bpy.context.scene.selected_ffxiv_test_model)) from bpy_extras.io_utils import ImportHelper @@ -531,30 +512,41 @@ def execute(self, context): @register_wrap -class SelectTexToolsModelFolder(bpy.types.Operator): +class SelectTexToolsSavedFolder(bpy.types.Operator,ImportHelper): """User can select the folder for materials""" - bl_idname = "ffxiv_mmd.select_textools_model_folder" - bl_label = "Select TexTools Model Folder" + bl_idname = "ffxiv_mmd.select_textools_saved_folder" + bl_label = "Accept" bl_options = {'REGISTER', 'UNDO'} + # Filter folders only + filename_ext = "" + filter_folder = True + filter_file = False + filter_glob: bpy.props.StringProperty(default="", options={'HIDDEN'}) - - bpy.types.Scene.textools_saved_folder = bpy.props.StringProperty( name="TexTools 'Saved' Folder" , description="Folder where FFXIV TexTools stores it's texture files" , default='' - , maxlen=0, options={'ANIMATABLE'}, subtype='DIR_PATH', update=None, get=None, set=None) + , maxlen=0, update=None, get=None, set=None) - #@classmethod - #def poll(cls, context): - # return context.active_object is not None and context.active_object.type == 'MESH' + def invoke(self, context, event): + self.filepath = context.scene.textools_saved_folder + + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} + + def execute(self, context): - #self.textools_model_folder = context.preferences.addons['ffxiv_mmd_tools_helper'].preferences.textools_saved_folder.title() + context.scene.textools_saved_folder = bpy.path.abspath(self.filepath) + + #addon_prefs_textools_folder = context.preferences.addons['ffxiv_mmd_tools_helper'].preferences.textools_saved_folder + #print(addon_prefs_textools_folder) + + # Use the default folder from the addon preferences + #default_folder = addon_prefs_textools_folder + - context.scene.textools_saved_folder = bpy.path.abspath(context.scene.textools_saved_folder) - folder_path = context.scene.textools_saved_folder - print(folder_path) return {'FINISHED'} \ No newline at end of file diff --git a/ffxiv_mmd_tools_helper/miscellaneous_tools.py b/ffxiv_mmd_tools_helper/miscellaneous_tools.py index f91b177..ed400aa 100644 --- a/ffxiv_mmd_tools_helper/miscellaneous_tools.py +++ b/ffxiv_mmd_tools_helper/miscellaneous_tools.py @@ -324,9 +324,8 @@ def main(context): reset_selected_bone_positions(context,armature) - - + @register_wrap diff --git a/ffxiv_mmd_tools_helper/model.py b/ffxiv_mmd_tools_helper/model.py index cbec1a8..f7fd810 100644 --- a/ffxiv_mmd_tools_helper/model.py +++ b/ffxiv_mmd_tools_helper/model.py @@ -56,9 +56,7 @@ def findArmature(obj): if bpy.context.mode == 'OBJECT': if obj.hide == True: - obj.hide = False - - + obj.hide = False if obj.type == 'ARMATURE': arm = obj @@ -67,14 +65,16 @@ def findArmature(obj): if obj.parent.type == 'ARMATURE': #obj.mmd_root.show_armature = True arm = obj.parent - arm.hide = False + if arm.hide == True: + arm.hide = False return arm else: for child in obj.parent.children: if child.type == 'ARMATURE': #child.mmd_root.show_armature = True arm = child - arm.hide = False + if arm.hide == True: + arm.hide = False return arm if obj.parent.parent is not None: if obj.parent.parent.type == 'ARMATURE': @@ -87,7 +87,8 @@ def findArmature(obj): for child in obj.parent.parent.children: if child.type == 'ARMATURE': arm = child - arm.hide = False + if arm.hide == True: + arm.hide = False #child.mmd_root.show_armature = True return arm if hasattr(obj, "mmd_type"): diff --git a/ffxiv_mmd_tools_helper/panels.py b/ffxiv_mmd_tools_helper/panels.py index 953dfa5..5a6eeda 100644 --- a/ffxiv_mmd_tools_helper/panels.py +++ b/ffxiv_mmd_tools_helper/panels.py @@ -1,6 +1,7 @@ import bpy from . import register_wrap from . import model +from . import addon_preferences @register_wrap @@ -14,7 +15,11 @@ class ImportModelPanel_MTH(bpy.types.Panel): bl_options = {'DEFAULT_CLOSED'} bl_order = 1 + + def draw(self, context): + + layout = self.layout # Add a help button row = layout.row() @@ -35,8 +40,9 @@ def draw(self, context): row = layout.row() row.label(text="TexTools 'Saved' Folder:") - row = layout.row() + row = layout.row(align=True) row.prop(context.scene,"textools_saved_folder", text = "") + row.operator("ffxiv_mmd.select_textools_saved_folder", text='', icon='FILE_FOLDER') #row.operator("ffxiv_mmd.textools_saved_folder", text="Select TexTools 'Saved' Folder") row = layout.row() @@ -529,6 +535,30 @@ def draw(self, context): if node.type == 'MIX_SHADER' and node.name == 'ffxiv_mmd_eye_catchlight_mix_shader': eye_catchlight_mix_node = node + + + #Glossy BSDF panel + if glossy_bsdf_node: + row = layout.row() + grid = row.grid_flow(columns=2,align=True) + grid.prop(glossy_bsdf_node.inputs[1], "default_value", text="Glossy Roughness") + grid.operator("ffxiv_mmd.remove_glossy_shader", text="", icon='X') + else: + row = layout.row() + row.operator("ffxiv_mmd.apply_glossy_shader", text="Add Glossy Shader") + + #Eye Catchlight panel + if eye_catchlight_node: + row = layout.row() + grid.label(text="Eye Catchlight Settings") + grid = row.grid_flow(columns=2,align=True) + grid.prop(eye_catchlight_mix_node.inputs['Fac'], "default_value", text="Eye Catchlight Mix") + grid.operator("ffxiv_mmd.remove_catchlight_shader", text="", icon='X') + else: + row = layout.row() + row.operator("ffxiv_mmd.apply_catchlight_shader", text="Add Eye Catchlight Shader") + + #Colorsetter Gear panel if colorsetter_gear_node: colorsetter_gear_multi_node = colorsetter_gear_node.inputs['Multi Texture'].links[0].from_node @@ -542,15 +572,16 @@ def draw(self, context): box = layout.box() grid = box.grid_flow(columns=2,align=True) grid.label(text="Colorsetter Gear Settings") + grid.label(text="For MaterialType 'e' or 'a'") #grid.operator("ffxiv_mmd.remove_colorsetter_gear_shader", text="", icon='X') grid.operator("ffxiv_mmd.remove_colorsetter_shader", text="", icon='X').shader_type = 'gear' grid = box.grid_flow(columns=2,align=True) - grid.prop(colorsetter_gear_multi_node,"image",text='Multi') - grid.prop(colorsetter_gear_normal_node,"image",text='Normal') - grid.prop(colorsetter_gear_normal_nearest_node,"image",text='Normal (Nearest)') - grid.prop(colorsetter_gear_diffuse_node,"image",text='Diffuse') - grid.prop(colorsetter_gear_specular_node,"image",text='Specular') + grid.prop(colorsetter_gear_multi_node,"image",text='Multi (_m,_d,_s)') + grid.prop(colorsetter_gear_normal_node,"image",text='Normal (_n)') + grid.prop(colorsetter_gear_normal_nearest_node,"image",text='Normal (Nearest)(_n)') + grid.prop(colorsetter_gear_diffuse_node,"image",text='Diffuse (_d)') + grid.prop(colorsetter_gear_specular_node,"image",text='Specular (_s)') grid.prop(colorsetter_gear_specular_mask_node,"image",text='Specular Mask') grid.operator("ffxiv_mmd.update_colorsetter_image_node",text='',icon='FILEBROWSER').image_node_name = colorsetter_gear_multi_node.name grid.operator("ffxiv_mmd.update_colorsetter_image_node",text='',icon='FILEBROWSER').image_node_name = colorsetter_gear_normal_node.name @@ -558,29 +589,11 @@ def draw(self, context): grid.operator("ffxiv_mmd.update_colorsetter_image_node",text='',icon='FILEBROWSER').image_node_name = colorsetter_gear_diffuse_node.name grid.operator("ffxiv_mmd.update_colorsetter_image_node",text='',icon='FILEBROWSER').image_node_name = colorsetter_gear_specular_node.name grid.operator("ffxiv_mmd.update_colorsetter_image_node",text='',icon='FILEBROWSER').image_node_name = colorsetter_gear_specular_mask_node.name - - - - #Glossy BSDF panel - if glossy_bsdf_node: - row = layout.row() - grid = row.grid_flow(columns=2,align=True) - grid.prop(glossy_bsdf_node.inputs[1], "default_value", text="Glossy Roughness") - grid.operator("ffxiv_mmd.remove_glossy_shader", text="", icon='X') - else: - row = layout.row() - row.operator("ffxiv_mmd.apply_glossy_shader", text="Add Glossy Shader") - - #Eye Catchlight panel - if eye_catchlight_node: - row = layout.row() - grid.label(text="Eye Catchlight Settings") - grid = row.grid_flow(columns=2,align=True) - grid.prop(eye_catchlight_mix_node.inputs['Fac'], "default_value", text="Eye Catchlight Mix") - grid.operator("ffxiv_mmd.remove_catchlight_shader", text="", icon='X') - else: - row = layout.row() - row.operator("ffxiv_mmd.apply_catchlight_shader", text="Add Eye Catchlight Shader") + grid = box.grid_flow(columns=1,align=True) + grid.label(text="Replace Textures Folder") + grid = box.grid_flow(columns=2,align=True) + grid.prop(context.scene,"shaders_replacement_texture_folder",text='') + grid.operator("ffxiv_mmd.replace_colorsetter_textures",text='',icon='CHECKMARK') #MekTools skin panel @@ -663,6 +676,7 @@ def draw(self, context): box = layout.box() grid = box.grid_flow(columns=2,align=True) grid.label(text="Colorsetter Eye Settings") + grid.label(text="For materials that end with '_iri_a'") #grid.operator("ffxiv_mmd.remove_colorsetter_eye_shader", text="", icon='X') grid.operator("ffxiv_mmd.remove_colorsetter_shader", text="", icon='X').shader_type = 'eye' @@ -697,6 +711,7 @@ def draw(self, context): box = layout.box() grid = box.grid_flow(columns=2,align=True) grid.label(text="Colorsetter Hair Settings") + grid.label(text="For MaterialType 'h'") #grid.operator("ffxiv_mmd.remove_colorsetter_hair_shader", text="", icon='X') grid.operator("ffxiv_mmd.remove_colorsetter_shader", text="", icon='X').shader_type = 'hair' @@ -740,7 +755,7 @@ def draw(self, context): box = layout.box() grid = box.grid_flow(columns=2,align=True) grid.label(text="Colorsetter Face Settings") - grid.label(text="For materiels that end with '_fac_a'") + grid.label(text="For materials that end with '_fac_a'") #grid.operator("ffxiv_mmd.remove_colorsetter_face_shader", text="", icon='X') grid.operator("ffxiv_mmd.remove_colorsetter_shader", text="", icon='X').shader_type='face' @@ -784,7 +799,7 @@ def draw(self, context): box = layout.box() grid = box.grid_flow(columns=2,align=True) grid.label(text="Colorsetter Face Accent Settings") - grid.label(text="For materiels that end with '_etc_a'") + grid.label(text="For materials that end with '_etc_a'") #grid.operator("ffxiv_mmd.remove_colorsetter_faceacc_shader", text="", icon='X') grid.operator("ffxiv_mmd.remove_colorsetter_shader", text="", icon='X').shader_type = 'faceacc' @@ -799,8 +814,8 @@ def draw(self, context): grid.prop(colorsetter_faceacc_limbal_intensity,"default_value",text='Limbal Intensity') grid = box.grid_flow(columns=2,align=True) - grid.prop(colorsetter_faceacc_multi_node,"image",text='Multi') - grid.prop(colorsetter_faceacc_normal_node,"image",text='Normal') + grid.prop(colorsetter_faceacc_multi_node,"image",text='Multi (_s)') + grid.prop(colorsetter_faceacc_normal_node,"image",text='Normal (_n)') grid.operator("ffxiv_mmd.update_colorsetter_image_node",text='',icon='FILEBROWSER').image_node_name = colorsetter_faceacc_multi_node.name grid.operator("ffxiv_mmd.update_colorsetter_image_node",text='',icon='FILEBROWSER').image_node_name = colorsetter_faceacc_normal_node.name #grid.prop(colorsetter_eye_node.node_tree.nodes['Normal Map'].inputs['Strength'],"default_value",text='Normal Strength',slider=True) @@ -846,6 +861,7 @@ def draw(self, context): box = layout.box() grid = box.grid_flow(columns=2,align=True) grid.label(text="Colorsetter Skin Settings") + grid.label(text="For MaterialType 'b'") #grid.operator("ffxiv_mmd.remove_colorsetter_skin_shader", text="", icon='X') grid.operator("ffxiv_mmd.remove_colorsetter_shader", text="", icon='X').shader_type='skin' @@ -853,7 +869,7 @@ def draw(self, context): if colorsetter_skin_color: grid = box.grid_flow(columns=1,align=True) grid.prop(colorsetter_skin_color,"default_value",text='Skin Color') - grid.prop(colorsetter_skin_sss,"default_value",text='Subsurface Scattering') + grid.prop(colorsetter_skin_sss,"default_value",text='Subsurface Scattering', slider=True) grid = box.grid_flow(columns=2,align=True) grid.prop(colorsetter_skin_diffuse_node,"image",text='Diffuse') @@ -880,13 +896,6 @@ def draw(self, context): grid.operator("ffxiv_mmd.apply_mektools_eye_shader", text="Eyes") row = layout.row() row.label(text = 'Apply Colorsetter Shader') - # Colorsetter Addon Gear Stuff - row = layout.row() - row.label(text="Colorsetter Gear Texture Folder:") - row = layout.row() - grid = row.grid_flow(columns=2,align=True) - grid.prop(context.scene,"shaders_texture_folder", text = "") - grid.operator("ffxiv_mmd.select_colorsetter_gear_materials_folder", text="", icon='CHECKMARK') row = layout.row() grid = row.grid_flow(columns=2, align=True) grid.operator("ffxiv_mmd.apply_colorsetter_shader", text="Skin").shader_type = 'skin' @@ -895,14 +904,13 @@ def draw(self, context): grid.operator("ffxiv_mmd.apply_colorsetter_shader", text="Eyes").shader_type = 'eye' grid.operator("ffxiv_mmd.apply_colorsetter_shader", text="Face Accent").shader_type = 'faceacc' grid.operator("ffxiv_mmd.apply_colorsetter_shader", text="Hrothgar/Miqote Tail").shader_type = 'tail' - """ - grid.operator("ffxiv_mmd.apply_colorsetter_skin_shader", text="Skin") - grid.operator("ffxiv_mmd.apply_colorsetter_face_shader", text="Face") - grid.operator("ffxiv_mmd.apply_colorsetter_hair_shader", text="Hair") - grid.operator("ffxiv_mmd.apply_colorsetter_eye_shader", text="Eyes") - grid.operator("ffxiv_mmd.apply_colorsetter_faceacc_shader", text="Face Accent") - grid.operator("ffxiv_mmd.apply_colorsetter_tail_shader", text="Hrothgar/Miqote Tail") - """ + # Colorsetter Addon Gear Stuff + row = layout.row() + row.label(text="Apply Colorsetter Gear Texture Folder:") + row = layout.row() + grid = row.grid_flow(columns=2,align=True) + grid.prop(context.scene,"shaders_texture_folder", text = "") + grid.operator("ffxiv_mmd.select_colorsetter_gear_materials_folder", text="", icon='CHECKMARK') @@ -1056,6 +1064,9 @@ def draw(self, context): col.prop(context.scene, "bone_compare_scale_z", text="Z") + + + @register_wrap class ExportMMD_MTH(bpy.types.Panel): bl_idname = "OBJECT_PT_ExportMMD_MTH" @@ -1094,3 +1105,36 @@ def draw(self, context): row = layout.row() """ +@register_wrap +class TexToDDS_MTH(bpy.types.Panel): + bl_idname = "OBJECT_PT_TexToDDS_MTH" + bl_label = "Tex to DDS Converter" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" if bpy.app.version < (2,80,0) else "UI" + bl_category = "FFXIV MMD" + bl_options = {'DEFAULT_CLOSED'} + bl_order = 12 + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label(text='Input Folder') + row = layout.row() + grid = row.grid_flow(columns=2, align=True) + grid.prop(context.scene,"convert_input_file_folder",text='') + grid.operator("ffxiv_mmd.select_convert_input_folder", icon='FILE_FOLDER', text='') + row = layout.row() + row.label(text='Output Folder') + row = layout.row() + grid = row.grid_flow(columns=2, align=True) + grid.prop(context.scene,"convert_output_file_folder",text='') + grid.operator("ffxiv_mmd.convert_open_output_folder", icon='FILE_FOLDER', text='') + row = layout.row() + grid = row.grid_flow(columns=1, align=True) + row.operator("ffxiv_mmd.convert_tex_to_dds", text='Convert .tex to .dds') + row.operator("ffxiv_mmd.convert_dds_to_tex", text='Convert .dds to .tex') + row = layout.row() + row.label(text='WARNING: This converts ALL subfolders as well') + row = layout.row() + row.label(text='THIS MAY TAKE A WHILE IF LOTS OF FILES/SUBFOLDERS') + diff --git a/ffxiv_mmd_tools_helper/shaders_colorsetter.py b/ffxiv_mmd_tools_helper/shaders_colorsetter.py index f50b2aa..2f22127 100644 --- a/ffxiv_mmd_tools_helper/shaders_colorsetter.py +++ b/ffxiv_mmd_tools_helper/shaders_colorsetter.py @@ -215,6 +215,56 @@ def apply_material_to_all_matching_ffxiv_meshes (source_object): obj.active_material = source_object.active_material +import fnmatch +def find_replacement_texture(image_node, replacement_folderpath): + if image_node.image and image_node.image.filepath: + filename = os.path.basename(image_node.image.filepath) + filename_no_extension, extension = os.path.splitext(filename) + + # List of valid extensions to check for + valid_extensions = ['.dds', '.png', '.bmp'] + + # Get the list of files in the replacement folder and its subfolders + replacement_files = set() + for root, dirs, files in os.walk(replacement_folderpath): + for file in fnmatch.filter(files, f"{filename_no_extension}*"): + if any(file.lower().endswith(ext) for ext in valid_extensions): + replacement_files.add(os.path.join(root, file)) + + # Check if any replacement file with a valid extension exists + if replacement_files: + # Choose the first match + new_filepath = sorted(replacement_files)[0] + + if new_filepath != image_node.image.filepath: + + update_image_node_file(image_node,new_filepath) + + print(f"Node:{image_node.name} updated with replacement image: {os.path.basename(new_filepath)}") + else: + print(f"No matching replacement image found for node:{image_node.name}") + + + +@register_wrap +class ReplaceColorsetterTextures(bpy.types.Operator): + """Apply the Colorsetter addon to the selected mesh using DDS/PNG/BMP textures from the selected folder""" + bl_idname = "ffxiv_mmd.replace_colorsetter_textures" + bl_label = "Select Materials Folder" + bl_options = {'REGISTER', 'UNDO'} + + bpy.types.Scene.shaders_replacement_texture_folder = bpy.props.StringProperty(name="Texture Folder", description="Folder where the gear for _a, _m, and _n files are located", default="", maxlen=0, options={'ANIMATABLE'}, subtype='DIR_PATH', update=None, get=None, set=None) + + def execute(self, context): + active_object = context.active_object + active_material = active_object.active_material + + for node in active_material.node_tree.nodes: + if node.type == 'TEX_IMAGE': + find_replacement_texture(node,context.scene.shaders_replacement_texture_folder) + + return {'FINISHED'} + @@ -225,7 +275,6 @@ class SelectColorsetterGearMaterialsFolder(bpy.types.Operator): bl_label = "Select Materials Folder" bl_options = {'REGISTER', 'UNDO'} - #folder_path = "yoooooo" bpy.types.Scene.shaders_texture_folder = bpy.props.StringProperty(name="Texture Folder", description="Folder where the gear for _a, _m, and _n files are located", default="", maxlen=0, options={'ANIMATABLE'}, subtype='DIR_PATH', update=None, get=None, set=None) @classmethod @@ -242,8 +291,7 @@ def execute(self, context): addon_name = 'Colorsetter' # Check if the addon is enabled - if addon_name not in bpy.context.preferences.addons.keys(): - + if addon_name not in context.preferences.addons.keys(): raise Exception(f"The addon '{addon_name}' is not installed or is not enabled. Please install and enable it.") else: #print(f"The addon '{addon_name}' is installed and enabled.") @@ -251,30 +299,27 @@ def execute(self, context): context.scene.shaders_texture_folder = bpy.path.abspath(context.scene.shaders_texture_folder) folder_path = context.scene.shaders_texture_folder print (folder_path) - #context.scene.ffxiv_mmd.select_materials_folder.folder_path old_material = None new_material = None - if bpy.context.active_object: + if context.active_object: - obj = bpy.context.active_object + obj = context.active_object if obj.type == 'MESH': old_material = apply_colorset_plugin() new_material = apply_textures_to_colorset_material(context,folder_path).id_data - if undo_if_colorset_plugin_error(bpy.context.active_object,old_material, new_material): + if undo_if_colorset_plugin_error(context.active_object,old_material, new_material): #if no errors, apply to all materials - print ("no errors ya'll!") apply_material_to_all_matching_ffxiv_meshes (obj) else: print ("ERROR: COULD NOT APPLY THE COLORSETTER PLUGIN (probably a 'Import DDS' error in Colorsetter addon)") - # return {"CANCELLED"} - return {'FINISHED'} - + + def get_ffxiv_skin_file(active_object,texture_type): diff --git a/ffxiv_mmd_tools_helper/tex_converter.py b/ffxiv_mmd_tools_helper/tex_converter.py new file mode 100644 index 0000000..ad6ad75 --- /dev/null +++ b/ffxiv_mmd_tools_helper/tex_converter.py @@ -0,0 +1,144 @@ +import bpy +import subprocess +import sys +import os + +from . import register_wrap +from bpy_extras.io_utils import ImportHelper + + +def tex_to_dds(source_folder): + file_path = (__file__ + r"ext\ffxiv-tex-converter.pyz").replace("tex_converter.py" , "") + # Example usage + #args = ["--directory", "/path/to/your/directory", "--command", "dds-to-tex", "--parallel", "--multiplier", "5"] + #source_folder = r"D:\temp" + target_folder = None + + run_pyz_script(file_path, ["--directory", source_folder, "--command", "tex-to-dds"]) + + +def dds_to_tex(source_folder): + file_path = (__file__ + r"ext\ffxiv-tex-converter.pyz").replace("tex_converter.py" , "") + # Example usage + #args = ["--directory", "/path/to/your/directory", "--command", "dds-to-tex", "--parallel", "--multiplier", "5"] + #source_folder = r"D:\temp" + target_folder = None + + run_pyz_script(file_path, ["--directory", source_folder, "--command", "dds-to-tex"]) + + + + +def run_pyz_script(pyz_file_path, args): + # Get the path to the Python executable running Blender + blender_python_executable = sys.executable + + # Construct the command to run the pyz file + command = [blender_python_executable, pyz_file_path] + args + # Run the command + subprocess.run(command) + +@register_wrap +class SelectConvertInputFolder(bpy.types.Operator,ImportHelper): + """Select Input Folder for Conversion""" + bl_idname = "ffxiv_mmd.select_convert_input_folder" + bl_label = "Accept" + bl_options = {'REGISTER', 'UNDO'} + + # Filter folders only + filename_ext = "" + filter_folder = True + filter_file = False + filter_glob: bpy.props.StringProperty(default="", options={'HIDDEN'}) + + bpy.types.Scene.convert_input_file_folder = bpy.props.StringProperty( + name="Input File Folder" + , description="Folder where the input files are located" + , default='' + , maxlen=0, update=None, get=None, set=None) + + bpy.types.Scene.convert_output_file_folder = bpy.props.StringProperty( + name="Output File Folder" + , description="Folder where the output files are located" + , default='' + , maxlen=0, update=None, get=None, set=None) + + #def invoke(self, context, event): + #self.filepath = context.scene.convert_input_file_folder + #context.window_manager.fileselect_add(self) + #return {'RUNNING_MODAL'} + + def execute(self, context): + context.scene.convert_input_file_folder = bpy.path.abspath(self.filepath) + context.scene.convert_output_file_folder = '' + + return {'FINISHED'} + + +@register_wrap +class ConvertTEXtoDDS(bpy.types.Operator): + """User can select the folder for materials""" + bl_idname = "ffxiv_mmd.convert_tex_to_dds" + bl_label = "Convert .tex to .dds" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self,context): + input_folder_path = context.scene.convert_input_file_folder + + # Get the folder path + folder_path = os.path.dirname(input_folder_path) + + # Append "_dds" to the folder path + output_folder_path = os.path.join(folder_path, folder_path + "_dds") + context.scene.convert_output_file_folder = bpy.path.abspath(output_folder_path) + + + tex_to_dds(input_folder_path) + + return {'FINISHED'} + + +@register_wrap +class ConvertDDStoTEX(bpy.types.Operator): + """User can select the folder for materials""" + bl_idname = "ffxiv_mmd.convert_dds_to_tex" + bl_label = "Convert .dds to .tex" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self,context): + input_folder_path = context.scene.convert_input_file_folder + + # Get the folder path + folder_path = os.path.dirname(input_folder_path) + + # Append "_tex" to the folder path + output_folder_path = os.path.join(folder_path, folder_path + "_tex") + context.scene.convert_output_file_folder = bpy.path.abspath(output_folder_path) + + dds_to_tex(input_folder_path) + + return {'FINISHED'} + + +@register_wrap +class ConvertOpenOutputFolderInWindowsExplorer(bpy.types.Operator): + """Opens output folder in Windows Explorer""" + bl_idname = "ffxiv_mmd.convert_open_output_folder" + bl_label = "Open Output Folder in Windows Explorer" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + folder_path = bpy.path.abspath(context.scene.convert_output_file_folder) + + return os.path.exists(folder_path) + + + def execute(self,context): + folder_path = bpy.path.abspath(context.scene.convert_output_file_folder) + + if os.path.exists(folder_path): + # Use subprocess to open the folder in Windows Explorer + subprocess.Popen(['start', 'explorer', folder_path], shell=True) + + return {'FINISHED'} diff --git a/ffxiv_mmd_tools_helper/toon_modifier.py b/ffxiv_mmd_tools_helper/toon_modifier.py deleted file mode 100644 index 857d505..0000000 --- a/ffxiv_mmd_tools_helper/toon_modifier.py +++ /dev/null @@ -1,78 +0,0 @@ -import bpy - -from . import register_wrap -from . import model - - # blend_type - # Type: enum in ["MIX", "ADD", "MULTIPLY", "SUBTRACT", "SCREEN", "DIVIDE", "DIFFERENCE", "DARKEN", "LIGHTEN", "OVERLAY", "DODGE", "BURN", "HUE", "SATURATION", "VALUE", "COLOR", "SOFT_LIGHT", "LINEAR_LIGHT"], default ‘MIX’ - -def main(context): - mesh_objects_list = model.find_MMD_MeshesList(bpy.context.active_object) - # print("mesh_objects_list = ", mesh_objects_list) - assert(mesh_objects_list is not None), "The active object is not an MMD model." - for o in mesh_objects_list: - bpy.context.view_layer.objects.active = o - for m in bpy.context.active_object.data.materials: - for n in m.node_tree.nodes: - if n.label == "toon_modifier": - n.inputs['Color2'].default_value[0] = bpy.context.scene.ToonModifierColor[0] - n.inputs['Color2'].default_value[1] = bpy.context.scene.ToonModifierColor[1] - n.inputs['Color2'].default_value[2] = bpy.context.scene.ToonModifierColor[2] - n.blend_type = bpy.context.scene.ToonModifierBlendType - - -@register_wrap -class MMDToonModifier(bpy.types.Operator): - """User can modify the rendering of toon texture color""" - bl_idname = "ffxiv_mmd.toon_modifier" - bl_label = "MMD toon modifier" - bl_options = {'REGISTER', 'UNDO'} - - bpy.types.Scene.ToonModifierColor = bpy.props.FloatVectorProperty( \ - name="Toon Modifer Color" \ - , description="toon modifer color" \ - , default=(1.0, 1.0, 1.0) \ - , min=0.0 \ - , max=1.0 \ - , soft_min=0.0 \ - , soft_max=1.0 \ - , step=3 \ - , precision=2 \ - , options={'ANIMATABLE'} \ - , subtype='COLOR' \ - , unit='NONE' \ - , size=3 \ - , update=None \ - , get=None \ - , set=None) - - bpy.types.Scene.ToonModifierBlendType = bpy.props.EnumProperty( \ - items = [ \ - ('MIX', 'MIX', 'MIX') \ - , ('ADD', 'ADD', 'ADD') \ - , ('MULTIPLY', 'MULTIPLY', 'MULTIPLY') \ - , ('SUBTRACT', 'SUBTRACT', 'SUBTRACT') \ - , ('SCREEN', 'SCREEN', 'SCREEN') \ - , ('DIVIDE', 'DIVIDE', 'DIVIDE') \ - , ('DIFFERENCE', 'DIFFERENCE', 'DIFFERENCE') \ - , ('DARKEN', 'DARKEN', 'DARKEN') \ - , ('LIGHTEN', 'LIGHTEN', 'LIGHTEN') \ - , ('OVERLAY', 'OVERLAY', 'OVERLAY') \ - , ('DODGE', 'DODGE', 'DODGE') \ - , ('BURN', 'BURN', 'BURN') \ - , ('HUE', 'HUE', 'HUE') \ - , ('SATURATION', 'SATURATION', 'SATURATION') \ - , ('VALUE', 'VALUE', 'VALUE') \ - , ('COLOR', 'COLOR', 'COLOR') \ - , ('SOFT_LIGHT', 'SOFT_LIGHT', 'SOFT_LIGHT') \ - , ('LINEAR_LIGHT', 'LINEAR_LIGHT', 'LINEAR_LIGHT')] \ - , name = "Toon Modifier Blend Type", default = 'MULTIPLY' \ - ) - - # @classmethod - # def poll(cls, context): - # return context.active_object is not None - - def execute(self, context): - main(context) - return {'FINISHED'} \ No newline at end of file diff --git a/ffxiv_mmd_tools_helper/toon_textures_to_node_editor_shader.py b/ffxiv_mmd_tools_helper/toon_textures_to_node_editor_shader.py deleted file mode 100644 index 5558e60..0000000 --- a/ffxiv_mmd_tools_helper/toon_textures_to_node_editor_shader.py +++ /dev/null @@ -1,444 +0,0 @@ -import bpy - -from . import register_wrap -from . import model - - -# Each image is a list of numbers(floats): R,G,B,A,R,G,B,A etc. -# So the length of the list of pixels is 4 X number of pixels -# pixels are in left-to-right rows from bottom left to top right of image - - -def toon_image_to_color_ramp(toon_texture_color_ramp, toon_image): - pixels_width = toon_image.size[0] - pixels_height = toon_image.size[1] - toon_image_pixels = [] - toon_image_gradient = [] - - for f in range(0, len(toon_image.pixels), 4): - pixel_rgba = toon_image.pixels[f:f+4] - toon_image_pixels.append(pixel_rgba) - - for p in range(0, len(toon_image_pixels), int(len(toon_image_pixels)/32)): - toon_image_gradient.append(toon_image_pixels[p]) - - toon_texture_color_ramp.color_ramp.elements[0].color = toon_image_gradient[0] - toon_texture_color_ramp.color_ramp.elements[-1].color = toon_image_gradient[-1] - - for i in range(1, len(toon_image_gradient)-2, 1): - toon_texture_color_ramp.color_ramp.elements.new(i/(len(toon_image_gradient)-1)) - toon_texture_color_ramp.color_ramp.elements[i].color = toon_image_gradient[i] - if i > len(toon_image_gradient)/2: - toon_texture_color_ramp.color_ramp.elements[i].color[3] = 0.0 #alpha of non-shadow colors set to 0.0 - - return - - -def clear_material_nodes(context): - for m in bpy.context.active_object.data.materials: - if m.node_tree is not None: - for n in range(len(m.node_tree.nodes)-1, 1, -1): - m.node_tree.nodes.remove(m.node_tree.nodes[n]) - - -def main_OLD(context): - o = bpy.context.active_object - if o.type == 'MESH': - - if len(bpy.data.lights) == 0: - bpy.data.lights.new("Lamp", "SUN") - - for object in bpy.context.scene.objects: - if object.data == bpy.data.lights[0]: - LAMP = object - - if o.data.materials is not None: - for m in o.data.materials: - m.use_nodes = True - # m.node_tree.nodes.new('OUTPUT') - # m.node_tree.nodes.new('MATERIAL') - output_node = m.node_tree.nodes[0] - output_node.location = (1450, 800) - material_node = m.node_tree.nodes[1] - material_node = m.node_tree.nodes.new('ShaderNodeBsdfPrincipled') - material_node.material = m - material_node.location = (-800, 800) - - lamp_node = m.node_tree.nodes.new('ShaderNodeLampData') - lamp_node.lamp_object = LAMP - lamp_node.location = (-530, -50) - - rgb_to_bw = m.node_tree.nodes.new('ShaderNodeRGBToBW') - rgb_to_bw.location = (-90, -50) #(120, 470) (-530, -50) - - vector_math_node = m.node_tree.nodes.new('ShaderNodeVectorMath') - vector_math_node.operation = 'DOT_PRODUCT' - vector_math_node.location = (-520, 470) - - math_node_1 = m.node_tree.nodes.new('ShaderNodeMath') - math_node_1.operation = 'ADD' - math_node_1.inputs[1].default_value = 1.0 - math_node_1.location = (-325, 470) - - math_node_2 = m.node_tree.nodes.new('ShaderNodeMath') - math_node_2.operation = 'MULTIPLY' - math_node_2.inputs[1].default_value = 0.5 #1.0 - # math_node_2.use_clamp = True - math_node_2.location = (-90, 470) - - math_node_3 = m.node_tree.nodes.new('ShaderNodeMath') - math_node_3.operation = 'MULTIPLY' - math_node_3.location = (120, 470) - - toon_texture_color_ramp = m.node_tree.nodes.new('ShaderNodeValToRGB') - toon_texture_color_ramp.location = (340, 470) - - mix_rgb_node_ramp_overlay = m.node_tree.nodes.new('ShaderNodeMixRGB') - mix_rgb_node_ramp_overlay.blend_type = 'MULTIPLY' #was 'OVERLAY' - mix_rgb_node_ramp_overlay.inputs[0].default_value = 1.0 - mix_rgb_node_ramp_overlay.inputs['Color2'].default_value = (1.0, 1.0, 1.0, 1.0) - mix_rgb_node_ramp_overlay.location = (690, 470) - mix_rgb_node_ramp_overlay.label = "toon_modifier" - - mix_rgb_node = m.node_tree.nodes.new('ShaderNodeMixRGB') - mix_rgb_node.blend_type = 'MULTIPLY' - mix_rgb_node.inputs[0].default_value = 1.0 - # mix_rgb_node.use_clamp = True - mix_rgb_node.location = (1000, 470) - mix_rgb_node.inputs['Color2'].default_value = (m.diffuse_color[0], m.diffuse_color[1], m.diffuse_color[2], 1.0) - - mix_rgb_node_add_sphere = m.node_tree.nodes.new('ShaderNodeMixRGB') - mix_rgb_node_add_sphere.blend_type = 'ADD' - mix_rgb_node_add_sphere.inputs[0].default_value = 1.0 - mix_rgb_node_add_sphere.location = (1240, 470) - - diffuse_texture_geomety_uv_node = m.node_tree.nodes.new('ShaderNodeGeometry') - diffuse_texture_geomety_uv_node.location = (620, 250) - - diffuse_texture_node = m.node_tree.nodes.new('ShaderNodeTexture') - diffuse_texture_node.location = (820, 250) - - sphere_texture_geometry_normal_node = m.node_tree.nodes.new('ShaderNodeGeometry') - sphere_texture_geometry_normal_node.location = (620, -50) - - sphere_texture_node = m.node_tree.nodes.new('ShaderNodeTexture') - sphere_texture_node.location = (820, -50) - - - print(len(m.node_tree.links)) - m.node_tree.links.new(output_node.inputs['Alpha'], material_node.outputs['Alpha']) - print(len(m.node_tree.links)) - m.node_tree.links.new(vector_math_node.inputs[0], material_node.outputs['Normal']) #vector_math_node.inputs['Vector'] - print(len(m.node_tree.links)) - m.node_tree.links.new(vector_math_node.inputs[1], lamp_node.outputs['Light Vector']) #vector_math_node.inputs['Vector'] - print(len(m.node_tree.links)) - m.node_tree.links.new(rgb_to_bw.inputs['Color'], lamp_node.outputs['Shadow']) #math_node_3.inputs['Value'] - print(len(m.node_tree.links)) - m.node_tree.links.new(math_node_3.inputs[1], rgb_to_bw.outputs['Val']) - - print(len(m.node_tree.links)) - m.node_tree.links.new(math_node_1.inputs[0], vector_math_node.outputs['Value']) #math_node_1.inputs['Value'] - print(len(m.node_tree.links)) - m.node_tree.links.new(math_node_2.inputs['Value'], math_node_1.outputs['Value']) - print(len(m.node_tree.links)) - m.node_tree.links.new(math_node_3.inputs[0], math_node_2.outputs['Value']) #math_node_3.inputs['Value'] - print(len(m.node_tree.links)) - m.node_tree.links.new(toon_texture_color_ramp.inputs['Fac'], math_node_3.outputs['Value']) - print(len(m.node_tree.links)) - m.node_tree.links.new(mix_rgb_node_ramp_overlay.inputs['Color1'], toon_texture_color_ramp.outputs['Color']) - print(len(m.node_tree.links)) - m.node_tree.links.new(mix_rgb_node_ramp_overlay.inputs['Fac'], toon_texture_color_ramp.outputs['Alpha']) - print(len(m.node_tree.links)) - m.node_tree.links.new(mix_rgb_node.inputs['Color1'], mix_rgb_node_ramp_overlay.outputs['Color']) - - print(len(m.node_tree.links)) - m.node_tree.links.new(mix_rgb_node_add_sphere.inputs['Color1'], mix_rgb_node.outputs['Color']) - print(len(m.node_tree.links)) - m.node_tree.links.new(output_node.inputs['Color'], mix_rgb_node_add_sphere.outputs['Color']) - print(len(m.node_tree.links)) - m.node_tree.links.new(diffuse_texture_node.inputs['Vector'], diffuse_texture_geomety_uv_node.outputs['UV']) - print(len(m.node_tree.links)) - m.node_tree.links.new(mix_rgb_node.inputs['Color2'], diffuse_texture_node.outputs['Color']) - print(len(m.node_tree.links)) - m.node_tree.links.new(sphere_texture_node.inputs['Vector'], sphere_texture_geometry_normal_node.outputs['Normal']) - print(len(m.node_tree.links)) - m.node_tree.links.new(mix_rgb_node_add_sphere.inputs['Color2'],sphere_texture_node.outputs['Color']) - - if m.texture_slots is not None: - for t in range(len(m.texture_slots)): - if m.texture_slots[t] is not None: - texture_name = m.texture_slots[t].texture.name - if t == 0: - diffuse_texture_node.texture = bpy.data.textures[texture_name] - diffuse_exists = True - # bpy.data.textures[texture_name]["mmd_texture_type"] = "DIFFUSE" - if t == 1: - if m.texture_slots[t].texture.type == 'IMAGE': - if m.texture_slots[t].texture.image is not None: - #toon_image_bottom_half_to_color_ramp(toon_texture_color_ramp, m.texture_slots[t].texture.image) - toon_image_to_color_ramp(toon_texture_color_ramp, m.texture_slots[t].texture.image) - # bpy.data.textures[texture_name]["mmd_texture_type"] = "TOON" - # bpy.context.active_object.data.materials[0].texture_slots[1].texture.image.name - if t == 2: - mix_rgb_node_add_sphere.blend_type = m.texture_slots[t].blend_type - sphere_texture_node.texture = bpy.data.textures[texture_name] - sphere_exists = True - # bpy.data.textures[texture_name]["mmd_texture_type"] = "SPHERE" - - if diffuse_texture_node.texture == None: - # m.node_tree.links.new(mix_rgb_node.inputs['Color2'], material_node.outputs['Color']) - # m.use_shadeless = True - m.node_tree.links.remove(mix_rgb_node.inputs['Color2'].links[0]) - print("This mesh object has no diffuse texture.") - - - -def main (context): - o = bpy.context.active_object - if o.type == 'MESH': - - if len(bpy.data.lights) == 0: - bpy.data.lights.new("Lamp", "SUN") - - for object in bpy.context.scene.objects: - if object.data == bpy.data.lights[0]: - LAMP = object - - #toonify_material(o) - - # Iterate over the selected objects - for obj in bpy.context.selected_objects: - # Check if the object is a mesh object - if obj.type == 'MESH': - # Iterate over the object's material slots - for slot in obj.material_slots: - mat = slot.material - #bpy.context.object.active_material = mat - toonify_material(mat) - print(f"The material in slot {slot.name} of object {obj.name} is {mat.name}") - #else: - #print(f'{obj.name} is not a mesh object') - - - - - -def toonify_material(mat): - - # Get the active material - #mat = bpy.context.object.active_material - - # Find the principled BSDF node - principled = None - for node in mat.node_tree.nodes: - if node.type == 'BSDF_PRINCIPLED': - principled = node - break - - if principled is None: - print("Principled BSDF node not found.") - principled = mat.node_tree.nodes.new(type='ShaderNodeBsdfPrincipled') - principled.location = (0,0) - else: - # Create 'shader to RGB' node if it doesnt exist - shader_to_rgb = None - for node in mat.node_tree.nodes: - if node.name == 'MMDH_shader_to_rgb': - shader_to_rgb = node - break - if shader_to_rgb == None: - shader_to_rgb = mat.node_tree.nodes.new(type='ShaderNodeShaderToRGB') - shader_to_rgb.name = 'MMDH_shader_to_rgb' - shader_to_rgb.label = shader_to_rgb.name - shader_to_rgb.location.x = principled.location.x + 300 - shader_to_rgb.location.y = principled.location.y - - # Create 'color ramp' node if it doesn't exist - color_ramp = None - for node in mat.node_tree.nodes: - if node.name == 'MMDH_color_ramp': - color_ramp = node - break - if color_ramp == None: - color_ramp = mat.node_tree.nodes.new(type='ShaderNodeValToRGB') - color_ramp.name = 'MMDH_color_ramp' - color_ramp.label = color_ramp.name - color_ramp.location.x = shader_to_rgb.location.x + 300 - color_ramp.location.y = shader_to_rgb.location.y - - # Create mix_rgb node if it doesn't exist - mix_rgb = None - for node in mat.node_tree.nodes: - if node.name == 'MMDH_mix_rgb': - mix_rgb = node - break - if mix_rgb == None: - mix_rgb = mat.node_tree.nodes.new(type='ShaderNodeMixRGB') - mix_rgb.name = 'MMDH_mix_rgb' - mix_rgb.label = mix_rgb.name - mix_rgb.location.x = color_ramp.location.x + 300 - mix_rgb.location.y = color_ramp.location.y - - # Create hue & saturation node if it doesn't exist - hue_sat = None - for node in mat.node_tree.nodes: - if node.name == 'MMDH_hue_sat': - hue_sat = node - break - if hue_sat == None: - hue_sat = mat.node_tree.nodes.new(type='ShaderNodeHueSaturation') - hue_sat.name = 'MMDH_hue_sat' - hue_sat.label = hue_sat.name - hue_sat.location.x = mix_rgb.location.x + 300 - hue_sat.location.y = mix_rgb.location.y - - # Create the transparent BSDF node if it doesn't exist - transparent_bsdf = None - for node in mat.node_tree.nodes: - if node.name == 'MMDH_transparent_bsdf': - transparent_bsdf = node - break - if transparent_bsdf == None: - transparent_bsdf = mat.node_tree.nodes.new(type='ShaderNodeBsdfTransparent') - transparent_bsdf.name = 'MMDH_transparent_bsdf' - transparent_bsdf.label = transparent_bsdf.name - transparent_bsdf.location.x = hue_sat.location.x - transparent_bsdf.location.y = hue_sat.location.y + 300 - - # Create the mix shader node if it doesnt exist - mix_shader = None - for node in mat.node_tree.nodes: - if node.name == 'MMDH_mix_shader': - mix_shader = node - break - if mix_shader == None: - mix_shader = mat.node_tree.nodes.new(type='ShaderNodeMixShader') - mix_shader.name = 'MMDH_mix_shader' - mix_shader.label = mix_shader.name - mix_shader.location.x = transparent_bsdf.location.x + 300 - mix_shader.location.y = transparent_bsdf.location.y - - # Find the material output node - material_output = None - for node in mat.node_tree.nodes: - if node.type == 'OUTPUT_MATERIAL': - material_output = node - material_output.location.x = hue_sat.location.x + 300 - material_output.location.y = hue_sat.location.y - break - - if material_output is None: - print("Material Output node not found.") - else: - # Connect the principled BSDF node to the shader to RGB node - mat.node_tree.links.new(principled.outputs[0], shader_to_rgb.inputs[0]) - - # Connect the shader to RGB node to the color ramp node - mat.node_tree.links.new(shader_to_rgb.outputs[0], color_ramp.inputs[0]) - - # Connect the color ramp node to the mix node - mat.node_tree.links.new(color_ramp.outputs[0], mix_rgb.inputs[1]) - - # Connect the mix node to the hue&saturation output node - mat.node_tree.links.new(mix_rgb.outputs[0], hue_sat.inputs[4]) - - # Connect shader_to_rgb alpha channel to mix_shader node - mat.node_tree.links.new(shader_to_rgb.outputs[1], mix_shader.inputs[0]) - - # Connect transparent_BSDF to mix_shader node - mat.node_tree.links.new(transparent_bsdf.outputs[0], mix_shader.inputs[1]) - - #Connect the hue saturation node to the mix_shader node - mat.node_tree.links.new(hue_sat.outputs[0], mix_shader.inputs[2]) - - - #Connect mix_shader to material_outputs node - mat.node_tree.links.new(mix_shader.outputs[0], material_output.inputs[0]) - - - # check for a base color input on the principled BSDF node - - - if 'Base Color' in principled.inputs: - - # Get the node connected to the base color input - base_color_input = None - for link in mat.node_tree.links: - if link.to_node == principled and link.to_socket == principled.inputs[0]: - base_color_input = link.from_node - break - - if base_color_input is not None: - - base_color_input.location.x = shader_to_rgb.location.x - base_color_input.location.y = shader_to_rgb.location.y - 300 - - - # create toon modifier node if it doesn't exist - toon_modifier = None - for node in mat.node_tree.nodes: - if node.name == 'MMDH_toon_modifier': - toon_modifier = node - break - if toon_modifier == None: - toon_modifier = mat.node_tree.nodes.new(type='ShaderNodeMath') - toon_modifier.name = 'MMDH_toon_modifier' - toon_modifier.label = toon_modifier.name - toon_modifier.operation = 'MULTIPLY' - toon_modifier.location.y = mix_rgb.location.y - 300 - toon_modifier.location.x = mix_rgb.location.x - - #connect base color input to mix_rgb input 1 - mat.node_tree.links.new(base_color_input.outputs[0], mix_rgb.inputs[1]) - # connect base color input to toon_modifier - mat.node_tree.links.new(base_color_input.outputs[0], toon_modifier.inputs[0]) - # connect toon_modifier to mix_rgb node input 2 - mat.node_tree.links.new(toon_modifier.outputs[0], mix_rgb.inputs[2]) - - # Find the material output node - material_output = None - for node in mat.node_tree.nodes: - if node.type == 'OUTPUT_MATERIAL': - material_output = node - break - - if material_output is None: - print("Material Output node not found.") - else: - # Connect the shader to RGB node to the color ramp node - mat.node_tree.links.new(shader_to_rgb.outputs[0], color_ramp.inputs[0]) - - # Connect the color ramp node to the mix_rgb node - mat.node_tree.links.new(color_ramp.outputs[0], mix_rgb.inputs[0]) - - # Connect the mix node to the hue_sat input node - #mat.node_tree.links.new(mix.outputs[0], hue_sat.inputs[0]) - - else: - print("No node connected to the base color input of the principled BSDF node") - -@register_wrap -class MMDToonTexturesToNodeEditorShader(bpy.types.Operator): - """Sets up nodes in Blender node editor for rendering toon textures""" - bl_idname = "ffxiv_mmd.mmd_toon_render_node_editor" - bl_label = "MMD toon textures render using node editor " - bl_options = {'REGISTER', 'UNDO'} - - # @classmethod - # def poll(cls, context): - # return context.active_object is not None - - def execute(self, context): - - """ - mesh_objects_list = model.find_MMD_MeshesList(bpy.context.active_object) - assert(mesh_objects_list is not None), "The active object is not an MMD model." - for o in mesh_objects_list: - bpy.context.view_layer.objects.active = o - clear_material_nodes(context) - # create_default_material_nodes(context) - main(context) - """ - main(context) - - return {'FINISHED'} \ No newline at end of file