Maya Python3でインテリセンスを効かせたい
この記事はMaya Advent Calendar 2023の20日目の記事です
この記事ではMayaCommandのスタブファイルを作成についての流れを紹介します
コードをすべて紹介するにはコードの数が多すぎるため、かいつまんで何を行っていたのかの紹介になります。
できたもの
スタブファイルがほしい人はこちらからダウンロードしてください
VSCodeでの読み込み方法はREADMEに記述しています
僕のランチのために投げ銭お願いします(⋈◍>◡<◍)。✧♡
日本語対応もしました
初めに
スタブファイルを知らない方もいると思いますのでざっくり説明しますと、拡張子が.pyiのPythonの型情報などを記載してロジック部分を省略したファイルです。これをIDE上で使用することで型チェックや補完がうまく動作しないモジュールに対して、補完やチェックを有効にできるという便利なものです。
スタブとは、大規模なシステム開発の際に、完成済みのプログラムの動作を検証するための、完成していないプログラムの代用となるプログラムのことである。または、外部プログラムとの細かなインターフェース制御を引き受けるプログラムのことである。
引用先: スタブとは (stub): - IT用語辞典バイナリスタブファイルとは、クラス、変数、関数、そして最も重要なそれらの型を含む、Pythonモジュールのパブリックインターフェイスのスケルトンを含むファイルです。
ライブラリ(または任意のモジュール)のスタブファイルを書き、ライブラリモジュールと同じディレクトリに.pyiファイルとして保存します。あるいは、スタブ(.pyiファイル)をスタブ用に予約されたディレクトリ(例えば、myproject/stubs)に置きます。
引用先: mypy 1.7.1 documentation: Stub files
Maya Python3でインテリセンスを効かせたい
Maya2023以降スタブファイルがSDKにない
Autodesk_Maya_2022_5_Update_DEVKIT_Windowsにあるスタブファイル
pymelが任意インストールになってからMaya 2023以降のdevkitには、pymelは含まれなくなったため、Mayaのスタブファイルが同梱されなくなりました。
そのため、2023以降のMayaを開発する際にIDE上でインテリセンスを効かせるには古いバージョンのdevkitを使うか、外部モジュールや拡張機能を使うといったような選択しになってきます。
VSCodeであればMayaPyが有名どころでしょうか?
とはいえ、このMayaPyも内部はMayaのDEVKITを使用したものになっているのでかなり古く、かつPython2系です。
Maya2023やMaya2024はPython3でPython2と比べ型ヒントを使った開発は必須といっても過言ではない!!そんな中、Mayaのコマンドでインテリセンスが使えないのはものすごく不便です。
どうせならDocstringsも欲しいよな・・・
ということでスタブファイルを手動で作ってみた
本来であればmypy の stubgen コマンドを使用したりするんですがその場合docstringsが存在していないし...じゃあDocumentから作ればいいんじゃない?
そんな感じの見切り発車でスタート
HTMLを文字列として取得し、正規表現でtag消してとかしてたら恐ろしいほどネストが深くなったり、処理速度が尋常じゃなくやばくなったので、ちゃんと作ることにしました
開発環境の整備
作業を開始する前に開発環境を整えました。開発のベースはMaya 2024.2
開発環境
VSCode: 1.85.1
Python 3.11.6
NuGet: 6.4.0.123
Maya 2024.2 Help English
Maya Product HelpのダウンロードはDownload & Install Maya Product Help
Python Modules
black==23.3.0
flake8==6.0.0
isort==5.12.0
mypy==1.3.0
mypy-extensions==1.0.0
pyflakes==3.0.1
pyproject-flake8==6.0.0.post1
typing_extensions==4.7.1
pylint==2.17.4
pep8==1.7.1
autopep8==2.0.4
beautifulsoup4==4.12.2
pydantic==2.3.0
PyYAML==6.0.1
VSCode Extentions
全て現状リリースしているものの最新版
ms-python.black-formatter
ms-python.flake8
ms-python.isort
ms-python.mypy-type-checker
ms-python.python
ms-python.vscode-pylance
Python環境の自動構築
自前でビルドする人のすることを考え、Pythonの環境を自動構築できるようにしておきました。
とはいえ、Pythonの環境を準備してもらうのも大変なのでPython環境がないPCでも展開できるようにします。
NuGetを使って、pythonの展開とvenvの構築ができるようにします。
venvには上記に記述している、requirements.txtで必要なモジュールをpipインストールさせておきます。
現在展開したいるフォルダをVSCodeで開き、code-workspaceの作成、pyproject.tomlやsetting.jsonなど必要な設定をしておきます。
pyproject.tomlの詳しい説明はこちらを読んでください
設定したものは以下の通りです。
開発環境を整えたところでコーディングについて
ファイル構成
|-- .venv/
|-- .vscode/
|-- bin/
|-- src/
| |-- create_pyi.py : pyiを作成するための実行部|
| |-- create_pyi.yml : pyiを作成する際に参照するデータ群(処理を無視するファイルなど)
| |-- maya2023.yml : Maya2023に関するデータ
| |-- maya2024.yml : Maya2024に関するデータ
| `-- models.py : Maya本体や各Functionに関するデータクラスなど
|-- pyproject.toml
|-- setup.bat
ソースコード
全てのコードを説明するのは大変ですので、ソースコードの確認はこちらから
Autodesk-Maya-Python-IntelliSense
ざっくり処理を説明するとMayaのPythonCommandのドキュメントは一つのコマンドごとに1つのHTMLを持っているため、すべてのHTMLを一つずつfor文で回します
その中で必要な要素をデータクラスなどに保持し、そのデータを元に関数を作り、アノテーションとDocstringsを作成するといった方法です
HTMLはh2タグごとに項目が分かれているので以下の項目を必要に応じてデータを取得、編集しています
- hSynopsis
- hReturn
- hKeywords
- hFlags
- hExamples
- hRelated
- hNotes
引数やそのコマンドの説明が書かれたhSynopsis
からdef addPP(attribute: string) -> list[string]:
を作成するのですが
中にはMaya独自の型などもあるのでそういった本来存在しない方はtyping.NewType
を使って新しい型を定義しています。addPP
もattribute
の引数がstring
になっています
Pythonではstr
が文字列ですがstring = NewType("string", str)
を定義し、string
という型を新しく定義しています。
現状は引数はLongNameしか使えませんが将来的にはShortNameを含めたり、ShortNameのみのスタブファイルを作れるようになど
欲しい人に合わせて切り替えられたりしてもいいかもなんて思って一応、データクラスにはshortNameを定義しています。
class ArgumentData:
longName: str
shortName: str
type: str
docs: str
properties: list[str]
HTMLはそのまま文字列で使用すると恐ろしいことになります。そういう時はBeautifulSoup
を使うと便利ですので今回のツールでも使用しています
(こんなこと言ってますがBeautifulSoupがめんどくさいから文字列から正規表現で...以下略)
for文で回す際にHTMLを文字列として取得し、BeasutifulSoupで読み込み
...
def extract_html_content(self, file_path: Path | str) -> None:
try:
with open(file_path, "r", encoding="utf-8") as file:
self.html_content = file.read()
except Exception as e:
raise e
...
@property
def soup(self) -> BeautifulSoup:
return BeautifulSoup(self.html_content, "html.parser")
...
def create_code_text(self) -> None:
...
for iter in self.document_root.iterdir():
self.function_name = iter.stem
if iter.stem not in self.option.common.ignore:
...
self.extract_html_content(iter)
...
そのsoupを元に、必要な要素を取得し、加工し、保持するようにしました
@property
def synopsis(self) -> Tag | NavigableString | None:
synopsis_section = self.soup.find("a", {"name": HTags.hSynopsis.value}).find_parent("h2")
return synopsis_section.find_next_sibling("p")
def extract_table_data(self) -> None:
tables = self.soup.find_all("table")
...
その時取得したデータや事前に必要な情報をデータクラスに格納することで
コードの作成や、バージョン、言語の対応がスムーズできるように簡単な設計をしています。
(言語対応はまだしてませんが将来的にするかも?)
処理のためのデータクラスについて
models.py
や各yamlファイルを使用して、Mayaクラスのデータクラスやそれらをまとめて持つModelクラスを用意し
そのオブジェクトをcreate_pyi.py
にあるCreateMayaCommandPYIクラスに読み込ませ、実際の処理をCreateMayaCommandPYIで行っています。
まず、データクラスに関してざっくり説明します。
models.py
models.pyはMaya本体や各Mayaコマンドに関するデータクラスなどを持っているモジュールです
class FunctionData:
class ArgumentData:
class NewType(BaseModel):
class NewTypes(BaseModel):
class Common(BaseModel):
class Docs(BaseModel):
class Maya(BaseModel):
class IntelliSenseOptionModel(BaseModel):
class HTags(Enum):
class Arguments(BaseModel):
FunctionData
は各Mayaコマンドの持つ引数や、Docstrings, Web上にあるドキュメントのURLを持つクラスNewType
はMaya独自の形を定義する際に使用する情報を持ち、NewTypes
はNewType
をitems
としてlist[NewType]
という形で保持させています。
そういったようなデータクラスがたくさんあります
具体的な例を1つ挙げるとclass Maya(BaseModel)
はmaya2024.yml
のデータを入れることで必要な要素をPythonのデータクラスとして扱えるようにしています
Maya2023.yml
やMaya2024.yml
は各Mayaのバージョンに合わせてCloudhelpのURL、pythonのバージョンに合わせて必要なモジュール情報など必要なデータを保持しています
version: 2023
python: "3.9.7"
help url: help.autodesk.com/cloudhelp/2023/ENU/Maya-Tech-Docs/CommandsPython
imports:
- from __future__ import annotations
documents:
2023:
en:
autodesk maya user guide 2023=1=htm (ade 2.1)=en-2
jp:
autodesk maya user guide 2023=1=htm (ade 2.1)=ja-2
このデータをmodels.py
にあるclass Maya
が読み込むことでコーディングの際に便利に扱えるようになり、データを切り替えることで中身が変わるのでコード自体を変えずにバージョンの対応がスムーズに行くようにしています
from argparse import ArgumentParser
from enum import Enum
from pathlib import Path
from typing import Optional
from pydantic import BaseModel, Field, validator
from typing_extensions import Self
...python
class Docs(BaseModel):
jp: str
en: str
class Maya(BaseModel):
version: int
python: str
help_url: Path = Field(alias="help url")
imports: list[str]
documents: dict[str, Docs]
class Config:
populate_by_name = True
@validator("imports", pre=True)
def create_imports_list(cls, v) -> list[str]:
return v or []
@validator("help_url", pre=True)
def create_help_url(cls, v) -> Path:
return Path(v)
@validator("documents", pre=True)
def create_help_url(cls, v) -> Path:
docs: dict[str, Docs] = {}
for key, value in v.items():
docs[str(key)] = Docs(**value)
return docs
class IntelliSenseOptionModel(BaseModel):
common: Common
maya: Maya
import re
from functools import cached_property
from pathlib import Path
from typing import Any
import autopep8
import yaml
from bs4 import BeautifulSoup, NavigableString, Tag
from models import ArgumentData, Arguments, FunctionData, HTags, IntelliSenseOptionModel
class CreateMayaCommandPYI:
...
def __init__(
self,
document_root: str | Path,
export_path: str | Path,
language: str,
version: str,
option: IntelliSenseOptionModel,
) -> None:
self.option = option
...
@cached_property
def cloudhelp_url(self) -> str:
return f"https://{self.option.maya.help_url.as_posix()}"
if __name__ == "__main__":
...
with open(create_pyi, "r") as file:
data = yaml.safe_load(file)
with open(maya, "r") as file:
maya_data = yaml.safe_load(file)
data["maya"] = maya_data
option = IntelliSenseOptionModel(**data)
mayacmd = CreateMayaCommandPYI(
document_root=document_dir,
export_path=export_path,
language=language,
version=version,
option=IntelliSenseOptionModel(**data),
)
mayacmd.run()
Docstringsに記述するURLのアドレスを取得するときにcloudhelp_urlを使用していますが読み込むソースが変わることでcloudhelp_url
の取得する値が変わるようになどそういったときに
データクラスをいくつか用意しています。create_pyi.yml
も同じデータクラスのためのデータでバージョン関係なく、主に共通で使用するものが含めています
正直コードが長かったりするためすべての紹介はできませんがざっくり行ったことは伝えられたのではないでしょうか?
もう少し詳しいことが知りたい人はGithubのほうを見てください、プルリクエストとかあればリクエストしてください
明日は COYOTE TAチームさんの[Mayaのリファレンス機能、 ちゃんと理解して使えば怖くない! -中編-]()です
ディスカッション
コメント一覧
まだ、コメントがありません