ChatGPTを使ってGrasshopperのC#スクリプトを作成してみる

話題のAIツールであるChatGPTを活用して、Grasshopperで使用するC#スクリプトが作成できるか、作成できるとすればどのように指示するのが効果的か試してみました。

1.C# Scriptコンポーネントを用意

まずはGrasshopperを開き、C# Scriptコンポーネントを配置します。

2.ChatGPTを開く

ChatGPTは、OpenAI社が開発した人工知能ツールです。人間のテキストを学習し、それに基づいて会話形式で新しいテキストを生成します。
参考:Introducing ChatGPT

登録方法などは割愛しますが、メールアドレスがあれば簡単にアカウント作成できます。
Webブラウザでログインし、下図の赤枠の部分にテキストを入力すると回答が返ってきます。

3.コード作成をお願いしてみる

早速、テキストを入力してみます。

「GrasshopperのC#スクリプトで、予め用意した曲線領域の内側にランダムな大きさの矩形をランダムに配置するC#コードを教えて」

と送ってみます。

以下が返答です。
コードに加え、解説付きで教えてくれました。

クリックで拡大表示します

コードだけでなく、C#コンポーネントを配置するところから教えてくれているのでとても親切です。ひとまず信用してそのままコピーしてみます。

4.C#コンポーネントにコードを貼り付け

Grasshopperに戻ります。コンポーネントを拡大すると、端子の部分に「+」「ー」のマークが現れるので「+」を押して端子を増やします。今回入力データは4つのようなので端子が4つになるようにします。

変数名は、端子部分を右クリックしたときに出る白い枠の中に入力すると反映されます。

変数型は、Type hintで選択します。それぞれ以下のように設定します。

・変数名:curve Type hint:Curve(曲線)
・変数名:count Type hint:int(整数)
・変数名:sizeMin Type hint:double(実数)
・変数名:sizeMax Type hint:double(実数)

C#コンポーネントをダブルクリックしてエディタを開き、先ほどコピーしたコードを貼り付けます。以下は貼り付け時の注意です。

