見出し画像

デジラボ第0回-C#でオリジナルのgrasshopperコンポーネントを作成する-

はじめに

北九大/DN-Archiの藤田です。学生のコンピュテーショナルデザインツールのスキル向上を目的として,学生主体の勉強会「デジラボ」を立ち上げました。先行してYoutubeチャンネルを作っております。

上記チャンネルは,私が開発したOpensees for Grasshopperと呼ばれる構造解析プラグインの紹介動画の置き場にもしています。下記はその例です。

本当は4月から勉強会をスタートさせて,アーカイブを残していくつもりでいたのですが,コロナ禍でそれどころではなくなっしまって,活動できずにいました。緊急事態宣言も解除されたことですし,6/5(金)から正式にスタートさせようと思います。隔週で金曜日の朝9:00~にデジラボ勉強会を開催することになりました。

第1回~は学生が交代で講師となり,教員は基本的にタッチしないつもりですが,まずは第0回(プレ勉強会)ということで,段取りを確認する意味合いも含め,私の方で最近独学を始めたC#と,C#によるGrasshopper用コンポーネントの作り方について簡単に共有します。

事前にインストールする必要があるもの

Microsoft Visual Studioをインストールします。2020年5月現在の最新版は2019です。バージョンは無料で使用できるコミュニティでOKです。

インストーラーは下記よりダウンロードできます。

exeファイルを起動し,続行を押すと,下記の画面が立ち上がりますので,.NET デスクトップ開発にチェックを入れ,インストールを開始します。このとき,Rhino6.0との互換性のために右側のタブで.NET Framework 4.6.1開発ツールにもチェックを入れてください。

画像2

インストールが終わったら,下記の画面が立ち上がります。Microsoftアカウントを持っていない場合は新たに作成し,サインインしておきましょう。なお,大学のアドレスなどに紐ついているアカデミックアカウントではサインインできませんので注意してください。

画像2

サインインが終わるとVisual Studioが立ち上がりますが,一旦閉じ,下記サイトからGrasshopper用テンプレートを入手します。

ダウンロードしたファイルを実行すると下記の画面

画像3

