vicc blog

株式会社ヴィックの技術ブログです。

ChatGPT と Gemini で RhinoPython をどのくらい書けるか検証してみた

昨今 AI の進歩が凄まじく、特にコードを生成するという分野で非常に大きな力を発揮します。

自分自身としては、RhinoPython/GhPython の作業の中で行き詰まったときに ChatGPT を雑に指示を出し、Rhino ではない 3D ソフトにあるであろう関数を使った動かないコードが返ってくるなど、イマイチな経験を多くしてきました。

今回の記事では、一度落ち着いて OpenAI の ChatGPT と Google の Gemini を用いて RhinoPython をどのくらい書けるのか検証してみました。

※2025/08/08 時点での検証です。

モデルはそれぞれ下記を利用します。

  • ChatGPT : o4-mini-high
  • Gemini : 2.5 Pro

AI に投げる課題は?

AI に投げる課題を考えるのも面倒なので、ChatGPT(こちらでは4o)に以下の指示を投げてみました。

chatGPT や Google Gemini を使って RhinoPython をどれだけ書けるか調査したいです。
それらに投げる課題をいくつか考えてください。

それに対して、レベル1からレベル5、そして運用アイデアを答えてくれました。
ライノの仕様上、出来ないだろうなというものも一部含まれていますが、いくつか参考になりそうな例があったので、今回の検証に利用してみます。

ちょっと前から無駄に絵文字使って、まとめている感を出してくるのがちょっとだるい

検証① ポリラインで四角を書く

まずはこのプロンプトを投げます。

RhinoPython の相談です。RhinoPython のコードを出力してほしいです

Rhino上に正方形のポリラインを描画してください。
条件:XY平面に、1辺100の正方形。

Gemini(左)、ChatGPT(右)

Gemini の回答

# -*- coding: utf-8 -*-
import rhinoscriptsyntax as rs

# 正方形の頂点座標をリストで定義します
# 描画を閉じるために、始点と同じ座標を最後にも追加します
points = [
    (0, 0, 0),      # 原点
    (100, 0, 0),    # X方向に100
    (100, 100, 0),  # X, Y方向に100
    (0, 100, 0),    # Y方向に100
    (0, 0, 0)       # 始点に戻り、閉じる
]

# 定義した頂点リストを基にポリラインを追加します
rs.AddPolyline(points)

ChatGPT の回答

# -*- coding: utf-8 -*-
import rhinoscriptsyntax as rs

def draw_square(side_length=100, origin=(0, 0, 0)):
    """
    XY 平面上に正方形ポリラインを描画する関数。

    :param side_length: 正方形の一辺の長さ(デフォルト 100)
    :param origin: 正方形の左下隅の座標(デフォルト (0,0,0))
    :return: 作成されたポリラインのオブジェクト ID
    """
    x0, y0, z0 = origin
    # 頂点リスト(最後の点は起点に戻る)
    points = [
        (x0,                y0,                 z0),
        (x0 + side_length,  y0,                 z0),
        (x0 + side_length,  y0 + side_length,   z0),
        (x0,                y0 + side_length,   z0),
        (x0,                y0,                 z0),
    ]
    return rs.AddPolyline(points)

# スクリプト実行
if __name__ == "__main__":
    draw_square()

どちらも全く問題なく動きました。

強いて比較すると、ChatGPT のほうはデフォルト引数付きの関数として書いているので、指示したサイズでポリラインが書けるうえにサイズ変更が容易な気が利いた実装に見えます。Gemini はハードコーディングしています。とはいえ、変数だとは指示していない上に指摘すればすぐ直ると思うのでほぼ差は無しです。

朝飯前?

検証② オブジェクトを特定の条件で判別する

次はロジックがあるものを書いてもらおうと思います。
このプロンプトを投げます。

選択されたサーフェスのうち、面積が最大のサーフェスのみを選択状態してください。

そして、入力データはこちらです。少し意地悪をして、サーフェスだけでなくカーブも含めて選択状態にして渡します。

テストケースは下記の4種類です

  • 入力が何も無し
  • サーフェスのみいくつか入力
  • カーブのみ入力
  • サーフェスとカーブを入力

カーブx1、サーフェスx6

Gemini の回答

最初の回答がこちら。

