翻訳(オリジナル)

Powered by Google Translate
日本語
English
简体中文
繁體中文
한국어
Français
Deutsch
Español
Italiano
Português
Русский
Bahasa Indonesia
ไทย
Tiếng Việt
Bahasa Melayu
العربية
हिन्दी
Türkçe
Nederlands
Polski
Українська
Powered by Google Translate

Mayaスクリプト(13):スクリプト実行ツール "StmScriptLauncher"

Mayaスクリプト(13):スクリプト実行ツール "StmScriptLauncher"

こんにちは。

 今回は「Mayaでスクリプトを使うためのPythonスクリプト」を紹介します。

このスクリプトについての解説記事をnoteにアップしました。(約20000字)

ご興味あれば是非ご覧下さい。

スクリプト概要

機能

  • 指定したフォルダ下のスクリプトの一覧表示
  • UIからのスクリプトの実行
  • スクリプトのプレビュー 

用途 

  •  シェルフを利用しないスクリプトへのアクセス

操作・UI 

Ul 

 UI:クリックで拡大

 操作

スクリプトを実行してUIを開きます。

  1. リストからスクリプトを選択します。
    • 検索窓からのフィルター検索が可能です。
    • ヒストリーからも同様に選択が可能です。 
    • 選択したスクリプトは右側にプレビュー表示されます。
  2. 各種ボタンからリストで選択したスクリプトを実行します。
    • Source:
      スクリプト内部に実行文が書かれている場合はこちらを使用します。
    • Call:
      スクリプト名で実行します。 ファイル名と異なる名前のコマンドを呼び出したい場合はボタンの上にある欄にコマンド名を入力して実行します。
    • Auto Execute:
      スクリプト内部の実行文の有無に応じて自動でSource / Call を切り替えます。
      ※リスト項目のダブルクリックでもこのボタンと同じ処理を行います。

Auto Execute時の優先度

Auto Execute時の判別における優先度は

  1.  内部に実行コマンド名(スクリプト名または入力名)と同じ名称のプロシージャ・関数名が見つかればそれを実行
  2. 見つからなければSourceを実行

という順番です。 

そのため、内部に実行文がありそれを実行したいものの、スクリプト名と同じ名前の異なる関数・プロシージャが存在するといった場合は、その名前を入力して実行する必要があります。

 スクリプトの保存先

スクリプトが参照するフォルダは以下の4つです。

  • ドキュメント > maya > scripts
  • ドキュメント > maya > (バージョン) > (言語) > scripts
  • ドキュメント > maya > (バージョン) > scripts
  • Mayaインストールディレクトリ(バージョン別) > scripts  

これらのフォルダの下にあるスクリプトを取得します。

 ヒストリー

直近で使用したスクリプトを保存し、ヒストリー欄に表示します。

 コード全文

ファイルとして保存して呼び出す必要のないように、ここにコード全文を載せてしまいます。

スクリプトエディタにそのまま貼り付けて実行可能です。

#=== [StmScriptLauncher] ===
# Function: A script for previewing and executing other scripts.
# Last Update: 2026.04
# To report errors, please contact me via my website: [Steem's CG Developin'].
# ( link: https://steems-cg-developin.com/ ) 
#==========================================

import maya.cmds as cmds
import maya.mel as mel
import os
import glob

import re
import ast
import importlib
import sys