が立ち上がるので,installをクリックします。インストールが終わったら再びVisual Studioを立ち上げます。新しいプロジェクトの作成でGrasshopper Add-On for v6 (C#)を選択し,.NET Frameworkのバージョンは4.6.1を選択します。プロジェクトの保存場所を指定し,プロジェクト名やソリューション名を指定したら作成ボタンを押してください。プロジェクト名やソリューション名はデフォルトではMyProject1となります。これは後で変更可能です。

画像4

作成ボタンを押すと次のような画面が立ち上がります。今日は,閉じたカーブで描画される任意の断面の断面性能を求めるコンポーネントを作ってみたいと思います。下記のように名前付けしておきましょう。

画像5

すると下記のようにVisual Studioのエディタが立ち上がります。

コンポーネントの作成

SectionPerformanceCalculation.csを編集し,コンポーネントを作っていきます。Info.csファイルの方は基本的にはいじらなくてOKです。

画像6

まず,下記のようなコードが自動生成されていると思います。

public SectionPerformanceCalculation()
         : base("SectionPerformanceCalculation", "SecPerfCalc",
             "Calc section performance",
             "Maths", "Util")
       {
       }

baseの引数は,base("①コンポーネントのフルネーム","②省略名(glasshopperキャンバスに表示される名前)","③コンポーネントにカーソルを合わせると出てくる説明文","④カテゴリー名","⑤サブカテゴリー名")となっています。例えば,デフォルトで入っているコンポーネントであるGHPythonの場合は下記のようになります。お好みで各自編集してみてください。

画像7

続いて,少し下に行くと,下記のようなソースコードブロックがあると思います。

protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
       {
       }

ここではinputパラメータを定義します。今回作るコンポーネントでは,レイヤ名をinputデータとしたいと思いますので,下記のように記述します。

protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
       {
           pManager.AddTextParameter("layer", "layer", "layername", GH_ParamAccess.item, "");
       }


pManagerにはAddTextParameterのほかにも,引数が実数の場合に使用するAddNumberParameterや,整数の時に使用するAddIntegerParameter,点のジオメトリの時に使用するAddPointParameterなど,inputデータの方に応じた関数が用意されています。詳細はこちらを参照ください。引数は,AddTextParameter("①ソースコード内で使用する名前","②input名","③説明文","④itemかlistかtreeかの区別","⑤デフォルト値(optional)")となっています。作られるinputは下図のようになります。

画像8

①と②は特段の理由がなければ同じ名前として差し支えありません。もし名前を変える場合,例えば①を"layer name of geometry"とした場合には,①②の部分はlayer name of geometry(layer)のように表示されます。また,今回は④でGH_ParamAccess.itemを指定しているので,表示はlayerとなりますが,例えばinputデータをリストとして読み込みたい場合は,④をGH_ParamAccess.listとします。その場合,layer as listのように後ろの青かっこにデータ構造の説明が付きます。treeの場合も同様です。⑤のデフォルト値は省略可能です。

さて,inputパラメータの定義が終わったら,次に

protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
       {
       }

の部分でoutputパラメータを定義します。今日つくるプログラムでは下記のようにしました。

protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
       {
           pManager.AddBrepParameter("S", "S", "created surface from closed curve", GH_ParamAccess.item);
           pManager.AddPointParameter("C", "C", "centroid", GH_ParamAccess.item);
           pManager.AddNumberParameter("A", "A", "area f the closed curve", GH_ParamAccess.item);
           pManager.AddNumberParameter("Ix", "Ix", "second moment around global x axis", GH_ParamAccess.item);
           pManager.AddNumberParameter("Iy", "Iy", "second moment around global y axis", GH_ParamAccess.item);
           pManager.AddNumberParameter("Zxt", "Zxt", "cross-sectional coefficient around global x axis(top)", GH_ParamAccess.item);
           pManager.AddNumberParameter("Zxb", "Zxb", "cross-sectional coefficient around global x axis(bottom)", GH_ParamAccess.item);
           pManager.AddNumberParameter("Zyr", "Zyr", "cross-sectional coefficient around global y axis(right)", GH_ParamAccess.item);
           pManager.AddNumberParameter("Zyl", "Zyl", "cross-sectional coefficient around global y axis(left)", GH_ParamAccess.item);
       }

引数の構造はデフォルト値を設定できないこと以外はinputデータの時と全く同様です。①~④までを指定します。今回のプログラムでは,

S:線分や曲線で囲まれた領域で構成された平面(Brep Surface)
C:図心位置のジオメトリ(点)
A:Sの面積
Ix:Sの全体X軸周りの断面二次モーメント
Iy:Sの全体Y軸周りの断面二次モーメント
Zxt:Sの全体X軸周りの断面断面係数(上端引張時)
Zxb:Sの全体X軸周りの断面断面係数(下端引張時)
Zyr:Sの全体Y軸周りの断面断面係数(右端引張時)
Zyl:Sの全体Y軸周りの断面断面係数(左端引張時)

をoutputデータとして出力します。

続いて,ソースコードのトップにあるモジュールの呼び出し部分に,
using Rhinoとusing Rhino.DocObjectsを追記しておきます。

using System;
using System.Collections.Generic;
using Grasshopper.Kernel;
using Rhino;
using Rhino.Geometry;
using Rhino.DocObjects;

ここまで準備ができたら,いよいよ

protected override void SolveInstance(IGH_DataAccess DA)
       {
       }

の中にプログラムの根幹部分を記述していきます。プログラムの完成形を先に示しておきます。

protected override void SolveInstance(IGH_DataAccess DA)
       {
           var layer = ""; DA.GetData("layer", ref layer);
           var lines = new List<Curve>();
           if (layer != "")
           {
               var doc = RhinoDoc.ActiveDoc;
               var obj = doc.Objects.FindByLayer(layer);
               for (int i=0; i<obj.Length; i++)
               {
                   lines.Add(new ObjRef(obj[i]).Curve());
               }
           }
           var surf = new Brep();
           if (lines.Count == 1)
           {
               surf = Brep.CreatePlanarBreps(lines[0])[0];
           }
           else
           {
               surf = Brep.CreateEdgeSurface(lines);
           }
           DA.SetData("S", surf);
           var A = surf.GetArea(); DA.SetData("A", A);//断面積の計算
           var mass = AreaMassProperties.Compute(surf);//断面性能の計算
           var C = mass.Centroid; DA.SetData("C", C);//図心
           var I = mass.CentroidCoordinatesSecondMoments;//y軸周りおよびx軸周りの断面二次モーメント
           var Ix = I[1]; var Iy = I[0]; DA.SetData("Ix", Ix); DA.SetData("Iy", Iy);
           var bb = surf.GetBoundingBox(true);//bounding box
           var cc = bb.GetCorners();//コーナーの座標
           var c1 = cc[0]; var c2 = cc[2];
           var ex1 = c2[1] - C[1]; var ex2 = C[1] - c1[1];//上端および下端の中立軸までの距離
           var ey1 = c2[0] - C[0]; var ey2 = C[0] - c1[0];//右端および左端の中立軸までの距離
           var Zxt = Ix / ex1; var Zxb = Ix / ex2; DA.SetData("Zxt", Zxt); DA.SetData("Zxb", Zxb);
           var Zyr = Iy / ey1; var Zyl = Iy / ey2; DA.SetData("Zyr", Zyr); DA.SetData("Zyl", Zyl);
       }

プログラムの中身を文章で説明するのは少し難しいため,詳細はYoutubeにあげている動画

を視聴いただけたらと思いますが,簡潔にプログラムの中身を説明します。

まず,

var layer = ""; DA.GetData("layer", ref layer);
var lines = new List<Curve>();
if (layer != "")
{
    var doc = RhinoDoc.ActiveDoc;
    var obj = doc.Objects.FindByLayer(layer);
    for (int i=0; i<obj.Length; i++)
    {
        lines.Add(new ObjRef(obj[i]).Curve());
    }
}

の部分ですが,inputデータであるlayer名をlayerというstring型の変数に格納しています。inputデータにlayer名が入力されている場合には,var doc = RhinoDoc.ActiveDocで現在開いているRhino画面を参照し,layer名に合致するオブジェクトをvar obj = doc.Objects.FindByLayer(layer)で検索し格納しています。さらにlines.Add(new ObjRef(obj[i]).Curve())の部分でobj内の直線あるいは曲線情報をlinesに格納しています。

このプログラムでは,
A.面を構成する線分が1つの閉じたポリラインである場合
と,
B.複数の線分のつなぎ合わせで構成される場合
とで面の作り方を分けています。

var surf = new Brep();
if (lines.Count == 1)
{
    surf = Brep.CreatePlanarBreps(lines[0])[0];
}
else
{
    surf = Brep.CreateEdgeSurface(lines);
}

最初のif分岐がAのケースの場合の平面生成,elseの部分がBのケースに対応しています。CreatePlanarBrepsは1つの閉じた曲線(item)から平面を生成する関数,CreateEdgeSurfaceは境界(edge)を構成する複数の曲線で囲まれた平面を生成する関数です。

断面性能を求める面の生成ができれば,あとは図心,断面積,断面二次モーメント,断面係数をそれぞれ求めていくだけです。本来であれば,断面二次モーメントなどを求めるためには面に対して積分計算を行う必要がありますが,grasshopperでは既に断面二次モーメントを計算する関数は用意されているので,そちらを利用し,下記のようなコードとしています。

DA.SetData("S", surf);
var A = surf.GetArea(); DA.SetData("A", A);//断面積の計算
var mass = AreaMassProperties.Compute(surf);//断面性能の計算
var C = mass.Centroid; DA.SetData("C", C);//図心
var I = mass.CentroidCoordinatesSecondMoments;//y軸周りおよびx軸周りの断面二次モーメント
var Ix = I[1]; var Iy = I[0]; DA.SetData("Ix", Ix); DA.SetData("Iy", Iy);
var bb = surf.GetBoundingBox(true);//bounding box
var cc = bb.GetCorners();//コーナーの座標
var c1 = cc[0]; var c2 = cc[2];
var ex1 = c2[1] - C[1]; var ex2 = C[1] - c1[1];//上端および下端の中立軸までの距離
var ey1 = c2[0] - C[0]; var ey2 = C[0] - c1[0];//右端および左端の中立軸までの距離
var Zxt = Ix / ex1; var Zxb = Ix / ex2; DA.SetData("Zxt", Zxt); DA.SetData("Zxb", Zxb);
var Zyr = Iy / ey1; var Zyl = Iy / ey2; DA.SetData("Zyr", Zyr); DA.SetData("Zyl", Zyl);

SetData("①ソースコード内で使用する名前",②outputする変数)で,求めたジオメトリや断面性能をoutputデータとして保存します。①はRegisterOutputParamsで定義した引数と一致している必要があります。

Brepに対してGetArea()を適用すると,面積を計算することができます。

AreaMassProperties.Compute()は,Brepを引数にとることで,その面の断面性能の情報群を計算することができます。本プログラムではmassという変数に断面性能の情報群を格納しています。この変数に対して
Centroid
CentroidCoordinatesSecondMoments
を適用すると,それぞれ図心,断面二次モーメント(ただしIy,Ixの順でリストとして出力)を取得できます。

断面係数を直接計算する関数は存在しないのですが,図心からの距離をeとしたとき,断面係数はZ=I/eの関係にありますから,Brepに対してGetBoundingBox(true)関数を適用し,GetCorners()でコーナーの座標を取得することで図心までの距離が計算できますので,そこからZを計算することができます。コーナーの座標は,左下,右下,右上,左上の順に格納されますので,プログラム内では左下の座標と右上の座標をそれぞれc1,c2という変数に格納し,図心からの距離を計算しています。

この状態で一度コンパイルをしてみてください。無事成功すれば,binフォルダの中にMyProject1.ghaファイルが生成されているはずです。これをgrasshopperのコンポーネントフォルダに入れた状態で起動すれば,作成されたコンポーネントがiconがない状態で存在し,使用できるようになっていると思います。

画像9

こんな感じで,任意断面の断面性能を求めるコンポーネントをgrasshopper上で利用できるようになりました。

iconを設定する

このままでもプログラムとしては機能しますが,iconがないのが少し寂しいですので,作ってみましょう。アイコンは24ピクセル×24ピクセルのpngデータで用意します。イラレやinkscapeなどで用意しましょう。そして,名前は何でも良いですが,ソースコードが入っているフォルダ内に新たにiconというフォルダを作成し,そこに適当な名前で保存します。ここでは,aiz.pngとして保存しました。

画像10

次に,Visual studioに戻り,右側のソリューションエクスプローラでPropertiesをダブルクリックし,リソース→リソースの追加→既存ファイルの追加をクリックし,先ほど作成したpngファイルを選択し,Ctrl+Sで保存します。

第0回デジラボアーカイブ_edit.mp4_003967440

第0回デジラボアーカイブ_edit.mp4_003975400

これでプロジェクトにicon画像データが追加されたので,SectionPerformanceCalculation.csに戻り,

protected override System.Drawing.Bitmap Icon
{
    get
    {
        // You can add image files to your project resources and access them like this:
        //return Resources.IconForThisComponent;
        return MyProject1.Properties.Resources.aiz;
    }
}

と記述すれば,追加した画像がiconとして設定されます。ビルドの後,再びgrasshopperのコンポーネントフォルダにghaファイルを上書きしてgrasshopperを起動すれば,コンポーネントにiconが設定されているのが確認できると思います。

画像13

最後に,MyProject1という名前を変更したい場合は,Visual studio上でファイル名の変更をしたうえで,Ctrl+HでMyProject1という文字列をソリューション全体でリネームするとできます。

おわりに

以上,簡単ですがC#を用いてgrasshopperのオリジナルコンポーネントを作る方法の紹介でした。本当はグラフィックユーザーインターフェースの実装など,もう少し凝ったコンポーネントを作成するためには追加の知識が必要なのですが,それはまた機会があれば紹介したいと思います。次回からは学生が主体となって勉強会がスタートし,それに関する記事がnoteにアップされ,動画アーカイブもyoutubeチャンネルにアップロードされる予定です。まだまだ学生はgrasshopperなどのツールは初心者ですので基礎的な機能に関する勉強会になると思いますが,あたたかく見守っていけたらと思います。

この記事が気に入ったらサポートをしてみませんか?