【Rhino+Grasshopperを理解して魔改造しよう】③Grasshopperアドオンの開発

【Rhino+Grasshopperを理解して魔改造しよう】③Grasshopperアドオンの開発【Let's understand Rhino+Grasshopper and customize】Developing Grasshopper Add-on with Custom Goo and Param

はじめに

前章で述べた通り、Grasshopperはその構成要素であるGoo, Param, Componentを自分で追加して利用することが可能です。

簡単な用途ではComponentの追加で事足りますが、自前の形状表現のクラスをGrasshopper上に組み込もうとするとGooとParamの追加が求められてきます。

本章では、チュートリアルや記事が膨大に存在するComponent実装ではなくGooとParamの実装について、コードを交えて紹介していきます。

C#およびVisualStudioなどの開発環境、公式のプロジェクトテンプレートの利用、デバッグの方法などは沢山解説があるのでそちらを見ていただければと思います。そのあたりの知識を前提として書いていきます。

本記事に対応するリポジトリ

https://gitlab.com/natureKotarou/GHAddonDemo

扱うデータ構造

今回は例として有限要素法などで用いられる六面体メッシュを表現するクラスを自前で用意し、それをGrasshopper上で扱えるようにしてみます。

六面体メッシュは、

  • 節点のリスト
  • HexElement(節点を繋いで定義される六面体。含む節点のインデックスの組から定義される)のリスト

から成るHexMeshクラスとします。

実際に有限要素法に使うにはさまざまな抽象化や付加的な情報が必要ですが、ここでは例としてこれに留めます。

GHAddonDemo/Core/HexMesh.cs

public class HexMesh
{
    public List<Point3d> Nodes { get; private set; }
    public List<HexElement> Elements { get; private set; }

    /*
    some constructors, properties and methods
    */

}

GHAddonDemo/Core/HexElement.cs

public class HexElement
{
    public int[] NodeIndices { get; }
    public int this[int index] => NodeIndices[index];

    /*
    some constructors, properties and methods
    */
}

Goo,Param,Componentのミニマム実装

前章の通り、GooはデータをGrasshopper内で扱うためのラッパークラス、ParamはGooの保持と受け渡しをするクラス、Componentは、入力ParamからGooを受け取って処理を実行し出力ParamにGooを受け渡すクラスです。

今回扱いたいHexMeshクラスのGooとParam、および例としてSurfaceからHexMeshを定義するComponentを実装していきます。

GH_Goo<HexMesh>の実装

最も基本的な例としてGH_Gooを継承したGH_HexMeshを実装してみます(リンク先のコードは完成形のものとなるので部分的に異なります)。

GH_Gooを継承したクラスでは、下記の実装が求められます

  • コンストラクタ
  • TypeName
    タイプの名前となる文字列を返すプロパティ
  • TypeDescription
    タイプの概要となる文字列を返すプロパティ
  • IsValid
    Gooに格納された情報がValidであるかを返すプロパティ
  • Duplicate()
    自身のディープコピーを返すメソッド
  • ToString()
    自身の情報を文字列として返すメソッド

GHAddonDemo/GH/GH_HexMesh.cs

public class GH_HexMesh : GH_Goo<HexMesh>
{
    public GH_HexMesh()
    {
    }

    public GH_HexMesh(HexMesh internal_data) : base(internal_data)
    {
    }

    public GH_HexMesh(GH_Goo<HexMesh> other) : base(other)
    {
    }

    public override string TypeName => "GH_HexMesh";

    public override string TypeDescription => "Goo for HexMesh class.";

    public override bool IsValid => Value != null && Value.IsValid;

    public override IGH_Goo Duplicate()
    {
        return new GH_HexMesh(new HexMesh(Value));
    }

    public override string ToString()
    {
        return Value.ToString();
    }
}

GH_Param<GH_HexMesh>の実装

更に、GH_Paramを継承してParamを実装してみます。

ここでは、コンストラクタに加えComponentGuidプロパティの実装が求められます。これはFloatingに設定されコンポーネントのようにUI上で扱われる際に必要となるもので、ほかのParamとComponentと重複しなければ問題ありません。

また、余談ですがここで引数なしのコンストラクタを定義しておくとFloatingのパラメータが利用可能となります(GH立ち上げ時に走査フォルダ内にあるアセンブリからReflectionを用いてコンポーネントが読み込まれるようで、その際にここが分岐となっているようです)