# -*- coding: utf-8 -*-

import rhinoscriptsyntax as rs

def select_largest_surface():
    """
    選択されているサーフェスの中から、面積が最大のものを選択し直す関数
    """
    # 現在選択されているオブジェクトのIDを取得
    selected_ids = rs.GetSelectedObjects()

    # オブジェクトが何も選択されていなかったら処理を終了
    if not selected_ids:
        print("オブジェクトが選択されていません。")
        return

    largest_area = 0.0
    largest_surface_id = None
    
    # 選択された各オブジェクトをチェック
    for obj_id in selected_ids:
        # そのオブジェクトがサーフェスであるかを確認
        if rs.IsSurface(obj_id):
            # サーフェスの面積を計算
            # rs.SurfaceArea()は[面積, 誤差]のタプルを返すので、面積のみ取得
            area = rs.SurfaceArea(obj_id)[0]
            
            # 現在の最大面積よりも大きければ、情報を更新
            if area > largest_area:
                largest_area = area
                largest_surface_id = obj_id

    # 最大面積のサーフェスが見つかった場合
    if largest_surface_id:
        # いったん全ての選択を解除
        rs.UnselectAllObjects()
        
        # 最大面積のサーフェスのみを選択状態にする
        rs.SelectObject(largest_surface_id)
        
        print("最大面積のサーフェスを選択しました。面積: {}".format(largest_area))
    else:
        print("選択されたオブジェクトの中にサーフェスが見つかりませんでした。")

# スクリプトを実行
if __name__ == "__main__":
    select_largest_surface()

こちらは実行できませんでした。rs.GetSelectedObjects() という RhinoScriptSyntax には無い関数を使おうとしています。その関数が無いと指摘すると、直ちに修正され問題なく実行できました。

そして、用意していた4つのテストケースすべてで期待通りに機能しました。

優しい指摘

ついでの実験として、このプログラムの確認のために mermaid 記法でフローチャートを AI に書いてもらいました。レンダリングエラーが多少出ましたが、エラーメッセージを渡すと即座に修正されました。生成されたフローチャートが合っているのかもわからないので結局、ソースコードと合わせてチェックもしました。

※プログラムの構造をグラフィカルに表示することについては、これまでの構文解析の技術でもできています。AI だけがすごいわけではありません。

graph TD
    A[開始] --> B{オブジェクトは選択されているか?};
    B -- No --> C[メッセージ表示: 「オブジェクトが選択されていません」];
    C --> Z[終了];
    B -- Yes --> D[変数を初期化<br>largest_area = 0<br>largest_surface_id = None];
    D --> E[ループ開始: 選択された各オブジェクトをチェック];
    E --> F{オブジェクトはサーフェスか?};
    F -- No --> G[次のオブジェクトへ];
    G --> E;
    F -- Yes --> H[サーフェスの面積を計算];
    H --> I{計算した面積 > largest_areaか?};
    I -- No --> G;
    I -- Yes --> J[変数を更新<br>largest_area = 計算した面積<br>largest_surface_id = 現在のID];
    J --> G;
    E -- ループ終了 --> K{"最大のサーフェスは見つかったか?<br>(largest_surface_idがNoneでないか)"};
    K -- No --> L[メッセージ表示: 「サーフェスが見つかりませんでした」];
    L --> Z;
    K -- Yes --> M[全ての選択を解除];
    M --> N[largest_surface_idのオブジェクトを選択];
    N --> O[メッセージ表示: 「最大面積のサーフェスを選択しました」];
    O --> Z;

ChatGPT の回答

最初の回答がこちら。

# -*- coding: utf-8 -*-
import rhinoscriptsyntax as rs

