Created
May 2, 2025 23:39
-
-
Save Pakmanv/12014b03fe68d8144e5f6b73a72114d9 to your computer and use it in GitHub Desktop.
TextureDebugVV0.5.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import maya.cmds as cmds | |
| import maya.mel as mel | |
| import maya.OpenMaya as OpenMaya | |
| import os | |
| from functools import partial | |
| import time | |
| import subprocess | |
| import re | |
| class DirectoryTool(object): | |
| def __init__(self): | |
| self.window_name = "TextureDebug" | |
| self.current_dir = "" | |
| self.history = [] | |
| self.max_history = 10 | |
| self.image_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.tga', '.tiff', '.gif', '.exr', '.hdr'} | |
| self.preview_size = 256 | |
| self.temp_texture = "temp_thumb" | |
| self.temp_file = None | |
| self.current_preview_path = None | |
| self.selected_node = None | |
| self.auto_apply = False | |
| self.cached_thumb_path = None | |
| self.file_list_height = 220 | |
| self.node_list_height = 220 | |
| self.history_file = os.path.join(os.path.expanduser("~/Documents"), "vvtoolbag", "history", "texturedebugvv01.txt") | |
| self.delete_unused_nodes = True | |
| self.refresh_preview = True | |
| self.thumbnail_size = True # True = scale to preview_size, False = native size | |
| self.auto_create_file = True | |
| self.shader_tabs = {} | |
| self.search_field = None # Initialize as None to avoid attribute errors | |
| def create_ui(self): | |
| if cmds.window(self.window_name, exists=True): | |
| cmds.deleteUI(self.window_name) | |
| window = cmds.window(self.window_name, title="Texture Debug VV 0.5", widthHeight=(700, 1080)) | |
| cmds.menuBarLayout() | |
| cmds.menu(label="Tools") | |
| cmds.menuItem(label="Batch Export", command=self.open_batch_export_window) | |
| cmds.columnLayout(adjustableColumn=True, rowSpacing=5) | |
| cmds.rowLayout(numberOfColumns=4, | |
| columnWidth4=(80, 400, 80, 80), | |
| adjustableColumn=2, | |
| columnAttach4=('left', 'both', 'right', 'right')) | |
| cmds.text(label="Directory: ") | |
| self.dir_field = cmds.textFieldGrp(changeCommand=self.update_directory, editable=True, adjustableColumn=True) | |
| self.history_popup = cmds.popupMenu(parent=self.dir_field, button=3) | |
| cmds.button(label="Open Source", command=self.open_source, width=80) | |
| self.find_source_button = cmds.button(label="Find Source", command=self.find_source, width=80) | |
| self.find_source_popup = cmds.popupMenu(parent=self.find_source_button, button=3) | |
| cmds.menuItem(parent=self.find_source_popup, label="Create Source Directory", | |
| command=self.create_source_directory) | |
| cmds.setParent("..") | |
| self.file_list = cmds.iconTextScrollList( | |
| height=self.file_list_height, | |
| allowMultiSelection=True, | |
| selectCommand=self.on_file_select, | |
| doubleClickCommand=self.rename_file, | |
| highlightColor=[0.5, 0.5, 0.7] | |
| ) | |
| self.file_list_popup = cmds.popupMenu(parent=self.file_list, button=3) | |
| cmds.menuItem(parent=self.file_list_popup, label="Open File", command=self.open_selected_file) | |
| cmds.menuItem(parent=self.file_list_popup, label="Open File Location", command=self.open_file_location) | |
| cmds.menuItem(parent=self.file_list_popup, label="Batch Rename", command=self.open_batch_rename) | |
| cmds.menuItem(parent=self.file_list_popup, label="Increase Height", | |
| command=partial(self.adjust_list_height, self.file_list, 50)) | |
| cmds.menuItem(parent=self.file_list_popup, label="Decrease Height", | |
| command=partial(self.adjust_list_height, self.file_list, -50)) | |
| cmds.menuItem(parent=self.file_list_popup, label="Reload", command=self.reload_file_list) | |
| cmds.rowLayout(numberOfColumns=2, adjustableColumn=1) | |
| cmds.text(label="") | |
| self.file_count_label = cmds.text(label="Items: 0", align="right") | |
| cmds.setParent("..") | |
| cmds.rowLayout(numberOfColumns=3, adjustableColumn=2, columnWidth3=(260, 140, 200)) | |
| self.size_menu = cmds.optionMenuGrp(label="Preview Size: ", changeCommand=self.update_preview_size, columnAlign=(1, "left")) | |
| cmds.menuItem(label="128x128", parent=self.size_menu + "|OptionMenu") | |
| cmds.menuItem(label="256x256", parent=self.size_menu + "|OptionMenu") | |
| cmds.menuItem(label="512x512", parent=self.size_menu + "|OptionMenu") | |
| cmds.optionMenuGrp(self.size_menu, edit=True, value="256x256") | |
| cmds.text(label="") | |
| cmds.rowLayout(numberOfColumns=3, adjustableColumn=3) | |
| self.refresh_preview_checkbox = cmds.checkBox(label="Refresh Preview", value=self.refresh_preview, | |
| changeCommand=self.toggle_refresh_preview) | |
| self.thumbnail_size_checkbox = cmds.checkBox(label="Thumbnail Size", value=self.thumbnail_size, | |
| changeCommand=self.toggle_thumbnail_size) | |
| self.delete_nodes_checkbox = cmds.checkBox(label="Delete Unused Shader Nodes", value=self.delete_unused_nodes, | |
| changeCommand=self.toggle_delete_unused_nodes) | |
| cmds.setParent("..") | |
| cmds.setParent("..") | |
| cmds.rowLayout(numberOfColumns=2, columnWidth2=(self.preview_size + 20, 250), adjustableColumn=2) | |
| self.preview_image = cmds.image(width=self.preview_size, height=self.preview_size, backgroundColor=[0.2, 0.2, 0.2], visible=True) | |
| cmds.columnLayout(adjustableColumn=True, rowSpacing=2) | |
| self.info_name = cmds.text(label="Name: ") | |
| self.info_size = cmds.text(label="Size: ") | |
| self.info_format = cmds.text(label="Format: ") | |
| self.info_resolution = cmds.text(label="Resolution: ") | |
| self.info_date = cmds.text(label="Date Created: ") | |
| self.info_creator = cmds.text(label="Creator: ") | |
| self.info_compression = cmds.text(label="Compression: ") | |
| cmds.setParent("..") | |
| cmds.setParent("..") | |
| self.path_display = cmds.textField(text="", editable=False, backgroundColor=[0.2, 0.2, 0.2], height=20) | |
| self.path_popup = cmds.popupMenu(parent=self.path_display, button=3) | |
| cmds.menuItem(parent=self.path_popup, label="Open File", command=self.open_current_preview) | |
| cmds.menuItem(parent=self.path_popup, label="Open File Location", command=self.open_current_preview_location) | |
| cmds.menuItem(parent=self.path_popup, label="Copy Path", command=self.copy_current_path) | |
| cmds.separator(height=10, style='in') | |
| cmds.rowLayout(numberOfColumns=2, adjustableColumn=1) | |
| cmds.text(label="Direct Assign", font="boldLabelFont") | |
| self.node_count_label = cmds.text(label="Items: 0", align="right") | |
| cmds.setParent("..") | |
| # Move search_field earlier to avoid attribute error | |
| self.search_field = cmds.textField(placeholderText="Search...", textChangedCommand=self.update_lists_with_search) | |
| cmds.columnLayout(adjustableColumn=True, rowSpacing=5) | |
| self.tab_layout = cmds.tabLayout(innerMarginWidth=5, innerMarginHeight=5) | |
| self.tab_popup = cmds.popupMenu(parent=self.tab_layout, button=3) | |
| cmds.menuItem(parent=self.tab_popup, label="Add Shader File Tab", subMenu=True) | |
| channels = ["Albedo", "Bump", "Transparency", "AmbientColor", "Specular", "Incandescence", "Diffuse"] | |
| for channel in channels: | |
| cmds.menuItem(label=channel, parent=self.tab_popup, | |
| command=partial(self.add_shader_tab, channel)) | |
| # File Tab | |
| self.file_tab = cmds.columnLayout(adjustableColumn=True) | |
| self.file_node_list = cmds.iconTextScrollList( | |
| height=self.node_list_height, | |
| allowMultiSelection=False, | |
| selectCommand=self.on_file_node_select, | |
| highlightColor=[0.5, 0.7, 0.5] | |
| ) | |
| self.file_node_popup = cmds.popupMenu(parent=self.file_node_list, button=3) | |
| cmds.menuItem(parent=self.file_node_popup, label="Open File Location", command=self.open_file_node_file_location) | |
| cmds.menuItem(parent=self.file_node_popup, label="Increase Height", | |
| command=partial(self.adjust_list_height, self.file_node_list, 50)) | |
| cmds.menuItem(parent=self.file_node_popup, label="Decrease Height", | |
| command=partial(self.adjust_list_height, self.file_node_list, -50)) | |
| cmds.menuItem(parent=self.file_node_popup, label="Reload", command=self.reload_file_node_list) | |
| cmds.setParent("..") | |
| # Initial Shader_Albedo Tab | |
| self.add_shader_tab("Albedo") | |
| cmds.tabLayout(self.tab_layout, edit=True, | |
| tabLabel=((self.file_tab, "File"), (self.shader_tabs["Albedo"]["layout"], "Shader_Albedo"))) | |
| cmds.setParent("..") | |
| # Shared Controls | |
| self.auto_create_checkbox = cmds.checkBox(label="Auto Create File", value=self.auto_create_file, | |
| changeCommand=self.toggle_auto_create_file) | |
| self.selected_node_field = cmds.textField(text="No Node Selected", editable=False, | |
| backgroundColor=[0.2, 0.2, 0.2], height=20) | |
| cmds.rowLayout(numberOfColumns=2, adjustableColumn=2) | |
| self.auto_apply_checkbox = cmds.checkBox(label="Automatic Apply", value=self.auto_apply, | |
| changeCommand=self.toggle_auto_apply) | |
| self.assign_button = cmds.button(label="Assign Selected Image", command=self.assign_image_to_node, | |
| enable=not self.auto_apply) | |
| cmds.setParent("..") | |
| cmds.separator(height=10, style='in') | |
| cmds.text(label="Texture Debug 1.0 created by Vypac Voeur. Please do not distribute.", | |
| font="smallObliqueLabelFont", align="center") | |
| cmds.showWindow(window) | |
| self.load_initial_directory() | |
| self.update_file_node_list() | |
| self.update_shader_list("Albedo") | |
| def add_shader_tab(self, channel, *args): | |
| if channel in self.shader_tabs: | |
| return | |
| tab_layout = cmds.columnLayout(adjustableColumn=True) | |
| note = cmds.text(label="Shader list view will auto-create a file connection.", align="center") | |
| shader_list = cmds.iconTextScrollList( | |
| height=self.node_list_height, | |
| allowMultiSelection=False, | |
| selectCommand=partial(self.on_shader_select, channel), | |
| highlightColor=[0.5, 0.7, 0.5] | |
| ) | |
| shader_popup = cmds.popupMenu(parent=shader_list, button=3) | |
| cmds.menuItem(parent=shader_popup, label="Open File Location", | |
| command=partial(self.open_shader_file_location, channel)) | |
| cmds.menuItem(parent=shader_popup, label="Increase Height", | |
| command=partial(self.adjust_list_height, shader_list, 50)) | |
| cmds.menuItem(parent=shader_popup, label="Decrease Height", | |
| command=partial(self.adjust_list_height, shader_list, -50)) | |
| cmds.menuItem(parent=shader_popup, label="Reload", | |
| command=partial(self.reload_shader_list, channel)) | |
| cmds.setParent("..") | |
| self.shader_tabs[channel] = { | |
| "layout": tab_layout, | |
| "list": shader_list, | |
| "channel": channel.lower() if channel != "Bump" else "normalCamera" | |
| } | |
| tab_labels = [(self.file_tab, "File")] + [(tab["layout"], f"Shader_{channel}") for channel, tab in self.shader_tabs.items()] | |
| cmds.tabLayout(self.tab_layout, edit=True, tabLabel=tab_labels) | |
| self.update_shader_list(channel) | |
| def open_batch_export_window(self, *args): | |
| if cmds.window("BatchExportWindow", exists=True): | |
| cmds.deleteUI("BatchExportWindow") | |
| batch_window = cmds.window("BatchExportWindow", title="Batch Export", widthHeight=(600, 350)) | |
| cmds.columnLayout(adjustableColumn=True, rowSpacing=5) | |
| cmds.text(label="Export all images in current directory", font="boldLabelFont") | |
| cmds.separator(height=10) | |
| self.export_format = cmds.optionMenuGrp(label="Export Format: ") | |
| for fmt in ["PNG", "JPEG", "TGA", "TIFF", "BMP", "EXR", "HDR", "IFF", "SGI", "PIC", "PSD"]: | |
| cmds.menuItem(label=fmt) | |
| self.export_size = cmds.optionMenuGrp(label="Export Size: ") | |
| for size in ["128x128", "256x256", "512x512", "1024x1024", "2048x2048", "4096x4096", "8192x8192"]: | |
| cmds.menuItem(label=size) | |
| self.export_dir_field = cmds.textFieldButtonGrp( | |
| label="Export Directory: ", | |
| buttonLabel="Browse", | |
| buttonCommand=self.browse_export_directory, | |
| adjustableColumn=True, | |
| columnWidth=[(1, 100), (2, 450), (3, 80)] | |
| ) | |
| self.progress_bar = cmds.progressBar(maxValue=100, width=300) | |
| self.progress_label = cmds.text(label="") | |
| cmds.button(label="Export", command=self.perform_batch_export) | |
| cmds.button(label="Cancel", command=lambda x: cmds.deleteUI(batch_window)) | |
| cmds.showWindow(batch_window) | |
| def browse_export_directory(self, *args): | |
| directory = cmds.fileDialog2( | |
| fileMode=3, | |
| caption="Select Export Directory", | |
| okCaption="Select", | |
| dialogStyle=2 | |
| ) | |
| if directory: | |
| cmds.textFieldButtonGrp(self.export_dir_field, edit=True, text=directory[0]) | |
| def perform_batch_export(self, *args): | |
| export_dir = cmds.textFieldButtonGrp(self.export_dir_field, query=True, text=True) | |
| if not export_dir or not os.path.isdir(export_dir): | |
| cmds.warning("Please select a valid export directory") | |
| return | |
| format_str = cmds.optionMenuGrp(self.export_format, query=True, value=True).lower() | |
| size_str = cmds.optionMenuGrp(self.export_size, query=True, value=True) | |
| size = int(size_str.split('x')[0]) | |
| image_files = [f for f in os.listdir(self.current_dir) | |
| if os.path.splitext(f.lower())[1] in self.image_extensions] | |
| total_files = len(image_files) | |
| if total_files == 0: | |
| cmds.warning("No images found to export") | |
| return | |
| cmds.progressBar(self.progress_bar, edit=True, maxValue=total_files, progress=0) | |
| cmds.text(self.progress_label, edit=True, label="Exporting...") | |
| for i, filename in enumerate(image_files): | |
| input_path = os.path.join(self.current_dir, filename) | |
| output_name = os.path.splitext(filename)[0] + f"_exported.{format_str}" | |
| output_path = os.path.join(export_dir, output_name) | |
| try: | |
| temp_node = cmds.shadingNode('file', asTexture=True, isColorManaged=True) | |
| cmds.setAttr(f"{temp_node}.fileTextureName", input_path, type="string") | |
| cmds.convertSolidTx(temp_node, | |
| samplePlane=True, | |
| rx=size, | |
| ry=size, | |
| fileFormat=format_str, | |
| fileImageName=output_path) | |
| cmds.delete(temp_node) | |
| cmds.progressBar(self.progress_bar, edit=True, step=1) | |
| cmds.refresh() | |
| except Exception as e: | |
| print(f"Failed to export {filename}: {e}") | |
| cmds.progressBar(self.progress_bar, edit=True, progress=0) | |
| cmds.text(self.progress_label, edit=True, label="Export Completed") | |
| print(f"Batch export completed: {total_files} files exported to {export_dir}") | |
| def open_batch_rename(self, *args): | |
| selected_files = cmds.iconTextScrollList(self.file_list, query=True, selectItem=True) | |
| if not selected_files: | |
| cmds.warning("No files selected for batch rename!") | |
| return | |
| if cmds.window('vvBatchRenameWin', exists=True): | |
| cmds.deleteUI('vvBatchRenameWin') | |
| cmds.window('vvBatchRenameWin', title="Batch Rename by Comet", widthHeight=(310, 400)) | |
| cmds.columnLayout(adjustableColumn=True) | |
| cmds.separator(style='in', height=8) | |
| cmds.rowColumnLayout(numberOfColumns=2, columnWidth=[(1, 50), (2, 250)]) | |
| cmds.text(label="Search:", align="right") | |
| self.search_field_rename = cmds.textField('tfSearch') | |
| cmds.setParent('..') | |
| cmds.rowColumnLayout(numberOfColumns=2, columnWidth=[(1, 50), (2, 250)]) | |
| cmds.text(label="Replace:", align="right") | |
| self.replace_field = cmds.textField('tfReplace') | |
| cmds.setParent('..') | |
| cmds.separator(style='none', height=4) | |
| cmds.button(label="Search And Replace", command=lambda *args: self.do_batch_rename(0)) | |
| cmds.separator(style='none', height=10) | |
| cmds.separator(style='in', height=8) | |
| cmds.rowColumnLayout(numberOfColumns=2, columnWidth=[(1, 50), (2, 250)]) | |
| cmds.text(label="Prefix:", align="right") | |
| self.prefix_field = cmds.textField('tfPrefix') | |
| cmds.setParent('..') | |
| cmds.separator(style='none', height=4) | |
| cmds.button(label="Add Prefix", command=lambda *args: self.do_batch_rename(1)) | |
| cmds.separator(style='none', height=10) | |
| cmds.separator(style='in', height=8) | |
| cmds.rowColumnLayout(numberOfColumns=2, columnWidth=[(1, 50), (2, 250)]) | |
| cmds.text(label="Suffix:", align="right") | |
| self.suffix_field = cmds.textField('tfSuffix') | |
| cmds.setParent('..') | |
| cmds.separator(style='none', height=4) | |
| cmds.button(label="Add Suffix", command=lambda *args: self.do_batch_rename(2)) | |
| cmds.separator(style='none', height=10) | |
| cmds.separator(style='in', height=8) | |
| cmds.rowColumnLayout(numberOfColumns=2, columnWidth=[(1, 50), (2, 250)]) | |
| cmds.text(label="Rename:", align="right") | |
| self.rename_field = cmds.textField('tfRename') | |
| cmds.setParent('..') | |
| cmds.rowColumnLayout(numberOfColumns=2, columnWidth=[(1, 50), (2, 60)]) | |
| cmds.text(label="Start #:", align="right") | |
| self.start_field = cmds.intField('ifNumber', value=1, minValue=0) | |
| cmds.setParent('..') | |
| cmds.rowColumnLayout(numberOfColumns=2, columnWidth=[(1, 50), (2, 60)]) | |
| cmds.text(label="Padding:", align="right") | |
| self.padding_field = cmds.intField('ifPadding', value=0, minValue=0) | |
| cmds.setParent('..') | |
| cmds.separator(style='none', height=4) | |
| cmds.button(label="Rename And Number", command=lambda *args: self.do_batch_rename(3)) | |
| cmds.separator(style='in', height=8) | |
| cmds.showWindow('vvBatchRenameWin') | |
| def do_batch_rename(self, mode): | |
| selected_files = cmds.iconTextScrollList(self.file_list, query=True, selectItem=True) | |
| if not selected_files: | |
| return | |
| search = cmds.textField(self.search_field_rename, query=True, text=True) if mode == 0 else "" | |
| replace = cmds.textField(self.replace_field, query=True, text=True) if mode == 0 else "" | |
| prefix = cmds.textField(self.prefix_field, query=True, text=True) if mode == 1 else "" | |
| suffix = cmds.textField(self.suffix_field, query=True, text=True) if mode == 2 else "" | |
| rename = cmds.textField(self.rename_field, query=True, text=True) if mode == 3 else "" | |
| start = cmds.intField(self.start_field, query=True, value=True) if mode == 3 else 0 | |
| padding = cmds.intField(self.padding_field, query=True, value=True) if mode == 3 else 0 | |
| for i, file_name in enumerate(selected_files): | |
| old_path = os.path.join(self.current_dir, file_name) | |
| base, ext = os.path.splitext(file_name) | |
| new_name = base | |
| if mode == 0: # Search and Replace | |
| if not search: | |
| cmds.warning("Search field is empty!") | |
| return | |
| new_name = re.sub(search, replace, base) | |
| elif mode == 1: # Prefix | |
| if not prefix: | |
| cmds.warning("Prefix field is empty!") | |
| return | |
| new_name = f"{prefix}{base}" | |
| elif mode == 2: # Suffix | |
| if not suffix: | |
| cmds.warning("Suffix field is empty!") | |
| return | |
| new_name = f"{base}{suffix}" | |
| elif mode == 3: # Rename and Number | |
| if not rename: | |
| cmds.warning("Rename field is empty!") | |
| return | |
| number = start + i | |
| padded_number = f"{number:0{padding}d}" | |
| new_name = f"{rename}{padded_number}" | |
| new_path = os.path.join(self.current_dir, f"{new_name}{ext}") | |
| try: | |
| os.rename(old_path, new_path) | |
| print(f"Renamed '{old_path}' to '{new_path}'") | |
| except Exception as e: | |
| print(f"Error renaming '{old_path}': {e}") | |
| self.update_file_list() | |
| def rename_file(self, *args): | |
| selected_file = cmds.iconTextScrollList(self.file_list, query=True, selectItem=True) | |
| if selected_file and len(selected_file) == 1: | |
| old_path = os.path.join(self.current_dir, selected_file[0]) | |
| new_name = cmds.promptDialog( | |
| title="Rename File", | |
| message="Enter new name:", | |
| text=selected_file[0], | |
| button=["OK", "Cancel"], | |
| defaultButton="OK", | |
| cancelButton="Cancel", | |
| dismissString="Cancel" | |
| ) | |
| if new_name == "OK": | |
| new_name = cmds.promptDialog(query=True, text=True) | |
| new_path = os.path.join(self.current_dir, new_name) | |
| try: | |
| os.rename(old_path, new_path) | |
| print(f"Renamed '{old_path}' to '{new_path}'") | |
| self.update_file_list() | |
| except Exception as e: | |
| print(f"Error renaming '{old_path}': {e}") | |
| def open_source(self, *args): | |
| directory = cmds.fileDialog2( | |
| fileMode=3, | |
| caption="Select Directory", | |
| okCaption="Select", | |
| dialogStyle=2 | |
| ) | |
| if directory: | |
| self.set_directory(directory[0]) | |
| self.save_history() | |
| def find_source(self, *args): | |
| current_file = cmds.file(query=True, sceneName=True) | |
| if not current_file or not os.path.exists(current_file): | |
| cmds.iconTextScrollList(self.file_list, edit=True, removeAll=True) | |
| cmds.iconTextScrollList(self.file_list, edit=True, append="Warning: No valid scene file is open!") | |
| cmds.text(self.file_count_label, edit=True, label="Items: 1") | |
| return | |
| scene_dir = os.path.dirname(current_file) | |
| if "scenes" not in scene_dir.lower(): | |
| cmds.iconTextScrollList(self.file_list, edit=True, removeAll=True) | |
| cmds.iconTextScrollList(self.file_list, edit=True, append="Warning: Scene path does not contain 'scenes' directory!") | |
| cmds.text(self.file_count_label, edit=True, label="Items: 1") | |
| return | |
| source_dir = scene_dir.lower().replace("scenes", "sourceimages") | |
| source_dir = os.path.normpath(source_dir) | |
| if not os.path.isdir(source_dir): | |
| scene_parts = scene_dir.split(os.sep) | |
| original_drive = scene_parts[0] if scene_parts[0].endswith(":") else "O:" | |
| for drive in ["O:", "P:", "C:", "D:"]: | |
| if drive != original_drive: | |
| alt_source_dir = os.path.join(drive, *scene_parts[1:]).lower().replace("scenes", "sourceimages") | |
| alt_source_dir = os.path.normpath(alt_source_dir) | |
| if os.path.isdir(alt_source_dir): | |
| source_dir = alt_source_dir | |
| break | |
| else: | |
| cmds.iconTextScrollList(self.file_list, edit=True, removeAll=True) | |
| cmds.iconTextScrollList(self.file_list, edit=True, append=f"Cannot locate: {source_dir}") | |
| cmds.text(self.file_count_label, edit=True, label="Items: 1") | |
| return | |
| self.set_directory(source_dir) | |
| print(f"Set source directory to: {source_dir}") | |
| self.save_history() | |
| def create_source_directory(self, *args): | |
| current_file = cmds.file(query=True, sceneName=True) | |
| if not current_file or not os.path.exists(current_file): | |
| cmds.warning("No valid scene file is open to create a source directory!") | |
| return | |
| scene_dir = os.path.dirname(current_file) | |
| if "scenes" not in scene_dir.lower(): | |
| cmds.warning("Scene path does not contain 'scenes' directory!") | |
| return | |
| source_dir = scene_dir.lower().replace("scenes", "sourceimages") | |
| source_dir = os.path.normpath(source_dir) | |
| if os.path.isdir(source_dir): | |
| print(f"Source directory already exists: {source_dir}") | |
| self.set_directory(source_dir) | |
| return | |
| try: | |
| os.makedirs(source_dir, exist_ok=True) | |
| print(f"Created source directory: {source_dir}") | |
| self.set_directory(source_dir) | |
| self.save_history() | |
| except Exception as e: | |
| cmds.warning(f"Failed to create source directory {source_dir}: {e}") | |
| def update_directory(self, *args): | |
| new_dir = cmds.textFieldGrp(self.dir_field, query=True, text=True) | |
| if os.path.isdir(new_dir): | |
| self.set_directory(new_dir) | |
| def set_directory(self, directory): | |
| self.current_dir = directory | |
| cmds.textFieldGrp(self.dir_field, edit=True, text=directory) | |
| if directory in self.history: | |
| self.history.remove(directory) | |
| self.history.insert(0, directory) | |
| if len(self.history) > self.max_history: | |
| self.history.pop() | |
| self.update_history_menu() | |
| self.update_file_list() | |
| def update_history_menu(self): | |
| cmds.popupMenu(self.history_popup, edit=True, deleteAllItems=True) | |
| for dir_path in self.history: | |
| cmds.menuItem(label=dir_path, | |
| parent=self.history_popup, | |
| command=partial(self.set_directory_from_menu, dir_path)) | |
| def set_directory_from_menu(self, directory, *args): | |
| self.set_directory(directory) | |
| def update_file_list(self): | |
| cmds.iconTextScrollList(self.file_list, edit=True, removeAll=True) | |
| item_count = 0 | |
| if os.path.isdir(self.current_dir): | |
| try: | |
| files = os.listdir(self.current_dir) | |
| files.sort() | |
| for file_name in files: | |
| full_path = os.path.normpath(os.path.join(self.current_dir, file_name)) | |
| if os.path.isfile(full_path): | |
| _, ext = os.path.splitext(file_name.lower()) | |
| if ext in self.image_extensions: | |
| try: | |
| cmds.iconTextScrollList(self.file_list, edit=True, append=file_name) | |
| item_count += 1 | |
| except Exception as e: | |
| print(f"Failed to add {full_path}: {e}") | |
| self.update_preview(None) | |
| except Exception as e: | |
| print(f"Error reading directory: {e}") | |
| cmds.text(self.file_count_label, edit=True, label=f"Items: {item_count}") | |
| def reload_file_list(self, *args): | |
| self.update_file_list() | |
| print("File list reloaded.") | |
| def on_file_select(self, *args): | |
| selected_file = cmds.iconTextScrollList(self.file_list, query=True, selectItem=True) | |
| if selected_file and len(selected_file) == 1: | |
| full_path = os.path.normpath(os.path.join(self.current_dir, selected_file[0])) | |
| if os.path.isfile(full_path): | |
| _, ext = os.path.splitext(full_path.lower()) | |
| if ext in self.image_extensions: | |
| new_dir = os.path.dirname(full_path) | |
| self.set_directory(new_dir) | |
| self.current_preview_path = full_path | |
| self.update_preview(full_path) | |
| cmds.iconTextScrollList(self.file_list, edit=True, selectItem=selected_file[0]) | |
| if self.auto_apply and self.selected_node: | |
| self.assign_image_to_node() | |
| else: | |
| self.current_preview_path = None | |
| self.update_preview(None) | |
| cmds.iconTextScrollList(self.file_list, edit=True, selectItem=selected_file[0]) | |
| if self.delete_unused_nodes: | |
| mel.eval('hyperShadePanelMenuCommand("hyperShadePanel1", "deleteUnusedNodes");') | |
| print("Cleaned up unused nodes via MEL command.") | |
| def open_selected_file(self, *args): | |
| selected_file = cmds.iconTextScrollList(self.file_list, query=True, selectItem=True) | |
| if selected_file: | |
| full_path = os.path.normpath(os.path.join(self.current_dir, selected_file[0])) | |
| if os.path.isfile(full_path): | |
| try: | |
| if os.name == 'nt': | |
| os.startfile(full_path) | |
| elif os.name == 'posix': | |
| subprocess.call(['open', full_path] if os.uname().sysname == 'Darwin' else ['xdg-open', full_path]) | |
| except Exception as e: | |
| print(f"Failed to open file {full_path}: {e}") | |
| def open_file_location(self, *args): | |
| selected_file = cmds.iconTextScrollList(self.file_list, query=True, selectItem=True) | |
| if selected_file: | |
| full_path = os.path.normpath(os.path.join(self.current_dir, selected_file[0])) | |
| if os.path.isfile(full_path): | |
| directory = os.path.dirname(full_path) | |
| try: | |
| if os.name == 'nt': | |
| os.startfile(directory) | |
| elif os.name == 'posix': | |
| subprocess.call(['open', directory] if os.uname().sysname == 'Darwin' else ['xdg-open', directory]) | |
| print(f"Opened file location: {directory}") | |
| except Exception as e: | |
| print(f"Failed to open file location {directory}: {e}") | |
| def open_current_preview(self, *args): | |
| if self.current_preview_path and os.path.isfile(self.current_preview_path): | |
| try: | |
| if os.name == 'nt': | |
| os.startfile(self.current_preview_path) | |
| elif os.name == 'posix': | |
| subprocess.call(['open', self.current_preview_path] if os.uname().sysname == 'Darwin' else ['xdg-open', self.current_preview_path]) | |
| except Exception as e: | |
| print(f"Failed to open file {self.current_preview_path}: {e}") | |
| def open_current_preview_location(self, *args): | |
| if self.current_preview_path and os.path.isfile(self.current_preview_path): | |
| directory = os.path.dirname(self.current_preview_path) | |
| try: | |
| if os.name == 'nt': | |
| os.startfile(directory) | |
| elif os.name == 'posix': | |
| subprocess.call(['open', directory] if os.uname().sysname == 'Darwin' else ['xdg-open', directory]) | |
| print(f"Opened file location: {directory}") | |
| except Exception as e: | |
| print(f"Failed to open file location {directory}: {e}") | |
| def copy_current_path(self, *args): | |
| if self.current_preview_path: | |
| OpenMaya.MGlobal.executeCommand(f'setClipboard("{self.current_preview_path}")') | |
| print(f"Copied path to clipboard: {self.current_preview_path}") | |
| else: | |
| print("No path to copy.") | |
| def generate_thumbnail(self, file_path): | |
| if not self.thumbnail_size: # False means native size, no scaling | |
| return file_path | |
| try: | |
| if self.temp_texture and cmds.objExists(self.temp_texture): | |
| connections = cmds.listConnections(self.temp_texture, source=False, destination=True) | |
| if connections: | |
| for conn in connections: | |
| node = conn.split('.')[0] | |
| shader_types = ['lambert', 'phong', 'blinn', 'standardSurface'] | |
| if cmds.nodeType(node) in shader_types: | |
| new_name = f"{node}_file" | |
| cmds.rename(self.temp_texture, new_name) | |
| self.temp_texture = new_name | |
| print(f"Renamed temp_texture to {self.temp_texture} due to shader connection") | |
| break | |
| else: | |
| shading_engines = cmds.listConnections(node, type='shadingEngine') | |
| if shading_engines: | |
| materials = cmds.listConnections(shading_engines[0], type=shader_types) | |
| if materials: | |
| new_name = f"{materials[0]}_file" | |
| cmds.rename(self.temp_texture, new_name) | |
| self.temp_texture = new_name | |
| print(f"Renamed temp_texture to {self.temp_texture} due to shader connection") | |
| break | |
| else: | |
| if not cmds.objExists("temp_thumb"): | |
| self.temp_texture = cmds.shadingNode('file', asTexture=True, isColorManaged=True, name="temp_thumb") | |
| else: | |
| self.temp_texture = "temp_thumb" | |
| cmds.setAttr(f"{self.temp_texture}.fileTextureName", file_path, type="string") | |
| if self.temp_file and os.path.exists(self.temp_file): | |
| os.remove(self.temp_file) | |
| temp_dir = cmds.internalVar(userTmpDir=True) | |
| self.temp_file = os.path.join(temp_dir, f"temp_thumb_{time.time()}.png") | |
| cmds.convertSolidTx(self.temp_texture, | |
| samplePlane=True, | |
| rx=self.preview_size, | |
| ry=self.preview_size, | |
| fileFormat="png", | |
| fileImageName=self.temp_file) | |
| return self.temp_file | |
| except Exception as e: | |
| print(f"Thumbnail generation failed: {e}") | |
| return None | |
| def get_image_resolution(self, file_path): | |
| try: | |
| temp_node = cmds.shadingNode('file', asTexture=True, isColorManaged=True) | |
| cmds.setAttr(f"{temp_node}.fileTextureName", file_path, type="string") | |
| width = cmds.getAttr(f"{temp_node}.outSizeX") | |
| height = cmds.getAttr(f"{temp_node}.outSizeY") | |
| cmds.delete(temp_node) | |
| return f"{int(width)}x{int(height)}" | |
| except Exception as e: | |
| print(f"Failed to get resolution for {file_path}: {e}") | |
| return "" | |
| def update_preview(self, file_path): | |
| if self.temp_file and os.path.exists(self.temp_file): | |
| os.remove(self.temp_file) | |
| self.temp_file = None | |
| self.cached_thumb_path = None | |
| cmds.textField(self.path_display, edit=True, text="") | |
| cmds.text(self.info_name, edit=True, label="Name: ") | |
| cmds.text(self.info_size, edit=True, label="Size: ") | |
| cmds.text(self.info_format, edit=True, label="Format: ") | |
| cmds.text(self.info_resolution, edit=True, label="Resolution: ") | |
| cmds.text(self.info_date, edit=True, label="Date Created: ") | |
| cmds.text(self.info_creator, edit=True, label="Creator: ") | |
| cmds.text(self.info_compression, edit=True, label="Compression: ") | |
| if file_path and os.path.isfile(file_path): | |
| _, ext = os.path.splitext(file_path.lower()) | |
| if ext in self.image_extensions: | |
| cmds.textField(self.path_display, edit=True, text=file_path) | |
| file_stats = os.stat(file_path) | |
| file_name = os.path.basename(file_path) | |
| creation_time = time.ctime(file_stats.st_ctime) | |
| file_size_kb = file_stats.st_size / 1024 | |
| file_size = f"{file_size_kb / 1024:.2f} MB" if file_size_kb >= 1000 else f"{file_size_kb:.2f} KB" | |
| resolution = self.get_image_resolution(file_path) if self.refresh_preview else "N/A" | |
| compression = "Unknown" | |
| if ext in {'.jpg', '.jpeg'}: | |
| compression = "JPEG" | |
| elif ext == '.png': | |
| compression = "Deflate" | |
| elif ext == '.gif': | |
| compression = "LZW" | |
| elif ext == '.exr': | |
| compression = "EXR" | |
| elif ext == '.hdr': | |
| compression = "HDR" | |
| cmds.text(self.info_name, edit=True, label=f"Name: {file_name}") | |
| cmds.text(self.info_size, edit=True, label=f"Size: {file_size}") | |
| cmds.text(self.info_format, edit=True, label=f"Format: {ext[1:].upper()}") | |
| cmds.text(self.info_resolution, edit=True, label=f"Resolution: {resolution}") | |
| cmds.text(self.info_date, edit=True, label=f"Date Created: {creation_time}") | |
| cmds.text(self.info_creator, edit=True, label="Creator: Unknown") | |
| cmds.text(self.info_compression, edit=True, label=f"Compression: {compression}") | |
| if self.refresh_preview: | |
| try: | |
| self.cached_thumb_path = self.generate_thumbnail(file_path) | |
| if self.cached_thumb_path and os.path.exists(self.cached_thumb_path): | |
| if self.thumbnail_size: # True means scale to preview_size | |
| cmds.image(self.preview_image, edit=True, | |
| image=self.cached_thumb_path, | |
| width=self.preview_size, | |
| height=self.preview_size) | |
| else: # False means native size | |
| cmds.image(self.preview_image, edit=True, image=self.cached_thumb_path) | |
| return | |
| except Exception as e: | |
| print(f"Failed to load preview for {file_path}: {e}") | |
| else: | |
| cmds.image(self.preview_image, edit=True, image="", annotation="Refresh Disabled") | |
| return | |
| cmds.image(self.preview_image, edit=True, image="") | |
| def update_preview_size(self, *args): | |
| if not self.thumbnail_size: # Skip if not scaling (native size mode) | |
| return | |
| selected_size = cmds.optionMenuGrp(self.size_menu, query=True, value=True) | |
| old_size = self.preview_size | |
| if selected_size == "128x128": | |
| self.preview_size = 128 | |
| elif selected_size == "256x256": | |
| self.preview_size = 256 | |
| elif selected_size == "512x512": | |
| self.preview_size = 512 | |
| if old_size != self.preview_size: | |
| self.cached_thumb_path = None | |
| cmds.image(self.preview_image, edit=True, | |
| width=self.preview_size, | |
| height=self.preview_size) | |
| if self.current_preview_path and self.refresh_preview: | |
| self.update_preview(self.current_preview_path) | |
| if self.delete_unused_nodes: | |
| mel.eval('hyperShadePanelMenuCommand("hyperShadePanel1", "deleteUnusedNodes");') | |
| print("Cleaned up unused nodes via MEL command after preview size change.") | |
| def load_initial_directory(self): | |
| if os.path.exists(self.history_file): | |
| try: | |
| with open(self.history_file, 'r') as f: | |
| self.history = [line.strip() for line in f.readlines() if os.path.isdir(line.strip())] | |
| if self.history: | |
| self.set_directory(self.history[0]) | |
| print(f"Loaded history from {self.history_file}") | |
| return | |
| except Exception as e: | |
| print(f"Failed to load history from {self.history_file}: {e}") | |
| current_file = cmds.file(query=True, sceneName=True) | |
| if current_file and os.path.exists(current_file): | |
| initial_dir = os.path.dirname(current_file) | |
| else: | |
| initial_dir = cmds.workspace(query=True, directory=True) | |
| if not initial_dir or not os.path.exists(initial_dir): | |
| initial_dir = cmds.internalVar(userPrefDir=True) | |
| self.set_directory(initial_dir) | |
| def save_history(self): | |
| os.makedirs(os.path.dirname(self.history_file), exist_ok=True) | |
| try: | |
| with open(self.history_file, 'w') as f: | |
| for dir_path in self.history: | |
| f.write(f"{dir_path}\n") | |
| print(f"Saved history to {self.history_file}") | |
| except Exception as e: | |
| print(f"Failed to save history to {self.history_file}: {e}") | |
| def update_file_node_list(self): | |
| search_text = cmds.textField(self.search_field, query=True, text=True).lower() if self.search_field else "" | |
| cmds.iconTextScrollList(self.file_node_list, edit=True, removeAll=True) | |
| file_nodes = cmds.ls(type='file') | |
| item_count = 0 | |
| for file_node in file_nodes: | |
| intermediate_nodes = [] | |
| channel = "" | |
| shader_name = "" | |
| connections = cmds.listConnections(file_node, plugs=True, source=False, destination=True) | |
| if connections: | |
| for conn in connections: | |
| node, attr = conn.split('.', 1) | |
| channel_mappings = { | |
| 'baseColor': 'baseColor', | |
| 'color': 'color', | |
| 'transparency': 'transparency', | |
| 'incandescence': 'incandescence', | |
| 'normalCamera': 'bump', | |
| 'specularColor': 'specular', | |
| 'diffuse': 'diffuse', | |
| 'ambientColor': 'ambientColor' | |
| } | |
| for key, value in channel_mappings.items(): | |
| if attr.lower() == key.lower(): | |
| channel = value | |
| break | |
| if cmds.nodeType(node) in ['bump2d', 'bump3d']: | |
| intermediate_nodes.append(node) | |
| next_conns = cmds.listConnections(node, plugs=True, source=False, destination=True) | |
| if next_conns: | |
| for next_conn in next_conns: | |
| next_node = next_conn.split('.')[0] | |
| if cmds.nodeType(next_node) in ['bump2d', "bump3d"]: | |
| intermediate_nodes.append(next_node) | |
| else: | |
| for shader_type in ['lambert', 'phong', 'blinn', 'standardSurface']: | |
| if cmds.nodeType(next_node) == shader_type: | |
| shader_name = next_node | |
| break | |
| if not shader_name: | |
| shading_engines = cmds.listConnections(next_node, type='shadingEngine') | |
| if shading_engines: | |
| for shader_type in ['lambert', 'phong', 'blinn', 'standardSurface']: | |
| materials = cmds.listConnections(shading_engines[0], type=shader_type) | |
| if materials: | |
| shader_name = materials[0] | |
| break | |
| parts = [] | |
| if shader_name: | |
| parts.append(shader_name) | |
| if channel: | |
| parts.append(channel) | |
| if intermediate_nodes: | |
| parts.append(" > ".join(intermediate_nodes)) | |
| parts.append(file_node) | |
| display_text = " > ".join(parts) | |
| if not search_text or search_text in display_text.lower(): | |
| cmds.iconTextScrollList(self.file_node_list, edit=True, append=display_text) | |
| item_count += 1 | |
| cmds.text(self.node_count_label, edit=True, label=f"Items: {item_count}") | |
| def update_shader_list(self, channel): | |
| search_text = cmds.textField(self.search_field, query=True, text=True).lower() if self.search_field else "" | |
| shader_list = self.shader_tabs[channel]["list"] | |
| cmds.iconTextScrollList(shader_list, edit=True, removeAll=True) | |
| shader_types = ['lambert', 'phong', 'blinn', 'standardSurface'] | |
| shaders = [] | |
| for st in shader_types: | |
| shaders.extend(cmds.ls(type=st)) | |
| item_count = 0 | |
| for shader in shaders: | |
| display_text = shader | |
| if not search_text or search_text in display_text.lower(): | |
| cmds.iconTextScrollList(shader_list, edit=True, append=display_text) | |
| item_count += 1 | |
| cmds.text(self.node_count_label, edit=True, label=f"Items: {item_count}") | |
| def update_lists_with_search(self, *args): | |
| self.update_file_node_list() | |
| for channel in self.shader_tabs: | |
| self.update_shader_list(channel) | |
| def reload_file_node_list(self, *args): | |
| self.update_file_node_list() | |
| print("File node list reloaded.") | |
| def reload_shader_list(self, channel, *args): | |
| self.update_shader_list(channel) | |
| print(f"Shader list for {channel} reloaded.") | |
| def on_file_node_select(self, *args): | |
| selected_item = cmds.iconTextScrollList(self.file_node_list, query=True, selectItem=True) | |
| if selected_item: | |
| self.selected_node = selected_item[0].split(" > ")[-1] | |
| if cmds.objExists(self.selected_node): | |
| current_path = cmds.getAttr(f"{self.selected_node}.fileTextureName") | |
| cmds.textField(self.selected_node_field, edit=True, | |
| text=f"Selected Node: {self.selected_node} ({current_path or 'None'})") | |
| shader_name = selected_item[0].split(" > ")[0] if " > " in selected_item[0] else "" | |
| if shader_name and cmds.objExists(shader_name): | |
| shading_engines = cmds.listConnections(shader_name, type='shadingEngine') | |
| if shading_engines: | |
| mesh_objects = cmds.listConnections(shading_engines[0], type='mesh') | |
| nurbs_objects = cmds.listConnections(shading_engines[0], type='nurbsSurface') | |
| affected_objects = (mesh_objects or []) + (nurbs_objects or []) | |
| if affected_objects: | |
| cmds.select(affected_objects, replace=True) | |
| print(f"Selected objects affected by {shader_name}: {affected_objects}") | |
| else: | |
| members = cmds.sets(shading_engines[0], query=True) | |
| if members: | |
| cmds.select(members, replace=True) | |
| print(f"Selected faces affected by {shader_name}: {members}") | |
| else: | |
| cmds.select(self.selected_node, replace=True) | |
| print(f"No objects/faces found, selected {self.selected_node}") | |
| else: | |
| cmds.select(self.selected_node, replace=True) | |
| print(f"No shading engine found, selected {self.selected_node}") | |
| else: | |
| cmds.select(self.selected_node, replace=True) | |
| print(f"Shader not found or not applicable, selected {self.selected_node}") | |
| if self.auto_apply and self.current_preview_path: | |
| self.assign_image_to_node() | |
| else: | |
| self.selected_node = None | |
| cmds.textField(self.selected_node_field, edit=True, text="No File Node Selected") | |
| else: | |
| self.selected_node = None | |
| cmds.textField(self.selected_node_field, edit=True, text="No File Node Selected") | |
| if self.delete_unused_nodes: | |
| mel.eval('hyperShadePanelMenuCommand("hyperShadePanel1", "deleteUnusedNodes");') | |
| print("Cleaned up unused nodes via MEL command.") | |
| def on_shader_select(self, channel, *args): | |
| shader_list = self.shader_tabs[channel]["list"] | |
| selected_item = cmds.iconTextScrollList(shader_list, query=True, selectItem=True) | |
| if selected_item: | |
| shader = selected_item[0] | |
| if cmds.objExists(shader): | |
| attr = self.shader_tabs[channel]["channel"] | |
| if attr == "albedo": | |
| attr = "color" if cmds.nodeType(shader) in ['lambert', 'phong', 'blinn'] else "baseColor" | |
| connections = cmds.listConnections(f"{shader}.{attr}", source=True, destination=False, plugs=True) | |
| file_node = None | |
| if connections: | |
| for conn in connections: | |
| node = conn.split('.')[0] | |
| if cmds.nodeType(node) == 'file': | |
| file_node = node | |
| break | |
| if not file_node and self.auto_create_file: | |
| file_node = cmds.shadingNode('file', asTexture=True, isColorManaged=True, name=f"{shader}_{channel.lower()}_file") | |
| cmds.connectAttr(f"{file_node}.outColor", f"{shader}.{attr}", force=True) | |
| print(f"Created file texture {file_node} for {shader} ({channel})") | |
| self.selected_node = file_node | |
| current_path = cmds.getAttr(f"{file_node}.fileTextureName") if file_node else "None" | |
| cmds.textField(self.selected_node_field, edit=True, | |
| text=f"Selected Node: {file_node or 'None'} ({current_path})") | |
| shading_engines = cmds.listConnections(shader, type='shadingEngine') | |
| if shading_engines: | |
| mesh_objects = cmds.listConnections(shading_engines[0], type='mesh') | |
| nurbs_objects = cmds.listConnections(shading_engines[0], type='nurbsSurface') | |
| affected_objects = (mesh_objects or []) + (nurbs_objects or []) | |
| if affected_objects: | |
| cmds.select(affected_objects, replace=True) | |
| print(f"Selected objects affected by {shader}: {affected_objects}") | |
| else: | |
| members = cmds.sets(shading_engines[0], query=True) | |
| if members: | |
| cmds.select(members, replace=True) | |
| print(f"Selected faces affected by {shader}: {members}") | |
| else: | |
| cmds.select(file_node or shader, replace=True) | |
| print(f"No objects/faces found, selected {file_node or shader}") | |
| else: | |
| cmds.select(file_node or shader, replace=True) | |
| print(f"No shading engine found, selected {file_node or shader}") | |
| if self.auto_apply and self.current_preview_path and file_node: | |
| self.assign_image_to_node() | |
| else: | |
| self.selected_node = None | |
| cmds.textField(self.selected_node_field, edit=True, text="No File Node Selected") | |
| else: | |
| self.selected_node = None | |
| cmds.textField(self.selected_node_field, edit=True, text="No File Node Selected") | |
| if self.delete_unused_nodes: | |
| mel.eval('hyperShadePanelMenuCommand("hyperShadePanel1", "deleteUnusedNodes");') | |
| print("Cleaned up unused nodes via MEL command.") | |
| def open_file_node_file_location(self, *args): | |
| selected_item = cmds.iconTextScrollList(self.file_node_list, query=True, selectItem=True) | |
| if selected_item: | |
| file_node = selected_item[0].split(" > ")[-1] | |
| if cmds.objExists(file_node): | |
| file_path = cmds.getAttr(f"{file_node}.fileTextureName") | |
| if file_path and os.path.isfile(file_path): | |
| directory = os.path.dirname(file_path) | |
| try: | |
| if os.name == 'nt': | |
| os.startfile(directory) | |
| elif os.name == 'posix': | |
| subprocess.call(['open', directory] if os.uname().sysname == 'Darwin' else ['xdg-open', directory]) | |
| print(f"Opened file location for {file_node}: {directory}") | |
| except Exception as e: | |
| print(f"Failed to open file location {directory}: {e}") | |
| else: | |
| print(f"No valid file path found for {file_node}") | |
| def open_shader_file_location(self, channel, *args): | |
| if self.selected_node and cmds.objExists(self.selected_node): | |
| file_path = cmds.getAttr(f"{self.selected_node}.fileTextureName") | |
| if file_path and os.path.isfile(file_path): | |
| directory = os.path.dirname(file_path) | |
| try: | |
| if os.name == 'nt': | |
| os.startfile(directory) | |
| elif os.name == 'posix': | |
| subprocess.call(['open', directory] if os.uname().sysname == 'Darwin' else ['xdg-open', directory]) | |
| print(f"Opened file location for {self.selected_node} ({channel}): {directory}") | |
| except Exception as e: | |
| print(f"Failed to open file location {directory}: {e}") | |
| else: | |
| print(f"No valid file path found for {self.selected_node}") | |
| def adjust_list_height(self, list_widget, delta, *args): | |
| current_height = cmds.iconTextScrollList(list_widget, query=True, height=True) | |
| new_height = max(50, current_height + delta) | |
| cmds.iconTextScrollList(list_widget, edit=True, height=new_height) | |
| if list_widget == self.file_list: | |
| self.file_list_height = new_height | |
| print(f"File list height adjusted to {new_height}") | |
| elif list_widget in [self.file_node_list] + [tab["list"] for tab in self.shader_tabs.values()]: | |
| self.node_list_height = new_height | |
| print(f"Node list height adjusted to {new_height}") | |
| def assign_image_to_node(self, *args): | |
| if not self.selected_node or not cmds.objExists(self.selected_node): | |
| cmds.warning("No valid file node selected.") | |
| return | |
| if not self.current_preview_path: | |
| cmds.warning("Please select an image from the list to assign.") | |
| return | |
| try: | |
| cmds.setAttr(f"{self.selected_node}.fileTextureName", self.current_preview_path, type="string") | |
| cmds.textField(self.selected_node_field, edit=True, | |
| text=f"Selected Node: {self.selected_node} ({self.current_preview_path})") | |
| print(f"Assigned {self.current_preview_path} to {self.selected_node}") | |
| except Exception as e: | |
| cmds.error(f"Failed to assign image: {e}") | |
| def toggle_auto_apply(self, value): | |
| self.auto_apply = value | |
| cmds.button(self.assign_button, edit=True, enable=not value) | |
| if self.auto_apply and self.current_preview_path and self.selected_node: | |
| self.assign_image_to_node() | |
| def toggle_delete_unused_nodes(self, value): | |
| self.delete_unused_nodes = value | |
| print(f"Delete Unused Shader Nodes set to: {self.delete_unused_nodes}") | |
| if self.delete_unused_nodes: | |
| mel.eval('hyperShadePanelMenuCommand("hyperShadePanel1", "deleteUnusedNodes");') | |
| print("Cleaned up unused nodes via MEL command.") | |
| def toggle_refresh_preview(self, value): | |
| self.refresh_preview = value | |
| print(f"Refresh Preview set to: {self.refresh_preview}") | |
| if self.current_preview_path: | |
| self.update_preview(self.current_preview_path) | |
| def toggle_thumbnail_size(self, value): | |
| self.thumbnail_size = value | |
| print(f"Thumbnail Size set to: {self.thumbnail_size}") | |
| cmds.optionMenuGrp(self.size_menu, edit=True, enable=self.thumbnail_size) # Enable/disable dropdown | |
| cmds.checkBox(self.delete_nodes_checkbox, edit=True, enable=self.thumbnail_size) | |
| if not self.thumbnail_size: | |
| self.delete_unused_nodes = False | |
| cmds.checkBox(self.delete_nodes_checkbox, edit=True, value=False) | |
| if self.current_preview_path: | |
| self.update_preview(self.current_preview_path) | |
| def toggle_auto_create_file(self, value): | |
| self.auto_create_file = value | |
| print(f"Auto Create File set to: {self.auto_create_file}") | |
| def show(self): | |
| self.create_ui() | |
| def launch_directory_tool(): | |
| tool = DirectoryTool() | |
| tool.show() | |
| if __name__ == "__main__": | |
| launch_directory_tool() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment