📂

Chat GPT で Maya のscriptファイルを一覧するpluginを作ってみた

2023/04/23に公開

概要

練習としてMayaのscriptディレクトリにあるpythonファイルの名前を一覧するpluginを制作する。
pythonとPyMelで作成する。

※ 後からスクリプトを「シェルフ」に追加しておけること知りました。シェルフでええやん

手順

新規にライブラリを学ぶのでChat GPTを使ってみる

  1. Chat GPTでscriptのたたきを出力
    a. 何回か改善してもらう
  2. デバッグ
  3. リファクタ
  4. 公開

1. Chat gPTでscriptのたたきを出力

何回かやりとりして下記のコードを出力してもらいました。
これを実際に読みつつデバッグして修正していきます。

import os
import maya.cmds as cmds
import pymel.core as pm


class ListScriptsWindow(object):
    """
    スクリプトディレクトリにあるPythonスクリプトの一覧を表示するウィンドウを作成するクラス
    """
    def __init__(self):
        """
        ウィンドウとレイアウトを作成し、スクリプトディレクトリを設定するコンストラクタ
        """
        self.window = pm.window(title='List Scripts', widthHeight=(300, 300))
        self.layout = pm.columnLayout(parent=self.window)
        # スクリプトディレクトリを取得
        self.script_directory = pm.internalVar(userScriptDir=True)

    def show(self):
        """
        ウィンドウを表示するメソッド
        """
        self.list_scripts()
        pm.showWindow(self.window)

    def list_scripts(self):
        """
        スクリプトディレクトリ内のPythonスクリプトを一覧表示するメソッド
        """
        # Pythonスクリプトのみを取得
        scripts = [f for f in os.listdir(self.script_directory) if os.path.isfile(os.path.join(self.script_directory, f)) and f.endswith('.py')]
        for script in scripts:
            # スクリプト名をボタンとして表示し、ボタンが押されたらrun_script()メソッドを呼び出すように設定
            pm.button(label=script, command=lambda script_path=os.path.join(self.script_directory, script): self.run_script(script_path))

    def run_script(self, script_path):
        """
        指定されたPythonスクリプトを実行するメソッド
        """
        with open(script_path, 'r') as f:
            script_content = f.read()
            # スクリプトの内容を実行する
            exec(script_content)

def create_plugin():
    """
    プラグインのエントリーポイントとなる関数
    """
    ListScriptsWindow().show()

def initializePlugin(plugin):
    """
    プラグインの初期化を行う関数
    """
    cmds.pluginInfo(plugin, edit=True, command=create_plugin)

def uninitializePlugin(plugin):
    """
    プラグインの終了処理を行う関数
    """
    pass

コードレビュー、修正

1. 全体の構造

class ListScriptsWindow # windowのUIを生成して、scriptを一覧する処理をまとめたClassdef __init__  # インスタンス生成時にウィンドウ作成、pm.columnLayoutで部品を縦に並べるレイアウトを作成、self.script_directory にscriptディレクトリのpathを格納def show # windowのUIを表示するメソッド(初期化時に呼ばれる)def list_scripts # scriptディレクトリの.pyの配列を生成後、ボタンを作ろうとしているdef run_script # 引数で渡されたpathをファイルとして実行する。list_scriptsで作られるボタンに設定される

def create_plugin # ListScriptsWindowのインスタンス化
def initializePlugin # mayaのpluginとして登録してる?
def uninitializePlugin # プラグイン終了処理を書きたかったっぽい(passしか書いてない)

2. デバッグ

  • 行末尾に create_plugin() を追記して実行したらListScriptsWindow の処理自体は動く。
    そのまま実行しただけでもエラー無く動いたのが驚きだった。(空windowが出てきただけなので機能してるかは微妙)
  • list_scripts でscriptsが空配列になっていて一覧表示できていないことを確認
  • initializePlugin の処理がいまいちわからなかった
    • cmds.pluginInfo(plugin, edit=True, command=create_plugin)
    • command(c) string querymultiuse
      このプラグインで登録されている、すべてのコマンドの名前を含む文字配列を返します。http://me.autodesk.jp/wam/maya/docs/Maya2009/CommandsPython/pluginInfo.html

    • 型は string となっているので create_pluginを渡しているのは間違ってるのでは?
      • acreate_plugin自体は関数で返値はvoid
    • edit の引数は存在するがドキュメントにはない… PyMelのコード見ないとわからなそう

cmds.pluginInfoが動いてないのでcreate_plugin を末尾で実行してデバッグします。

2. リファクタ

  1. まずlist_scripts でファイル一覧が生成できてない件
    1. コードは間違っておらず .py のみを見に行っていたので .melのファイル名も格納するようにした
    2. ついでに読みやすいように修正
# scriptsディレクトリ内の.py, .melファイル名を格納
scripts = []

for f in os.listdir(self.script_directory):
  if os.path.isfile(os.path.join(self.script_directory, f)) and f.endswith(('.py', 'mel')):
      scripts.append(f)
  1. 一覧化には成功したが、ファイル名のボタンを押下するとエラーが発生

    1. # エラー: OSError: file <maya console> line 48: [WinError 6] ハンドルが無効です。

    2. run_script の script_path にFalseが渡ってきている
  2. list_scripts のボタン押下時の処理を修正

    1. pm.button(label=script, command=lambda script_path=os.path.join(self.script_directory, script): self.run_script(script_path))
      https://download.autodesk.com/us/maya/2011help/PyMel/generated/functions/pymel.core.windows/pymel.core.windows.button.html

    2. command引数に関数を渡す際、scriptファイル名を渡せていなそう
      logを出力して調べているとcommandに渡す関数は第一引数にboolが入ってくるよう。なので下記のように都度関数を作って渡すように修正
        for script in scripts:
            # ボタン押下時に実行する関数
            def push_button(_):
              self.run_script(script)
    
            # スクリプト名をボタンとして表示
            pm.button(label=script, command=push_button)
    
    1. run_script を修正
        def run_script(self, script):
    	"""
    	指定されたPythonスクリプトを実行するメソッド
    	"""
    	script_path = os.path.join(self.script_directory, script)
    
    	with open(script_path, 'r') as f:
    	    script_content = f.read()
    	    # スクリプトの内容を実行する
    	    exec(script_content)
    
    1. ボタン押下でsceript実行できるようになった。ただここまで来てpythonの中で.melを実行しようとするとsyntax error になることに気づく(当たり前(-_-;))
  3. MELの場合はクリップボードにスクリプトファイル名をコピーするように修正

        def run_script(self, script):
    	"""
    	指定されたPythonスクリプトを実行するメソッド
    	"""
    
    	# pythonファイルの場合、スクリプトの内容を実行する
    	if script.endswith('.py'):
    	  script_path = os.path.join(self.script_directory, script)
    	  with open(script_path, 'r') as f:
    	    script_content = f.read()
    	    exec(script_content)
    
    	# melファイルの場合、ファイル名をクリップボードにコピー
    	# TODO: MELウィンドウに貼り付けて実行させるまでやりたい
    	if script.endswith('.mel'):
    	  p = subprocess.Popen( ['clip'], stdin=subprocess.PIPE, shell=True )
    	  p.stdin.write(str.encode(script))    def run_script(self, script):
    
  4. コピーできるスクリプト名が、どのボタンを押してもforループの最後の物になってしまっていたのでlambda式に戻して修正

        for script in scripts:
            # スクリプト名をボタンとして表示
            pm.button(label=script, command=lambda _, _script=script: self.run_script(_script))
    

まとめ・所感

課題

  • コマンドラインにファイル名入力で動かすというのができなかったので、他の人のプラグイン見て修正してみます
    • まだ initializePlugin とかのお約束が理解できていないのでドキュメントも読んでいく
  • UIにこだわっているプラグインもあるみたいなのでその辺もやってみたい

コードを生成したプロンプト

```
「PyMelを使ってMayaのscriptディレクトリに配置されたplugin用のpythonファイルの名前の一覧を、GUIウィンドウに表示するpluginを作成したいです。参考になるコードを出力してください。」
「上記のコードが、実際のpythonやPyMelのコードとして正しいかレビューして改善してください」
「上記の改善案を反映させたコードを出力してください」
「上記のコードがMAYAで本当に実行できるか確認して、修正が必要であれば修正してください。」
「上記のコードにわかりやすいようにコメントを追加してください。コメントはdocstring のフォーマットで記述してください。」
```

Discussion