・先頭の「private void RunScript(Curve curve,・・・」の部分は端子で設定したものが反映される部分なので貼り付けるコードからは省きます。

・末尾の「private static readonly Random Random = new Random();」は、メイン外の部分なので「 // <Custom additional code>」以下に貼り付けます。

エディタ右上の再生ボタンを押して結果を見てみます。
エラーが3つ出ました。

「1. Error (CS1729): ‘Rhino.Geometry.Rectangle3d’ に、引数を 4 個指定できるコンストラクターがありません (line 75)」
「2. Error (CS0117): ‘Rhino.Geometry.Curve’ に ‘PlanarCurveContainment’ の定義がありません。 (line 78)」
「3. Error (CS0103): 名前 ‘ContainmentStatus’ は現在のコンテキスト内に存在しません。 (line 78)」

どうやら存在しないメソッドやプロパティを使おうとしているようです。このあたりの精度はまだ完璧ではないようですね。

5.結果をChatCPTにフィードバック

諦めずにこの結果をフィードバックしてみます。C#コンポーネントのエラーバルーンをクリックしてエラーをコピーし、以下のようにChatGPTに送ってみます。

以下のような回答が来ました。

潔く誤りを認めて修正したコードを作成してくれました。どこが間違っていたか解説もしてくれます。

クリックで拡大表示します

再度コピーし、C#コンポーネントに貼り付けます。
結果を見るとエラーが1つに減りました。惜しいです。

しかし、この後同じ方法で5回ほど問答を繰り返しましたが、残念ながらこの方法だけでエラーの無いコードにたどり着くことはできませんでした。

質問の仕方が悪かったのか一気に作るのは難しそうなので、コードのどこがうまくいっていないか確認して質問の仕方を変えてみます。

6.考察

どうやら矩形作成まではうまくいっており、最後のそれぞれの矩形が領域内に完全に入っているか(交差がないか)をチェックする部分でつまづいているようです。

この内容でのエラーは以下の通りでした(このエラーをフィードバックしても解決されませんでした)。
「1. Error (CS1501): 引数を 6 個指定できる、メソッド ‘CurveCurve’ のオーバーロードはありません (line 83)」

Intersect関連は引数や型の種類が多いので難しいのかもしれません。

正直、矩形の中心点が曲線内に含まれるかどうかくらいのチェックになるかなと思っていたのでそれなりに複雑なことをしようとしていたようです。
ここまで必要なければちゃんと質問に明記しないといけないですね。

試しに、IF文をコメントアウトして、パラメータを入力し結果を見てみるとたしかに矩形作成はできています。

7.質問の仕方を変えてみる

反省を活かし、以下のように刻んで質問してみます。

クリックで拡大表示します

上記内容を参考に、先ほどうまくいかなかったC#コンポーネントの後半のコードを書き換えます。

この内容で実行すると、正しそうな結果が表示されました!

さらにこの後、何度か追加でやり取りは必要でしたが、当初チャレンジしてくれた領域カーブと交差する矩形を除外する処理もでき、最後まで人間は一切コードは書かずにコピーペーストでの切り貼りだけで実現できました。

8.まとめ

今回の実験で、いくつかコツのようなものがわかったのでまとめます。

・変数名、データ型、単数か複数か、何で何を判定するかなどの目的語は漏れなく具体的に記入する

・特にAPI関連は似たものが多く、引数の違いなど種類が豊富なため、より具体性に気を付ける

・一気にまとめてでうまくいかない場合は質問を刻んでみる

(その際、質問毎に異なる変数が定義されないように変数名とデータ型を明記)

・エラーが出た場合はエラー内容をそのまま伝えれば修正してくれる

(ただし必ず正しく修正されるとは限らないためうまくいかない場合は質問の仕方を変えてみる)

・使用する言語やライブラリ(C#・Python・RhinoCommonなど)、Rhinoのバージョンなど環境に関するものは最初に指示しておく

参考に以下が、最終的に得られたコードです。

private void RunScript(Curve curve, int count, double sizeMin, double sizeMax, ref object A)
  {

    List<Rectangle3d> rects = new List<Rectangle3d>();

    // 曲線領域の境界ボックスを取得
    BoundingBox bbox = curve.GetBoundingBox(false);

    for (int i = 0; i < count; i++)
    {
      // 矩形の大きさをランダムに決定
      double size = sizeMin + Random.NextDouble() * (sizeMax - sizeMin);

      // 矩形の位置をランダムに決定
      Point3d pt = new Point3d(
        bbox.Min.X + size + Random.NextDouble() * (bbox.Max.X - bbox.Min.X - size * 2),
        bbox.Min.Y + size + Random.NextDouble() * (bbox.Max.Y - bbox.Min.Y - size * 2),
        0);

      // 矩形を作成
      Plane plane = new Plane(pt, Vector3d.ZAxis);
      Rectangle3d rect = new Rectangle3d(plane, size, size);

      // 曲線rectの重心を求める
      Point3d centroid = rect.Center;

      if (curve.Contains(centroid, Plane.WorldXY, RhinoMath.ZeroTolerance)
        != PointContainment.Inside)
      {
        // centroidがcurveに内包されていない場合の処理
      }
      else
      {
        // centroidがcurveに内包されている場合の処理
        List<Point3d> intersectionPoints = new List<Point3d>();

        // 矩形をポリラインに変換する
        Polyline rectPolyline = new Polyline(rect.ToPolyline());

        // 矩形の辺と曲線との交点を計算する
        foreach (Line rectEdge in rectPolyline.GetSegments())
        {
          CurveIntersections curveIntersections 
            = Intersection.CurveCurve(curve, rectEdge.ToNurbsCurve(), 
            RhinoDoc.ActiveDoc.ModelAbsoluteTolerance, 
            RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);

          // 各交点をリストに追加する
          foreach (IntersectionEvent intersection in curveIntersections)
          {
            intersectionPoints.Add(intersection.PointA);
          }
        }
        if(intersectionPoints.Count == 0)
        {
          // intersectionPointsが0個の場合の処理
          rects.Add(rect);
        }
      }
    }

    A = rects;

  }

  // <Custom additional code> 

  private static readonly Random Random = new Random();

プログラムやGrasshopperアルゴリズムは、動く動かないだけでなく、変数や文法の統一感、無駄のない効率的な書き方なども重要ですのでそのあたりの再現性は課題ですが、

「これからC#やPythonを勉強したいけど何から書いていいかわからない」

「あのコードってこの言語だとどうやって書くんだっけ?」

「RhinoCommonでこの内容を実現できる方法ないかな?」

などの場合に最初にとりあえず試してみる方法としては非常に便利だと感じました。
精度については、ちょうどGPT4が発表されたばかりということもあり、今後ますます発展、改良されていくと思いますので期待ですね。

ご興味のある方はぜひ試してみてください。