def select_largest_surface():
    """
    現在選択されているサーフェスの中から、面積が最大のサーフェスのみを選択状態にする
    """
    # 現在の選択オブジェクトを取得
    sel = rs.SelectedObjects()
    if not sel:
        rs.MessageBox("サーフェスが選択されていません。", 0, "エラー")
        return

    # サーフェスのみ抽出
    surfaces = [obj for obj in sel if rs.IsSurface(obj)]
    if not surfaces:
        rs.MessageBox("選択されたオブジェクトにサーフェスが含まれていません。", 0, "エラー")
        return

    # 面積を計算し、最大面積のサーフェスを特定
    max_surf = None
    max_area = 0.0
    for s in surfaces:
        area_info = rs.SurfaceArea(s)
        if area_info:
            area = area_info[0]
            if area > max_area:
                max_area = area
                max_surf = s

    if not max_surf:
        rs.MessageBox("サーフェスの面積を計算できませんでした。", 0, "エラー")
        return

    # サーフェス以外の選択を維持する場合はコメントアウト
    # rs.UnselectAllObjects()

    # 最大面積以外のサーフェスを選択解除
    for s in surfaces:
        if s != max_surf:
            rs.UnselectObject(s)

    # 最大面積サーフェスを選択(念のため)
    rs.SelectObject(max_surf)

if __name__ == "__main__":
    select_largest_surface()

こちらは問題なく実行できました。

が、用意していたテストケースのうち、サーフェスとカーブを入力し実行したときに、最大のサーフェスとカーブが選択状態となり、思っていた通りには機能しませんでした。が、仕様として明記しなかったのでプログラムが間違っているということではありません。

ここで十分なテストケースを用意せずにプロジェクトの中で作業をすすめていたら、最大のサーフェスを取り出すはずが、他のオブジェクトも拾ってしまうこともあったと思います。AI が書いたプログラムを読まずに使うことや、検証せずに使うということは危ないなと改めて思いました。

期待していた動作とは異なることを一言指摘すると、即座に修正してくれました(実はすでに実装されていてコメントアウトを解除すればOKでした)。

思考時間: 5 秒

ChatGPT にもフローチャートをお願いしました。ChatGPT と Gemini では、Surface か否かを判別するタイミングが違いますね。

flowchart TD
    A([Start]) --> B[/Get selected objects/]
    B --> C{Any selected objects?}
    C -- No --> D[No objects selected]
    D --> Z([End])
    C -- Yes --> E[/Filter to surfaces/]
    E --> F{Any surfaces?}
    F -- No --> G[No surfaces selected]
    G --> Z
    F -- Yes --> H[/Initialize max_area=0<br>max_surface=None/]
    H --> I[/Loop through each surface/]
    I --> J[/Compute area of current surface/]
    J --> K{area > max_area?}
    K -- Yes --> L[/max_area = area<br>max_surface = surface/]
    K -- No --> M[Do nothing]
    L --> M
    M --> N{More surfaces?}
    N -- Yes --> I
    N -- No --> O[/Unselect all objects/]
    O --> P[/Select max_surface/]
    P --> Z

オマケ

さらに追加の実験として、ChatGPT の初回の回答を Gemini にレビューさせてみました。コードだけを投げていますが、Gemini は正確且つすごく丁寧にレビューしてくれました。ミスと決めつけず丁寧な解説がされ見習いたいと思いました。

自分の書いたコードとして ChatGPT のコードのレビューを Gemini 依頼

とても丁寧なコメント

まとめ

2025/08/08 の時点での、ChatGPT と Gemini を用いて RhinoPython をどのくらい書けるかという検証でした。RhinoPython 単体で形をいじくることはあまりないので②が余裕ならばそこそこ便利かもと思いました。

ややこしいコードまでまだ試し切れていないので、もう少しチェックしていきたいです。とはいえ、やはり存在しない関数を呼び出してしまうことを確認できたり、仕様の説明を適切にしないと思った通りのものは作れないという学びを得たり、さらには、丁寧なレビューを AI から学んだりと、個人的には面白い検証ができたのではと思います。

もう少し複雑な作業や、GhPython 用、RhinoCommon 等も含めた細々した実装などについては、またいつか記事にするかもしれません。

(今後に向けて)

記事をまとめてから改めて読み返してみると、AI 無しでもプログラムを書ける人がエラーメッセージの内容や起きている現象を簡潔に漏れなく伝えているので修正がスムーズだったような気もしました。

もう少し大きなプログラムに対して、完全な初学者が指示を出して、AI がコードを書いて、実行して、出たエラーメッセージをそのまま AI にコピペするといったもう少しの乱暴なプロセスでやるともう少し苦労するかもなとも思いました。次回機会があればそういった乱暴なプロセスでの場合も検証してみるかもしれません。

(終わり)