GHAddonDemo/GH/Param_HexMesh.cs

public class Param_HexMesh : GH_Param<GH_HexMesh>
{
    public Param_HexMesh() : base("HexMesh", "HM", "", "Demo", "0_Params", GH_ParamAccess.tree)
    {
    }
    public Param_HexMesh(IGH_InstanceDescription tag) : base(tag)
    {
    }

    public Param_HexMesh(IGH_InstanceDescription tag, GH_ParamAccess access) : base(tag, access)
    {
    }

    public Param_HexMesh(string name, string nickname, string description, GH_ParamAccess access) : base(name, nickname, description, "Demo", "Mesh", access)
    {
    }

    public override Guid ComponentGuid => new Guid("10f558b7-e786-482b-b0c2-de589b284bfd");
}

Paramを入出力として利用したGH_Componentの実装

コンポーネントの実装方法については他コンテンツに譲りますが、GH_Componentクラスを継承してクラスを定義します。

ここでは試しに、Surface(NURBSで定義された面)、オフセット量、UVW方向それぞれの分割数、オフセット方向の反転の有無を入力にSurfaceの法線方向を利用してUVWグリッド状にHexMeshを定義するコンポーネントを書いてみます。詳細はリポジトリをご覧ください。

GHAddonDemo/GH/HexMeshFromSurfaceOffset.cs

public class HexMeshFromSurfaceOffset : GH_Component
{
    public HexMeshFromSurfaceOffset() : base("HexMeshFromSurfaceOffset", "HexMeshFromSrfOffset", "Define HexMesh from offset of Surface.", "Demo", "1_Mesh")
    {
    }

    public override Guid ComponentGuid => new Guid("14b945cc-c4bd-4c88-854b-d5b4308616a6");

    protected override void RegisterInputParams(GH_InputParamManager pManager)
    {
        pManager.AddSurfaceParameter("Surface", "S", "Untrimmed Surface to generate HexMesh.", GH_ParamAccess.item);
        pManager.AddNumberParameter("Offset", "O", "Offset distance", GH_ParamAccess.item, 1);
        pManager.AddIntegerParameter("UDivision", "U", "Division along U-direction", GH_ParamAccess.item, 10);
        pManager.AddIntegerParameter("VDivision", "V", "Division along V-direction", GH_ParamAccess.item, 10);
        pManager.AddIntegerParameter("WDivision", "W", "Division along W-direction", GH_ParamAccess.item, 1);
    }

    protected override void RegisterOutputParams(GH_OutputParamManager pManager)
    {
        pManager.AddParameter(new Param_HexMesh_Geometric("HesMesh", "HM", "Generated HexMesh", GH_ParamAccess.item));
    }

    protected override void SolveInstance(IGH_DataAccess DA)
    {
        /*
        入力を取得しHexMeshを定義し出力に渡す処理
        */
    }
}

自前のParamを入出力に登録する際には上記コードのRegisterOutputParamsメソッド内の記述のようにします。

ここは、GH_Componentクラスの拡張メソッドを作ったり自前Param追加用のメソッドを持つGH_Componentを継承した抽象クラスを定義するなどすると多少扱いやすいかと思います。

ここまででミニマムは出来ました。

Rhinoのビューポートにはまだ何も表示されないですが、生成されたHexMeshの節点数および要素数を見るにどうやら処理は動いているようです。

描画機能の実装

Grasshopperがデフォルトで提供しているジオメトリはRhinoビューポート上に描画されますが、それは対応するGooがIGH_PreviewDataインターフェースを、対応するParamがIGH_PreviewObjectインターフェースを実装しているからです。

Grasshopperでは、特殊なコンポーネント以外ではParamおよびその内部のGooが描画の単位となっており、定義されたParamのうち描画可能なものがビューポートに描画されていきます。

HexMeshがRhinoの世界に描画されるよう、GH_HexMeshとParam_HexMeshにそれぞれインターフェースを実装してみます。

GooにIGH_PreviewDataの実装

ここではHexMeshから抽出されたエッジが描画されるようにしてみました。

DrawViewportWiresとDrawViewportMeshesメソッドはRhinoビューポート操作(パンやズームなど)の各フレームに呼ばれるので、ここに重い処理を含むことは推奨されません。

ここでは、エッジ抽出はHexMeshの定義時および変更時にのみ実行、描画用のジオメトリを_previewLinesフィールドで保持、描画メソッド内では描画処理のみを実行という形にしています。

また、ビューポート上での表示判定に用いられるClippingBoxプロパティの実装が求められます。これは、ジオメトリ全体を包含するBoundingBoxを返すことが求められます。

GHAddonDemo/GH/GH_HexMesh.cs

public class GH_HexMesh : GH_Goo<HexMesh>, IGH_PreviewData
{
    /*
    省略
    */

    private Line[] _previewLines;

    public BoundingBox ClippingBox => new BoundingBox(Value.Nodes);

    private void UpdatePreviewGeometries()
    {
        if (Value == null) return;
        var nodes = Value.Nodes;

        var edges = Value.GetIndexSetOfEdges();
        _previewLines = edges.Select(edge => new Line(nodes[edge.OrderedIndices[0]], nodes[edge.OrderedIndices[1]])).ToArray();
    }

    public void DrawViewportWires(GH_PreviewWireArgs args)
    {
        args.Pipeline.DrawLines(_previewLines, args.Color);
    }

    public void DrawViewportMeshes(GH_PreviewMeshArgs args)
    {
    }
}

ParamにIGH_PreviewObjectの実装

ParamにおけるIGH_PreviewObjectの実装においては下のようなプロパティとメソッドの実装が求められますがこの実装方法以外を使うことは少ないです。

GH_Paramクラスに実装されているPreview_*メソッドが、Param内のGooを用いて適切な処理を行ってくれます。

GHAddonDemo/GH/Param_HexMesh.cs

public class Param_HexMesh : GH_Param<GH_HexMesh>, IGH_PreviewObject
{
    /*
    省略
    */

    public bool Hidden { get; set; }

    public bool IsPreviewCapable => true;

    public BoundingBox ClippingBox => Preview_ComputeClippingBox();

    public void DrawViewportMeshes(IGH_PreviewArgs args)
    {
        Preview_DrawMeshes(args);
    }

    public void DrawViewportWires(IGH_PreviewArgs args)
    {
        Preview_DrawWires(args);
    }
}

これでHexMeshが描画されるようになりました!

もちろん入力のSurfaceの形状パラメータや分割数などをいじってもリアルタイムに反映されます。楽しい。

他クラスとの変換機能の実装

Grasshopperがデフォルトで提供しているデータは、可能なものの間での相互変換が自動で行われますが、これはGooのCastTo/CastFromメソッドが実装されているからです。

ここを実装してあげることで、他クラスとの変換に対応することが可能となります。

GooのCastメソッドのオーバーライド

挙動としては、下記のようなタイミング呼ばれるメソッドです。

  • 異なる型のParamからParamにデータが受け渡されて変換の必要があるとき
  • GH_ComponentのSolveInstanceメソッド内で、DA.GetData系メソッドやDA.SetData系メソッドによって自身と異なる型のインスタンスとの間での受け渡しを求められて変換の必要があるとき

詳細な話ですが、ジェネリクスで指定された型T(GH_HexMeshにおいてはHexMesh型)との変換はここで書いてあげる必要があり、基本的に自前のGooでは書いてあげると便利です。それにより、コンポーネント内でのDA.GetData/SetData系メソッドからの利用時に明示的にGooを定義してから渡す必要がなくなります。

今回は表面メッシュの抽出を行うことでRhinoでデフォルトでサポートされているMeshへの変換を可能にしてみます。

GHAddonDemo/GH/GH_HexMesh.cs

public class GH_HexMesh : GH_Goo<HexMesh>, IGH_PreviewData
{
    /*
    省略
    */

    public override bool CastFrom(object source)
    {
        if (source is HexMesh hexMesh)
        {
            Value = hexMesh.DeepCopy();
            UpdatePreviewGeometries();
            return true;
        }
        else
        {
            return false;
        }
    }

    public override bool CastTo<Q>(ref Q target)
    {
        target = default;
        if (typeof(Q).IsAssignableFrom(typeof(HexMesh)))
        {
            target = (Q)(object)Value;
            return true;
        }
        else if (typeof(Q).IsAssignableFrom(typeof(GH_Mesh)))
        {
            target = (Q)(object)new GH_Mesh(Value.ToSurfaceMesh());
            return true;
        }
        else
        {
            return false;
        }
    }
}

これで、Meshとの変換が可能になりました。例としてMeshパラメータにHexMeshパラメータをつなぐと、自動で変換が行われます。

もちろん、ケースによってSurfaceやCurveなど他のジオメトリとの変換を実装したり双方向での変換を実装することも可能です。

幾何学的変換処理への対応の実装

Grasshopperがデフォルトで提供しているジオメトリは、並進や回転、拡大縮小といった幾何学的変換処理や複雑な変形処理を行うことができますが、これはGooがそれら処理に対応するIGH_GeometricGooを実装しているからです。

今回は幾何学的なHexMeshクラスを扱っているので、継承するクラスをGooからGeometricGooに変更することでGHが持つ形状処理機能への対応が可能となります。

継承元をGooからGeometricGooに

GHAddonDemo/GH/GH_HexMesh.cs

public class GH_HexMesh : GH_GeometricGoo<HexMesh>, IGH_PreviewData
{
    /*
    省略
    */

    public override BoundingBox Boundingbox => new BoundingBox(Value.Nodes);
    public BoundingBox ClippingBox => Boundingbox;

    public override IGH_GeometricGoo DuplicateGeometry()
    {
        return new GH_HexMesh(new HexMesh(Value));
    }

    public override BoundingBox GetBoundingBox(Transform xform)
    {
        var bbox = Boundingbox;
        bbox.Transform(xform);
        return bbox;
    }

    public override IGH_GeometricGoo Transform(Transform xform)
    {
        var mesh = Value.DeepCopy();

        for (int i = 0; i < mesh.NodeCount; i++)
        {
            var node = mesh.Nodes[i];
            node.Transform(xform);
            mesh.Nodes[i] = node;
        }
        return new GH_HexMesh(mesh);
    }
    public override IGH_GeometricGoo Morph(SpaceMorph xmorph)
    {
        var mesh = Value.DeepCopy();

        var morphedPts = mesh.Nodes.Select(node => xmorph.MorphPoint(node)).ToArray();
        mesh.Nodes.Clear();
        mesh.Nodes.AddRange(morphedPts);

        return new GH_HexMesh(mesh);
    }
}

ここでは追加でいくつかのプロパティとメソッドの実装が求められていますが、幾何学的変換にかかわるのはTransformとMorphメソッドです。

HexMeshでは節点の位置のみが変形に影響するので、与えられた幾何学的変換を節点に対して行うよう処理を書いています。

するとこんな感じで並進や回転、拡大縮小が可能となったり



こんな変形も可能になったりします。楽しい。

上のものに限らず曲面に沿った変形など複雑な処理が多く提供されており、用途によっては非常に多くの資産の恩恵を自前ジオメトリ表現に受けさせることができます。

おわりに

このように、少しマニアックですがGooやParamの実装を含めてGrasshopperのアドオンを実装すると様々な嬉しさがあります。

前章からの繰り返しになりますが様々な用途に便利なツールですので、是非触ってみてください。

Introduction

As mentioned in the previous chapter, Grasshopper allows you to add your own Goo, Param, and Component components.

For simple applications, adding Component is sufficient, but if you want to incorporate your own shape representation classes into Grasshopper, you will be required to add Goo and Param.

In this chapter, we will introduce the Goo and Param implementation with code instead of the Component implementation, for which there are tons of tutorials and articles.

There are many explanations of development environments such as C# and VisualStudio, use of official project templates, debugging methods, etc., so please refer to them. I will write this article assuming that you have such knowledge.

Corresponding repository

https://gitlab.com/nature_architects/ghaddondemo

Data structure to be handled

This time, as an example, we will prepare a class that represents a hexahedral mesh used in the finite element method, etc., on our own and try to handle it in Grasshopper.

A hexahedral mesh is

  • A list of nodes
  • HexElement (a hexahedron defined by connecting nodes. The HexMesh class consists of a list of HexElements (a hexahedron defined by connecting nodes, defined by the pairs of indices of the nodes it contains)

The HexMesh class consists of a HexElement (a hexahedron defined by connecting nodes.

Various abstractions and additional information are required to actually use this class in the finite element method, but we will leave it as an example here.

GHAddonDemo/Core/HexMesh.cs

public class HexMesh
{
    public List Nodes { get; private set; }
    public List Elements { get; private set; }

    /*
    some constructors, properties and methods
    */

}

GHAddonDemo/Core/HexElement.cs

public class HexElement
{
    public int[] NodeIndices { get; }
    public int this[int index] => NodeIndices[index];

    /*
    some constructors, properties and methods
    */
}

Minimal implementation of Goo, Param, and Cmp

Goo is a wrapper class for handling data in Grasshopper as described in the previous chapter, Param is a class for holding and passing Goo, and Component is a class that receives Goo from input Param, executes processing, and passes Goo to output Param.

We will now implement the Goo and Param of the HexMesh class we wish to handle and, as an example, a Component that defines a HexMesh from a Surface.

Implementation of GH_Goo<HexMesh>

As the most basic example, let's implement GH_HexMesh, which inherits from GH_Goo (the linked code is the completed version, so it is partially different).

In a class inheriting from GH_Goo, the following implementation is required

  • Constructor
  • TypeName
    property that returns a string that serves as the name of the type
  • TypeDescription
    property that returns a string that serves as a summary of the type
  • IsValid
    property that returns whether the information stored in the Goo is Valid
  • Duplicate()
    method that returns a deep copy of itself
  • ToString()
    method that returns information about itself as a string

GHAddonDemo/GH/GH_HexMesh.cs

public class GH_HexMesh : GH_Goo
{
    public GH_HexMesh()
    {
    }

    public GH_HexMesh(HexMesh internal_data) : base(internal_data)
    {
    }

    public GH_HexMesh(GH_Goo other) : base(other)
    {
    }

    public override string TypeName => "GH_HexMesh"; }

    public override string TypeDescription => "Goo for HexMesh class.";

    public override bool IsValid => Value ! = null && Value.IsValid;

    public override IGH_Goo Duplicate().
    {
        return new GH_HexMesh(new HexMesh(Value)); }
    }

    public override string ToString()
    {
        return Value.ToString(); }
    }
}

Implementing GH_Param<GH_HexMesh>

Furthermore, let's implement Param by inheriting GH_Param.

Here, in addition to the constructor, implementation of the ComponentGuid property is required. This is necessary when the property is set to "Floating" and handled in the UI like a component, so there is no problem if it does not overlap with other Param and Component properties.

Also, as a side note, if you define a constructor with no arguments here, the Floating parameter will be available (it seems that the component is loaded from the assembly in the traversal folder using Reflection when GH is launched, and this is where the branch is located). (It seems that this is a branch of the component.)

GHAddonDemo/GH/Param_HexMesh.cs

public class Param_HexMesh : GH_Param
{
    public Param_HexMesh() : base("HexMesh", "HM", "", "Demo", "0_Params", GH_ParamAccess.tree)
    {
    }
    public Param_HexMesh(IGH_InstanceDescription tag) : base(tag)
    {
    }

    public Param_HexMesh(IGH_InstanceDescription tag, GH_ParamAccess access) : base(tag, access)
    {
    }

    public Param_HexMesh(string name, string nickname, string description, GH_ParamAccess access) : base(name, nickname, description, "Demo", "Mesh ", access)
    {
    }

    public override Guid ComponentGuid => new Guid("10f558b7-e786-482b-b0c2-de589b284bfd"); }
}

Implementing GH_Component using Param as input/output

We will leave the implementation of the component to other contents, but define a class by inheriting from the GH_Component class.

As a trial, let's write a component that defines a HexMesh in the form of a UVW grid using the normal direction of the Surface as inputs for the Surface (a surface defined by NURBS), the offset amount, the number of divisions in each UVW direction, and whether the offset direction is inverted or not. See the repository for details.

[GHAddonDemo/GH/HexMeshFromSurfaceOffset.cs](https://gitlab.com/nature_architects/ghaddondemo/-/blob/master/GHAddonDemo/GH/ HexMeshFromSurfaceOffset.cs)

public class HexMeshFromSurfaceOffset : GH_Component
{
    public HexMeshFromSurfaceOffset() : base("HexMeshFromSurfaceOffset", "HexMeshFromSrfOffset", "Define HexMesh from offset of Surface.", "Demo ", "1_Mesh")
    {
    }

    public override Guid ComponentGuid => new Guid("14b945cc-c4bd-4c88-854b-d5b4308616a6");

    protected override void RegisterInputParams(GH_InputParamManager pManager)
    {
        pManager.AddSurfaceParameter("Surface", "S", "Untrimmed Surface to generate HexMesh.", GH_ParamAccess.item);
        AddNumberParameter("Offset", "O", "Offset distance", GH_ParamAccess.item, 1); pManager.
        AddIntegerParameter("UDivision", "U", "Division along U-direction", GH_ParamAccess.item, 10); pManager.
        AddIntegerParameter("VDivision", "V", "Division along V-direction", GH_ParamAccess.item, 10); pManager.
        AddIntegerParameter("WDivision", "W", "Division along W-direction", GH_ParamAccess.item, 1); pManager.
    }

    protected override void RegisterOutputParams(GH_OutputParamManager pManager)
    {
        pManager.AddParameter(new Param_HexMesh_Geometric("HexMesh", "HM", "Generated HexMesh", GH_ParamAccess.item)); }
    }

    protected override void SolveInstance(IGH_DataAccess DA)
    {
        /*
        Get input, define HexMesh and pass it to output.
        */
    }
}

When registering your own Param to the input/output, do as already described in the RegisterOutputParams method of the above code.

It may be easier to handle this by creating an extension method of the GH_Component class or defining an abstract class inheriting from GH_Component that has a method for adding your own params.

Now we have a minimum.

The wrapper is not much more than a wrapper, but it behaves as shown in the video below.

Nothing appears in the Rhino viewport yet, but the number of nodes and elements in the generated HexMesh indicates that the process is working.

Implementing the drawing function

The geometry that Grasshopper provides by default is drawn on the Rhino viewport because the corresponding Goo implements IGH_PreviewData and Param implements IGH_PreviewObject.

In Grasshopper, except for special components, the Param is the unit of rendering, and the defined Param that can be rendered is rendered in the viewport.

Let's implement interfaces to GH_HexMesh and Param_HexMesh so that HexMesh can be drawn in the Rhino world, respectively.

Implementing IGH_PreviewData in Goo

Here we try to draw the edges extracted from the HexMesh.

Since the DrawViewportWires and DrawViewportMeshes methods are called for each frame of Rhino viewport operations (pan, zoom, etc.), it is not recommended to include heavy processing here.

Here, edge extraction is performed only when defining or modifying the HexMesh, the geometry for drawing is kept in the _previewLines field, and only the drawing process is performed in the drawing method.

It is also required to implement the ClippingBox property, which is used to determine visibility in the viewport. It is required to return a BoundingBox that encompasses the entire geometry.

GHAddonDemo/GH/GH_HexMesh.cs

public class GH_HexMesh : GH_Goo, IGH_PreviewData
{
    /*
    Abbreviation.
    */

    private Line[] _previewLines;.

    public BoundingBox ClippingBox => new BoundingBox(Value.Nodes);

    private void UpdatePreviewGeometries().
    {
        if (Value == null) return;
        var nodes = Value.Nodes;

        GetIndexSetOfEdges(); var edges = Value.GetIndexSetOfEdges();
        _previewLines = edges.Select(edges => new Line(nodes[edge.OrderedIndices[0]], nodes[edge.OrderedIndices[1]])).ToArray(); }
    }

    public void DrawViewportWires(GH_PreviewWireArgs args)
    {
        args.Pipeline.DrawLines(_previewLines, args.Color); }
    }

    public void DrawViewportMeshes(GH_PreviewMeshArgs args)
    {
    }
}

Implementing IGH_PreviewObject in Param

Param requires the implementation of the properties and methods shown below, but it is rare to use anything other than this implementation method.

The Prevew_* method implemented in the GH_Param class will do the appropriate processing using the Goo in Param.

GHAddonDemo/GH/Param_HexMesh.cs

public class Param_HexMesh : GH_Param, IGH_PreviewObject
{
    /*
    Abbreviation.
    */

    public bool Hidden { get; set; }

    public bool IsPreviewCapable => true;

    public BoundingBox ClippingBox => Preview_ComputeClippingBox();

    public void DrawViewportMeshes(IGH_PreviewArgs args)
    {
        Preview_DrawMeshes(args); }
    }

    public void DrawViewportWires(IGH_PreviewArgs args)
    {
        Preview_DrawWires(args); }
    }
}

Now the HexMesh is drawn!

Of course, if you tweak the shape parameters or number of divisions of the input Surface, it will be reflected in real time. Fun.

Implementation of conversion function with other classes

The data Grasshopper provides by default is automatically converted between the possible ones, because Goo's CastTo/CastFrom methods are implemented.

By implementing this, it is possible to support conversions with other classes.

Overriding Goo's Cast method

