MayaのGUIのコードをLLMで生成する

## 環境

* Windows
* Maya 2026.1
* Copilot for Windows

## はじめに

本記事では、MayaのGUIのコードをLLMで生成する例を紹介します。
注意点として、同じプロンプトや同じ参照画像、同じAIツールを使用しても常に同じ出力結果になるとは限りません。また、本手法で生成されるコードは、必ずしも最適化されたものではなく、品質が保証されるものでもありません。あらかじめご了承ください。

## 完成形のイメージ

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965005/d9d160b2-68dc-48b5-ab6d-b9209c87b5e8.jpeg">

わかりやすさを重視してシンプルな例にしています。

## 入力について

本記事はプロンプトエンジニアリングを主題としていないため、詳細は割愛しますが、入力するプロンプトには以下のような補足情報を加えています。

- AIの役割
- タスクの説明
- 条件 (制限事項)
- 出力フォーマット

出力の品質を高めるため、AIに役割を与えています。また条件やフォーマットの指示を出し、目的に近い出力を得やすくしています。


## 生成例

### 1. プロンプトのみ

GUIの構造をテキストで指示し、コードを生成する例です。

#### 入力例

```markdown
あなたはAutodesk Maya 2026のツール開発に熟練したPythonプログラマーです。

# タスク
下記のようなGUIをMaya内で実装するためのPythonコードを作成してください。

- 中央部に縦並びで3つのスライダーバーがあります。
  - スライダーバーは左からラベル、数値入力欄、スライダーバー本体の順に並んで構成されています。
  - スライダーバーのラベルは順に「x value:」、「y value:」、「z value:」です。
  - 数値の型はfloatです。
- 最下部に横並びで3つのボタンがあります。
  - ボタンのラベルは順に「Apply and Close」、「Apply」、「Close」です。

# 条件
- 使用言語：Python
- コーディング規約: PEP8
- GUIフレームワーク：Qt for Python (PySide6)
- Maya内で使用することを前提とするため、QApplicationの生成とイベントループの処理は不要です。
以下のようなコードにはしないでください:
"""python
app = QApplication([])
window = Window()
window.show()
app.exec()
"""
以下の形式で十分です：
"""python
window = Window()
window.show()
"""
- MayaQWidgetBaseMixinをミックスインとして継承してください。
"""python
class Window(MayaQWidgetBaseMixin, QWidget):
"""
- コードはプログラミング初心者が理解できるように、簡潔で短いコードにしてください。

# 出力
ソースコードのみ出力してください。
```

#### 出力例

```python
from PySide6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, QLabel,
    QSlider, QDoubleSpinBox, QPushButton
)
from PySide6.QtCore import Qt
from shiboken6 import wrapInstance
import maya.OpenMayaUI as omui
from maya.app.general.mayaMixin import MayaQWidgetBaseMixin


class FloatSliderRow(QWidget):
    def __init__(self, label_text, parent=None):
        super().__init__(parent)
        layout = QHBoxLayout(self)

        label = QLabel(label_text)
        self.spinbox = QDoubleSpinBox()
        self.spinbox.setRange(-100.0, 100.0)
        self.spinbox.setSingleStep(0.1)

        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(-1000, 1000)

        # 双方向に値を同期
        self.slider.valueChanged.connect(self.sync_spinbox)
        self.spinbox.valueChanged.connect(self.sync_slider)

        layout.addWidget(label)
        layout.addWidget(self.spinbox)
        layout.addWidget(self.slider)

    def sync_spinbox(self, value):
        self.spinbox.setValue(value / 10.0)

    def sync_slider(self, value):
        self.slider.setValue(int(value * 10))


class Window(MayaQWidgetBaseMixin, QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Vector Slider")
        self.setMinimumWidth(300)

        main_layout = QVBoxLayout(self)

        self.sliders = []
        for axis in ['x', 'y', 'z']:
            slider_row = FloatSliderRow(f"{axis} value:")
            self.sliders.append(slider_row)
            main_layout.addWidget(slider_row)

        button_layout = QHBoxLayout()
        for label in ["Apply and Close", "Apply", "Close"]:
            btn = QPushButton(label)
            btn.clicked.connect(self.handle_button)
            button_layout.addWidget(btn)

        main_layout.addLayout(button_layout)

    def handle_button(self):
        sender = self.sender()
        values = [s.spinbox.value() for s in self.sliders]
        print(f"{sender.text()} pressed. Values: {values}")
        if sender.text() == "Apply and Close" or sender.text() == "Close":
            self.close()


window = Window()
window.show()
```
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965005/ac0c13af-04c2-471f-b1d4-3d014fc76462.jpeg">


### 2. プロンプト + 参照画像

GUIの構造を参照画像で示してコードを生成する例です。

#### 入力例

```markdown
あなたはAutodesk Maya 2026のツール開発に熟練したPythonプログラマーです。

# タスク
添付画像のようなGUIをMaya内で実装するためのPythonコードを作成してください。

# 条件
- 使用言語：Python
- コーディング規約: PEP8
- GUIフレームワーク：Qt for Python (PySide6)
- Maya内で使用することを前提とするため、QApplicationの生成とイベントループの処理は不要です。
以下のようなコードにはしないでください:
"""python
app = QApplication([])
window = Window()
window.show()
app.exec()
"""
以下の形式で十分です：
"""python
window = Window()
window.show()
"""
- MayaQWidgetBaseMixinをミックスインとして継承してください。
"""python
class Window(MayaQWidgetBaseMixin, QWidget):
"""
- コードはプログラミング初心者が理解できるように、簡潔で短いコードにしてください。

# 出力
ソースコードのみ出力してください。

```

