GrasshopperでNumberSliderにつながるワイヤーにのみ色を付ける方法
08 December, 2019 - 3 min read - Tags: CSharp,Grasshopper,Qiita
はじめに
せっかくRhinocerosForumで Rutten に教えてもらったので、その知見を残しておきます。以下のように Grasshopper コンポーネント間をつなぐワイヤーで、NumberSlider につながっているもののみに色を付ける方法を説明します。発端は Galápagos みたいなコンポーネントを作ろうとしたとき、どのようにしたらいいのかわからなくて質問したのが始まりです。
Rutten さん、いつも本当にありがとうございます。
まずは中身を知りたい人むけ
Rutten が回答で gh ファイルを上げてくれているので、すぐに試したい人はそちらをどうぞ。
説明編
回答もらって半年くらいまとめずに放っておいて、年末にせっかくまとめを作っているので、のんびり勉強もかねて使用しているものの API の個所のリンクもつけてます。
RunScript の部分
NumberSlider を対象にしたいので入力の x の値をデフォルトの object から double に変えています。そのあと EnsurePaintHandler()を呼んでここでワイヤーに赤丸を付ける処理をしています。
private void RunScript(double x, object y, ref object A)
{
EnsurePaintHandler();
A = x;
}
EnsurePaintHandler の部分
初めに描画するためのハンドラーがあるかの有無を判断するため、bool で paintHandlerAssigned をつくって false にしておきます。
EnsurePaintHandler の中で、paintHandlerAssigned が true でなかったら、今アクティブな Grasshopper のキャンバスを取得するGrasshopper.Instances.ActiveCanvasを使って、ワイヤーを描く前に起動するCanvasPrePaintWiresに PrePaintWires(これは以下で説明)を追加しています。
// <Custom additional code>
private bool _paintHandlerAssigned = false;
private void EnsurePaintHandler()
{
if (_paintHandlerAssigned)
return;
Grasshopper.Instances.ActiveCanvas.CanvasPrePaintWires += PrePaintWires;
_paintHandlerAssigned = true;
}
PrePaintWires の部分(その 1)
最初に、自分が今アクティブにしているキャンバスに対してワイヤーを描画したいので、スクリプトがあるキャンバス(GrasshopperDocument)と入力されてきているキャンバス(canvas.Document)が同じかどうかReferenceEqualsで確認して、true でなければ return にしています。
GrasshopperDocument は、C#スクリプトコンポーネン作った時デフォルトで用意されているを #region Members の中に作られていてます。その部分の抜粋も以下につけておきます。GH_Document についてはこちら
そのあと input の x (順番でいうと 0 番目)に入力されているコンポーネント数を取得するため、Component.ParamsのInputの 0 番を first としています。もし 0 個(first.SourceCount==0)なら return してます。
private void PrePaintWires(Grasshopper.GUI.Canvas.GH_Canvas canvas)
{
// We should only draw wires if the document loaded in the canvas is the document we're in.
if (!ReferenceEquals(GrasshopperDocument, canvas.Document))
return;
// Find all sliders that plug into the first component input.
var first = Component.Params.Input[0];
if (first.SourceCount == 0)
return;
// その2、その3の部分
}
#region Members
/// <summary>Gets the Grasshopper document that owns this script.</summary>
private readonly GH_Document GrasshopperDocument;
/// <summary>Gets the Grasshopper script component that owns this script.</summary>
private readonly IGH_Component Component;
#endregion
PrePaintWires の部分(その 2)
ここでは NumberSlider か判定して、NumberSlider ならば処理を加えるということをやっています。
まずは上記で取得した入力の数だけ流れるように(var source in first.Sources)で foreach しています。
その後、source を Grasshopper.Kernel.Special.GH_NumberSlider としてキャストし直して slider を作っています。as でキャストすると、キャストできないときに null で返すので、キャストの下の個所で NumberSlider でない(つまり null)なら continue になってます。
private void PrePaintWires(Grasshopper.GUI.Canvas.GH_Canvas canvas)
{
// その1部分
foreach (var source in first.Sources)
{
var slider = source as Grasshopper.Kernel.Special.GH_NumberSlider;
if (slider == null)
continue;
// その3部分
}
}
PrePaintWires の部分(その 3)
ここからが、実際にワイヤーに色を付けていく部分です。
ワイヤーの描画は名前空間 System.Drawing のGraphics.DrawPathのメソッドを使ってやっているので、描画のために必要なパス(path)とペンの設定(edge)を作成していきます。
まずは path からです。最初に描画するためにワイヤーの始点と終点の座標をGH_Param.AttributesのInputGripとOutputGripを使用して取得し、それぞれ input と output としています。次にGH_Painter.ConnectionPathのメソッドを使って、描画のためのラインのパスを作成しています。
次に edge です。ここでは一番最初に図示したように点々になるようなペンの設定を作成します。System.Drawing.Penの部分でペンの色と太さ、edge のDashCapのプロパティを使用して丸くして、DashPatternのプロパティでダッシュと空白の長さを指定しています。
これで必要なものが設定できたので、canvas.Graphics.DrawPath(edge, path)の部分で赤丸の点線を描画しています。
そして最後に Dispose して終わりです。(Dispose なんもわからん)
private void PrePaintWires(Grasshopper.GUI.Canvas.GH_Canvas canvas)
{
// その1部分
foreach (var source in first.Sources)
{
// その2部分
var input = first.Attributes.InputGrip;
var output = slider.Attributes.OutputGrip;
var path = Grasshopper.GUI.Canvas.GH_Painter.ConnectionPath(
input,
output,
Grasshopper.GUI.Canvas.GH_WireDirection.left,
Grasshopper.GUI.Canvas.GH_WireDirection.right);
var edge = new System.Drawing.Pen(System.Drawing.Color.DeepPink, 8);
edge.DashCap = System.Drawing.Drawing2D.DashCap.Round;
edge.DashPattern = new float[] { 0.1f, 2f };
canvas.Graphics.DrawPath(edge, path);
edge.Dispose();
path.Dispose();
}
}
最終的なコードの全体
で結局全体でどうなっているのよ?というのは以下参照
private void RunScript(double x, object y, ref object A)
{
EnsurePaintHandler();
A = x;
}
// <Custom additional code>
private bool _paintHandlerAssigned = false;
private void EnsurePaintHandler()
{
if (_paintHandlerAssigned)
return;
Grasshopper.Instances.ActiveCanvas.CanvasPrePaintWires += PrePaintWires;
_paintHandlerAssigned = true;
}
private void PrePaintWires(Grasshopper.GUI.Canvas.GH_Canvas canvas)
{
// We should only draw wires if the document loaded in the canvas is the document we're in.
if (!ReferenceEquals(GrasshopperDocument, canvas.Document))
return;
// Find all sliders that plug into the first component input.
var first = Component.Params.Input[0];
if (first.SourceCount == 0)
return;
foreach (var source in first.Sources)
{
var slider = source as Grasshopper.Kernel.Special.GH_NumberSlider;
if (slider == null)
continue;
var input = first.Attributes.InputGrip;
var output = slider.Attributes.OutputGrip;
var path = Grasshopper.GUI.Canvas.GH_Painter.ConnectionPath
(
input,
output,
Grasshopper.GUI.Canvas.GH_WireDirection.left,
Grasshopper.GUI.Canvas.GH_WireDirection.right
);
var edge = new System.Drawing.Pen(System.Drawing.Color.DeepPink, 8);
edge.DashCap = System.Drawing.Drawing2D.DashCap.Round;
edge.DashPattern = new float[] { 0.1f, 2f };
canvas.Graphics.DrawPath(edge, path);
edge.Dispose();
path.Dispose();
}
}
// </Custom additional code>