The behavior of this method is as follows.

  • When data is passed from a Param to a Param of a different type and needs to be converted
  • In the SolveInstance method of GH_Component, when a DA.GetData type method or DA.SetData type method requires data to be passed between itself and an instance of a different type, and conversion is necessary.

As a detailed discussion, it is necessary to write the conversion between the type T (HexMesh type in GH_HexMesh) and the type T specified by generics here, and it is basically convenient to write it in your own Goo. This eliminates the need to explicitly define a Goo before passing it in when using DA.GetData/SetData-type methods in components.

In this case, we will try to extract the surface mesh to enable conversion to Mesh, which is supported by default in Rhino.

GHAddonDemo/GH/GH_HexMesh.cs

public class GH_HexMesh : GH_Goo, IGH_PreviewData
{
    /*
    Abbreviation.
    */

    public override bool CastFrom(object source)
    {
        if (source is HexMesh hexMesh)
        {
            Value = hexMesh.DeepCopy();
            UpdatePreviewGeometries();
            return true; }
        }
        }
        {
            return false; }
        }
    }

    public override bool CastTo(ref Q target)
    {
        target = default; }
        if (typeof(Q).IsAssignableFrom(typeof(HexMesh)))
        {
            target = (Q)(object)Value; if (typeof(Q).IsAssignableFrom(HexMesh))
            return true; }
        }
        else if (typeof(Q).IsAssignableFrom(typeof(GH_Mesh)))
        {
            target = (Q)(object)new GH_Mesh(Value.ToSurfaceMesh()); }
            return true; }
        }
        }
        {
            return false; }
        }
    }
}

Now you can convert to and from Mesh. As an example, if you connect a HexMesh parameter to a Mesh parameter, the conversion will be done automatically.

Of course, depending on the case, it is also possible to implement transformations with other geometries such as Surface or Curve, or in both directions.

Implementing support for geometric transformation processes

The geometry provided by Grasshopper by default can perform geometric transformations such as translation, rotation, scaling, and complex deformations, because Goo implements IGH_GeometricGoo to handle these transformations.

Since we are dealing with a geometric HexMesh class, changing the inherited class from Goo to GeometricGoo will enable GH to handle the shape processing functions.

Inheriting from Goo to GeometricGoo

GHAddonDemo/GH/GH_HexMesh.cs

public class GH_HexMesh : GH_GeometricGoo, IGH_PreviewData
{
    /*
    Omit.
    */

    public override BoundingBox Boundingbox => new BoundingBox(Value.Nodes);
    public BoundingBox ClippingBox => Boundingbox;

    public override IGH_GeometricGoo DuplicateGeometry()
    {
        return new GH_HexMesh(new HexMesh(Value)); }
    }

    public override BoundingBox GetBoundingBox(Transform xform)
    {
        var bbox = Boundingbox; bbox.
        Transform(xform); bbox.
        return bbox; }
    }

    public override IGH_GeometricGoo Transform(Transform xform)
    {
        var mesh = Value.DeepCopy();

        for (int i = 0; i < mesh.NodeCount; i++)
        {
            var node = mesh.Nodes[i];
            Transform(xform); for (int i = 0; i < mesh.NodeCount; i++) { node = mesh.
            mesh.Nodes[i] = node; node.
        }
        return new GH_HexMesh(mesh); }
    }
    public override IGH_GeometricGoo Morph(SpaceMorph xmorph)
    {
        var mesh = Value.DeepCopy();

        var morphedPts = mesh.Nodes.Select(node => xmorph.MorphPoint(node)).ToArray();
        Clear(); mesh.Nodes.
        AddRange(morphedPts); mesh.Nodes.

        return new GH_HexMesh(mesh);
    }
}

Although several additional properties and methods are required to be implemented here, it is the Transform and Morph methods that are concerned with geometric transformations.

In HexMesh, only the position of a node affects the deformation, so we write the process to perform the given geometric transformation on the node.

Then, translation, rotation, scaling, and so on are possible, like this



Such transformations are also possible. Fun.

Not only the above, but many other complex operations such as deformation along curved surfaces are provided, and depending on the application, you can benefit from a great number of assets in your own geometry representation.

Conclusion

As you can see, although a bit geeky, there are various joys in implementing Grasshopper add-ons, including Goo and Param implementations.

I repeat from the previous chapter, but it is a useful tool for various purposes, so please try it out.

Author