#### 出力例

```python
from PySide6.QtWidgets import (
    QWidget, QLabel, QSlider, QLineEdit,
    QPushButton, QVBoxLayout, QHBoxLayout
)
from PySide6.QtCore import Qt
from maya.app.general.mayaMixin import MayaQWidgetBaseMixin


class Window(MayaQWidgetBaseMixin, QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Transform Tool")
        self.setFixedWidth(300)

        # Create sliders and line edits
        self.x_slider = QSlider(Qt.Horizontal)
        self.y_slider = QSlider(Qt.Horizontal)
        self.z_slider = QSlider(Qt.Horizontal)

        self.x_edit = QLineEdit("0.0000")
        self.y_edit = QLineEdit("0.0000")
        self.z_edit = QLineEdit("0.0000")

        # Setup layout
        main_layout = QVBoxLayout()

        for label_text, slider, line_edit in [
            ("X", self.x_slider, self.x_edit),
            ("Y", self.y_slider, self.y_edit),
            ("Z", self.z_slider, self.z_edit)
        ]:
            row = QHBoxLayout()
            row.addWidget(QLabel(label_text))
            row.addWidget(line_edit)
            row.addWidget(slider)
            main_layout.addLayout(row)

        # Buttons
        apply_close_btn = QPushButton("Apply and Close")
        apply_btn = QPushButton("Apply")
        close_btn = QPushButton("Close")

        btn_row = QHBoxLayout()
        btn_row.addWidget(apply_close_btn)
        btn_row.addWidget(apply_btn)
        btn_row.addWidget(close_btn)

        main_layout.addLayout(btn_row)
        self.setLayout(main_layout)


window = Window()
window.show()

```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965005/0ca8ca71-a97b-4232-b202-9d4035a85bc8.jpeg">

### 調整例

参照画像を用いた生成例では、以下のような問題点がありました。
- ラベル名が参照画像と異なる
- 数値とスライダーバーが連動していない
- マイナス値へ対応していない

AIツールを用いたコード生成は、一度で理想的な出力にならないケースもあります。出力結果をきちんと評価し、必要に応じてプロンプトを反復させて調整することも重要です。(調整については、直接コードを書き換えるなどベストな方法はもちろんケースバイケースとなりますが、本記事ではプロンプトでの調整例を示します)

```markdown:追加プロンプト
下記のように調整してください。

- スライダーバーのラベルを順に「x value:」、「y value:」、「z value:」としてください。
- 数値とスライダーバーを連動させてください。
- スライダーバーはマイナスの値も設定できるようにしてください。
```

```python:再出力されたコード例
from PySide6.QtWidgets import (
    QWidget, QLabel, QSlider, QLineEdit,
    QPushButton, QVBoxLayout, QHBoxLayout
)
from PySide6.QtCore import Qt
from maya.app.general.mayaMixin import MayaQWidgetBaseMixin


class Window(MayaQWidgetBaseMixin, QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Transform Tool")
        self.setFixedWidth(300)

        self.sliders = {}
        self.line_edits = {}
        main_layout = QVBoxLayout()

        for axis in ["x", "y", "z"]:
            label = QLabel(f"{axis} value:")
            slider = QSlider(Qt.Horizontal)
            slider.setRange(-100, 100)
            slider.setValue(0)

            line_edit = QLineEdit("0.0000")
            self.sliders[axis] = slider
            self.line_edits[axis] = line_edit

            # Connect slider and line edit
            slider.valueChanged.connect(
                lambda val, a=axis: self.update_line_edit(a, val)
            )
            line_edit.editingFinished.connect(
                lambda a=axis: self.update_slider(a)
            )

            row = QHBoxLayout()
            row.addWidget(label)
            row.addWidget(line_edit)
            row.addWidget(slider)
            main_layout.addLayout(row)

        # Buttons
        apply_close_btn = QPushButton("Apply and Close")
        apply_btn = QPushButton("Apply")
        close_btn = QPushButton("Close")

        btn_row = QHBoxLayout()
        btn_row.addWidget(apply_close_btn)
        btn_row.addWidget(apply_btn)
        btn_row.addWidget(close_btn)

        main_layout.addLayout(btn_row)
        self.setLayout(main_layout)

    def update_line_edit(self, axis, value):
        self.line_edits[axis].setText(f"{value:.4f}")

    def update_slider(self, axis):
        try:
            value = float(self.line_edits[axis].text())
            clamped_value = max(-100, min(100, int(value)))
            self.sliders[axis].setValue(clamped_value)
        except ValueError:
            pass


window = Window()
window.show()
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965005/f009c013-b0de-42e4-b163-b5969b3c92fa.jpeg">

## 参考
- [Google - より優れたプロンプトを作成するための戦略](https://cloud.google.com/discover/what-is-prompt-engineering?hl=ja#strategies-for-writing-better-prompts)
- [Google - Tips to enhance your prompt-engineering abilities](https://cloud.google.com/blog/products/application-development/five-best-practices-for-prompt-engineering?hl=en)