class StmScriptLauncher:
    def __init__(self):
        self.window_id = "scriptLauncherWin"
        self.history_var = "scriptLauncher_history"
        self.script_data = {}
        
        version = cmds.about(v=True)
        lang = cmds.about(uiLanguage=True)
        user_docs = cmds.internalVar(userAppDir=True)
        maya_install_dir = os.environ.get('MAYA_LOCATION')

        search_paths = [
            os.path.join(user_docs, "scripts"),
            os.path.join(user_docs, version, lang, "scripts"),
            os.path.join(user_docs, version, "scripts"),
            os.path.join(maya_install_dir, "scripts")
        ]

        self.collect_scripts(search_paths)
        self.create_ui()

    def collect_scripts(self, paths):
        for path in paths:
            if not os.path.exists(path): continue
            files = glob.glob(os.path.join(path, "*.mel")) + glob.glob(os.path.join(path, "*.py"))
            for f in files:
                file_name = os.path.basename(f)
                if file_name not in self.script_data:
                    self.script_data[file_name] = f.replace("\\", "/")

    def create_ui(self):
        if cmds.window(self.window_id, exists=True):
            cmds.deleteUI(self.window_id)

        self.win = cmds.window(self.window_id, title="StmScriptLauncher", widthHeight=(800, 600))
        pane = cmds.paneLayout(configuration='vertical2', separatorThickness=5)

        left_col = cmds.columnLayout(adjustableColumn=True, rowSpacing=5, columnOffset=("both", 5))
        
        cmds.text(label="Search (AND):", align="left", height=20)
        self.search_field = cmds.textField(textChangedCommand=self.refresh_list)
        
        self.scroll_list = cmds.textScrollList(
            numberOfRows=15, 
            allowMultiSelection=False,
            selectCommand=self.on_search_select,
            doubleClickCommand=self.execute_source_call
        )

        cmds.text(label="History (Recent 5):", align="left", height=20)
        self.history_list = cmds.textScrollList(
            numberOfRows=5,
            allowMultiSelection=False,
            selectCommand=self.on_history_select,
            doubleClickCommand=self.execute_source_call
        )
        
        cmds.text(label="Custom Command (Optional):", align="left", height=20)
        self.custom_cmd_field = cmds.textField(placeholderText="Function name")
        
        cmds.rowLayout(numberOfColumns=3, adjustableColumn=3, columnWidth3=[60, 60, 70])
        cmds.button(label="Source", command=self.execute_source, height=40)
        cmds.button(label="Call", command=self.execute_call, height=40)
        cmds.button(label="Source / Call", command=self.execute_source_call, height=40, backgroundColor=(0.3, 0.5, 0.3))
        cmds.setParent('..')
        cmds.setParent('..')

        right_form = cmds.formLayout(numberOfDivisions=100)
        preview_label = cmds.text(label="Code Preview:", align="left", height=20)
        self.preview_field = cmds.scrollField(editable=False, wordWrap=False, font="fixedWidthFont")
        
        cmds.formLayout(right_form, edit=True,
            attachForm=[
                (preview_label, 'top', 0), (preview_label, 'left', 5), (preview_label, 'right', 5),
                (self.preview_field, 'left', 5), (self.preview_field, 'right', 8), (self.preview_field, 'bottom', 8)
            ],
            attachControl=[(self.preview_field, 'top', 5, preview_label)]
        )
        
        cmds.setParent('..')

        self.refresh_list()
        self.refresh_history_ui()
        cmds.showWindow(self.window_id)

    def update_history_var(self, script_name):
        if not script_name:
            return

        current_history = []
        if cmds.optionVar(exists=self.history_var):
            current_history = cmds.optionVar(q=self.history_var)
        
        current_history = list(current_history) if isinstance(current_history, (list, tuple)) else ([current_history] if current_history else [])

        if script_name in current_history:
            current_history.remove(script_name)
        current_history.insert(0, script_name)
        current_history = current_history[:5]

        cmds.optionVar(clearArray=self.history_var)
        for item in current_history:
            cmds.optionVar(stringValueAppend=(self.history_var, item))
        
        self.refresh_history_ui()

    def refresh_history_ui(self):
        cmds.textScrollList(self.history_list, edit=True, removeAll=True)
        if cmds.optionVar(exists=self.history_var):
            history = cmds.optionVar(q=self.history_var)
            if history:
                cmds.textScrollList(self.history_list, edit=True, append=history)

    def read_file_safe(self, path):
        encodings = ['utf-8', 'cp932', 'euc-jp', 'utf-16']
        for enc in encodings:
            try:
                with open(path, "r", encoding=enc) as f:
                    return f.read()
            except (UnicodeDecodeError, LookupError):
                continue
        with open(path, "r", encoding="utf-8", errors="replace") as f:
            return f.read()

    def refresh_list(self, *args):
        search_query = cmds.textField(self.search_field, q=True, text=True).lower()
        keywords = search_query.split()
        cmds.textScrollList(self.scroll_list, edit=True, removeAll=True)
        sorted_names = sorted(self.script_data.keys())
        for name in sorted_names:
            if all(kw in name.lower() for kw in keywords):
                cmds.textScrollList(self.scroll_list, edit=True, append=name)

    def update_preview(self, *args):
        selected = cmds.textScrollList(self.scroll_list, q=True, selectItem=True)
        if not selected:
            selected = cmds.textScrollList(self.history_list, q=True, selectItem=True)
            
        if not selected:
            cmds.scrollField(self.preview_field, edit=True, text="")
            return
            
        path = self.script_data[selected[0]]
        content = self.read_file_safe(path)
        cmds.scrollField(self.preview_field, edit=True, text=content)

    def on_search_select(self):
        cmds.textScrollList(self.history_list, edit=True, deselectAll=True)
        self.update_preview()

    def on_history_select(self):
        cmds.textScrollList(self.scroll_list, edit=True, deselectAll=True)
        self.update_preview()

    def get_active_selection(self):
        sel = cmds.textScrollList(self.scroll_list, q=True, selectItem=True)
        if not sel:
            sel = cmds.textScrollList(self.history_list, q=True, selectItem=True)
        return sel[0] if sel else None
    
    def has_mel_execution(self, file_path):
        if not os.path.exists(file_path):
            return 0
        
        content = self.read_file_safe(file_path)
        
        content = re.sub(r'//.*', lambda m: " " * len(m.group()), content)
        content = re.sub(r'/\*.*?\*/', lambda m: " " * len(m.group()), content, flags=re.DOTALL)
        
        defined_procs = set(re.findall(r'\bproc\s+([a-zA-Z_]\w*)', content))
        
        chars = list(content)
        n = len(chars)
        i = 0
        proc_start_pattern = re.compile(r'\bproc\s+\w+')

        print(f"\n{'='*80}\n[DEBUG: SCOPE ANALYSIS] {os.path.basename(file_path)}\n{'='*80}")

        while i < n:
            match = proc_start_pattern.match(content, i)
            if match:
                actual_start = i
                prefix_area = content[max(0, i-60):i]
                boundary = max(prefix_area.rfind(';'), prefix_area.rfind('\n')) + 1
                prefix_candidate = prefix_area[boundary:].strip()
                if prefix_candidate:
                    actual_start = (max(0, i-60) + boundary) + prefix_area[boundary:].find(prefix_candidate)
                
                start_line = content.count('\n', 0, actual_start) + 1
                
                brace_level = 0
                in_proc_body = False
                search_idx = i
                proc_name = "Unknown"
                
                while search_idx < n:
                    if chars[search_idx] == '{':
                        brace_level = 1
                        in_proc_body = True

                        for k in range(actual_start, search_idx + 1):
                            chars[k] = ' '
                        i = search_idx + 1
                        break
                    search_idx += 1
                
                if in_proc_body:
                    while i < n and brace_level > 0:
                        if chars[i] == '{':
                            brace_level += 1
                        elif chars[i] == '}':
                            brace_level -= 1
                            if brace_level == 0:
                                end_line = content.count('\n', 0, i) + 1
                                print(f"Found Proc: {proc_name:<30 ---="" .join="" 1="" 80="" be="" break="" case="" catch="" chars="" clean_content.splitlines="" clean_content="" code="" continue="" debug_lines="" default="" do="" else="" end_line="" float="" for="" global="" i="" if="" in="" int="" investigated="" join="" line.strip="" line="" lines="" matrix="" n="" o="" outside="" print="" procedures="" remains="" reserved_words="{" return="" source="" start_line="" string="" switch="" to="" tokens="re.sub(r" vector="" void="" while="">,\n\t\r\"\'`]', ' ', clean_content).split()
        
        found_any = 0
        seen = set()
        for word in tokens:
            if (word in reserved_words or word.startswith('$') or word[0].isdigit() or len(word) <= 1 or word in seen):
                continue
            seen.add(word)
            if word in defined_procs:
                print(f"[MATCH] Local Call: '{word}'")
                found_any = 1
                continue
            try:
                res = mel.eval(f'whatIs "{word}"').lower()
                if any(t in res for t in ['command', 'procedure', 'script']):
                    print(f"[MATCH] {res.capitalize()}: '{word}'")
                    found_any = 1
            except: continue
        
        return 1 if found_any else 0
    
    def has_python_execution(self, file_path):
        # determine whether a Python script contains an executable statement
        print(f"\n{'='*60}")
        print(f"[TARGET]: {file_path}")
    
        if not os.path.exists(file_path):
            print("[ERROR] File not found.")
            return 0
    
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                source = f.read()
            
            tree = ast.parse(source)
        except Exception as e:
            print(f"[ERROR] Syntax or Read Error: {e}")
            return 0
    
        found_execution = 0
        print(f"[LOG] Analyzing top-level nodes...")
    
        for node in tree.body:
            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Import, ast.ImportFrom)):
                continue
            if isinstance(node, ast.Expr) and isinstance(node.value, (ast.Str, ast.Constant)):
                continue
        
            if isinstance(node, ast.If):
                test = node.test
                if isinstance(test, ast.Compare) and isinstance(test.left, ast.Name) and test.left.id == '__name__':
                    if isinstance(test.ops[0], ast.Eq):
                        comp = test.comparators[0]
                        val = comp.value if isinstance(comp, ast.Constant) else getattr(comp, 's', None)
                        if val == '__main__':
                            print("[SKIP] True main block detected.")
                            continue
        
            found_execution = 1
            print(f"[MATCH!] Actual execution node: {ast.dump(node)}")
            break
    
        print(f"[RESULT]: {'1 (Execution Found)' if found_execution else '0 (Definitions Only)'}")
        print(f"{'='*60}\n")
        
        return found_execution
    
    def execute_source(self, *args):
        selected = self.get_active_selection()
        if not selected: return
        self.update_history_var(selected)
        
        file_name = selected
        full_path = self.script_data[file_name]
        
        print(f"\n# --- Sourcing: {file_name} --- #")
        try:
            if file_name.endswith(".mel"):
                mel.eval(f'source "{full_path}";')
            
            elif file_name.endswith(".py"):
                code = self.read_file_safe(full_path)
                exec(code, globals())
                
        except Exception as e:
            cmds.warning("Source failed. See Script Editor for details.")
            print(f"# --- ERROR: {e} --- #")
            
    def execute_call(self, *args):
        selected = self.get_active_selection()
        if not selected: return
        self.update_history_var(selected)
        
        file_name = selected
        module_name = os.path.splitext(file_name)[0]
        
        input_cmd = cmds.textField(self.custom_cmd_field, q=True, text=True).strip()
        exec_name = input_cmd.replace("()", "") if input_cmd else module_name
        
        try:
            if file_name.endswith(".mel"):
                exists_val = mel.eval(f'exists "{exec_name}"')
                what_is_val = mel.eval(f'whatIs "{exec_name}"')
                
                if exists_val and ("procedure" in what_is_val.lower() or "script" in what_is_val.lower()):
                    print(f"\n# --- Calling: {exec_name} --- #")
                    mel.eval(f'{exec_name}();')
                elif exists_val and what_is_val == "command":
                    print(f"\n# --- Calling: {exec_name} --- #")
                    mel.eval(f'{exec_name};')
                else:
                    print(f"Target '{exec_name}' not found in current session.")
                    
            elif file_name.endswith(".py"):
                found = False
                
                if exec_name in globals():
                    func = globals()[exec_name]
                    if callable(func):
                        func()
                        found = True
                        print(f"# Success: Found '{exec_name}' in globals.")
            
                if not found:
                    try:
                        if module_name in sys.modules:
                            module = sys.modules[module_name]
                        else:
                            module = importlib.import_module(module_name)
                        
                        if hasattr(module, exec_name):
                            func = getattr(module, exec_name)
                            if callable(func):
                                func()
                                print(f"# Success: {module_name}.{exec_name}() called.")
                                found = True
                        else:
                            if callable(module):
                                module()
                                found = True
            
                    except Exception as e:
                        print(f"# Error: Failed to import or execute '{module_name}': {e}")
            
                if not found:
                    print(f"# Warning: Target '{exec_name}' not found in current session or module '{module_name}'.")

        except Exception as e:
            cmds.warning("Call failed. See Script Editor for details.")
            print(f"# --- ERROR: {e} --- #")

    def execute_source_call(self, *args):
        selected = self.get_active_selection()
        if not selected: return
        self.update_history_var(selected)
        
        file_name = selected
        full_path = self.script_data[file_name]
        module_name = os.path.splitext(file_name)[0]
        
        input_cmd = cmds.textField(self.custom_cmd_field, q=True, text=True).strip()
        exec_name = input_cmd.replace("()", "") if input_cmd else module_name
        
        try:
            if file_name.endswith(".mel"):
                exists_val = mel.eval(f'exists "{exec_name}"')
                what_is_val = mel.eval(f'whatIs "{exec_name}"')
                hasExecution = self.has_mel_execution(full_path)
                
                if hasExecution:
                    print(f"# --- Sourcing: {file_name} --- #")
                    mel.eval(f'source "{full_path}";')
                elif exists_val and ("procedure" in what_is_val.lower() or "script" in what_is_val.lower()):
                    print(f"\n# --- Calling: {exec_name} --- #")
                    mel.eval(f'{exec_name}();')
                elif exists_val and what_is_val == "Command":
                    print(f"\n# --- Calling: {exec_name} --- #")
                    mel.eval(f'{exec_name};')
                    
            elif file_name.endswith(".py"):
                found = False
                hasExecution = self.has_python_execution(full_path)
                
                if hasExecution:
                    print(f"# --- Sourcing: {file_name} --- #")
                    code = self.read_file_safe(full_path)
                    exec(code, globals())
                else:
                    if exec_name in globals():
                        func = globals()[exec_name]
                        if callable(func):
                            print(f"\n# --- Calling: {exec_name} --- #")
                            func()
                            found = True
                
                    if not found:
                        try:
                            if module_name in sys.modules:
                                module = sys.modules[module_name]
                            else:
                                module = importlib.import_module(module_name)
                            
                            if hasattr(module, exec_name):
                                func = getattr(module, exec_name)
                                if callable(func):
                                    print(f"\n# --- Calling: {module_name}.{exec_name}() --- #")
                                    func()
                                    found = True
                            else:
                                if callable(module):
                                    module()
                                    found = True
                
                        except Exception as e:
                            print(f"# Error: Failed to import or execute '{module_name}': {e}")
            
                    if not found:
                        print(f"# Warning: Target '{exec_name}' not found in current session or module '{module_name}'.")

        except Exception as e:
            cmds.warning("Call failed. See Script Editor for details.")
            print(f"# --- ERROR: {e} --- #")

launcher = StmScriptLauncher()

補足

シェルフに追加する

以下の手順でシェルフに追加できます。

  1. スクリプトエディタに全文ペースト(※Pythonタブを使用)
  2. 貼り付けたスクリプトを全選択(Ctrl+A)
  3. シェルフに中ボタンドラッグ

シェルフのTooltip 表示について

シェルフアイコンにカーソルをホバーさせたときにこのスクリプト全文が表示されないようにするには、Tooltipを編集します。

アイコンを右クリックしてShelf Editorを開いて編集します。

Shelf Editor:

マーキングメニューへ追加する

 もしマーキングメニューに入れたくなったら、

  1.  StmScriptLauncher.py の名前でスクリプトファイルを保存
  2. MEL化した以下のスクリプトをシェルフに登録
     python("import StmScriptLauncher;StmScriptLauncher.StmScriptLauncher(); None");
  3. マーキングメニューに追加

という手順を踏むのが簡潔かと思います。

おわりに

あらゆるスクリプトを使用するのに利用できる、汎用性の高いスクリプトです。

非常に有用性の高いものかと思います。

皆さんの参考になれば幸いです。 

メインページフィード書き換え・2ページ目以降ページャー書き換え

いいね・シェアボタン

Share This Page!

前後ページのリンク表示