相信大家一定在Grasshopper中见过输入或者输出参数可以自由变化的电池,例如,笔者常用的电池“Entwine”就可以在电池的输入端添加一个参数或者减少参数,用来支持更多的电池接入,如下图所示。

grasshopper调出背后Python命令 grasshopper输入命令_c#

如果我们想要在电池中实现这样的功能,应该要怎么做呢?

本文包含C#语言中的“接口(interface)”的概念,如果你对接口的概念还不是特别清楚的话

文章最后有电池的cs源码。


声明实现 IGH_VariableParameterComponent 接口

要让电池支持前文中所述、gif中所展示的功能,那么就需要让我们写的电池类支持IGH_VariableParameterComponent接口(interface)。继承这个接口相当于在告诉Grasshopper:“这个电池的输入/输出端参数数量可以改变”:

public class VariableParameterComponent : GH_Component, IGH_VariableParameterComponent
{
    // ..... 
}

仅需在我们自己的电池类继承的GH_Component后加上接口名字,再以逗号分割父类和接口,即可声明类对接口的实现。


实现接口所需的方法

在声明实现接口完毕之后,我们发现这个接口名字下面出现了红色波浪线,报了一大堆笔者比较懒所以不想复制过来的错误。

这是因为我们仅仅 “声明” 我们自己的电池类实现了这个接口,但还没有写任何一行代码去真正地按照接口的规范去 “实现”。

Visual Studio可以帮助我们很快的生成这个接口所需要的函数,仅需右键点击有红色波浪线的接口名字,选择“Quick Actions and Refactoring…”,进而在接下来的菜单中选择“implement interface(实现接口)”即可。

grasshopper调出背后Python命令 grasshopper输入命令_ide_02

这样,代码检查器就不会再报错了,因为它发现我们的类当中包含了这个接口中所有的方法。不过,这个代码目前直接运行的话是会报错的,因为Visual Studio帮我们生成都所有接口类的方法声明中,内部都有一行发出程序异常信号的语句

throw new NotImplementedException();

这部分代码是Visual Studio贴心为我们准备的,防止我们生成代码之后忘记在里面实现我们真正需要的逻辑,接下来我们要做的就是把每个刚刚生成的throw的部分替换掉,真正地实现IGH_VariableParameterComponent

我们一共有5个方法需要实现:

  • CanInsertParameter
  • CanRemoveParameter
  • CreateParameter
  • DestroyParameter
  • VariableParameterMaintenance

CanInsertParameter()

这个方法的返回值是个布尔值,是用来返回一个“能否在输入/输出端的指定位置添加参数”。能添加则返回true,不能则返回falseside 参数用来表明是输入端还是输出端,index 参数用来表达具体位置。

比如下列代码就表明可以在输入端序号小于3的位置添加参数,而在输出端不可以添加参数:(index 参数是数组序号,从0开始。还记得前文提到过输入/输出参数在底层是是通过一个列表List保存的吗?)

public bool CanInsertParameter(GH_ParameterSide side, int index)
{
    if (side == GH_ParameterSide.Input)
    {
        if (index < 3)
            return true;
        else
            return false;
    }
    else
    {
        return false;
    }
}

值得提到的是 GH_ParameterSide 枚举类只有两个值,Input 或者 Output

在输入端和输入端两端都可以任意添加参数的话,则这个函数永远返回true即可。

CanRemoveParameter()

与前面 CanInsertParameter() 类似,这个方法是用来返回一个“能否在输入/输出端指定位置移除参数”。下列代码就是表明电池的输入端除了序号为0的位置之外都可以移除参数,输出端不可以移除参数:

public bool CanRemoveParameter(GH_ParameterSide side, int index)
{
    if (side == GH_ParameterSide.Input && index > 0)
        return true;
    else
        return false;
}

CreateParameter()

这个函数是实现参数可变的时候最关键的一个函数,它决定着在电池指定侧的指定位置添加参数时,添加的是一个什么类型的参数。

笔者在【基础2】和【基础3】中曾花了较大的篇幅用来讲解GH的自有的数据格式(GH_NumberGH_Curve等),我们也了解到了电池的输入端和输出端的参数类型其实都是这一类GH的自有数据格式类型,所以我们要实现函数输入/输出端参数数量的变化,添加的函数类型就需要从它们当中进行选择。

不过,我们注意到这个函数的返回值是一个IGH_Param接口类型,倘若我们想返回一个GH_Number,似乎不能直接return new GH_Number()。此时就需要使用Param_Number(其属于Grasshopper.Kernel.Parameters命名空间):

public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
    return new Param_Number();
}

当然,我们也可以在这个函数里加入有关 side 的条件判断,来实现诸如“输入端添加的是GH_Number,输出端则添加GH_Integer”的逻辑,甚至加入对 index 的条件判断实现在不同的位置添加时会添加不同类型的参数,这些发挥就留给读者你自己啦。

这里的Param_系列类型可以简单地认为是GH_系列GH自有类型再封装了一层,实现了更复杂的功能。在【Grasshopper高级】系列中还会介绍如何自定义Param_系列类。

这样我们就可以直接运行电池项目来看看效果啦:

grasshopper调出背后Python命令 grasshopper输入命令_ide_03

可以发现每个新添加的参数名字都默认为"Num"。我们还可以在上述代码中加入简单的逻辑使得我们新增加的参数可以在电池的输入端显示成自定义的名字:

public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
    var p = new Param_Number();
    p.NickName = string.Format("radius{0}", index);
    return p;
}

grasshopper调出背后Python命令 grasshopper输入命令_rhino_04

同样的,在我们添加参数时,也可以用到【基础2】中讲到的各种属性,比如Optional属性决定参数是否可选,Access属性确定电池逻辑运行时的数据获取逻辑等等。

DestroyParameter()

这个函数是用于实现“在输入/输出参数被移除时,可能需要的对电池内部数据进行重新整理”的逻辑。

这个函数的返回值是用来表达“参数移除是否成功”,我们在这里直接返回true即可。

public bool DestroyParameter(GH_ParameterSide side, int index)
{
    return true;
}

VariableParameterMaintenance()

这个函数是用来自定义在GH电池完成输入/输出端的增删操作之后进行数据整理时的额外操作,GH会在完成自己的参数调整逻辑之后调用这个函数,一般而言我们可以直接留空,即不进行任何操作。

public void VariableParameterMaintenance()
{

}

完成了所有的5个函数之后,我们的电池就可以实现输入/输出端的参数增减了。


总结

总结一下本次的内容,关键的点有:

  • CanInsertParameterCanRemoveParameter决定了在某个位置是否可以增/删参数
  • CreateParameter的返回值是一个GH电池输入/输出端的自有类型GH_系列类封装后的Param_类型,它决定了这个参数可接受的数据类型
  • DestroyParameterVariableParameterMaintenance一般无需做太多的逻辑处理,因为很多对于参数个数的处理都可以放在电池的SolveInstance里实现,只有与电池的外观相关的一些逻辑才需要放在它们中实现,目前并不需要了解。

下面是这次的电池的所有代码,仅供参考(SolveInstance里并没有实现任何逻辑):

using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using System;

namespace DigitalCrab.Grasshopper
{
public class VariableParameterComponent : GH_Component, IGH_VariableParameterComponent
{
public VariableParameterComponent()
    : base("VariableParameters", "VP",
        "Description",
        "Params", "DigitalCrab")
{}
protected override void RegisterInputParams(GH_InputParamManager pManager)
{}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{}
protected override void SolveInstance(IGH_DataAccess DA)
{}
public bool CanInsertParameter(GH_ParameterSide side, int index)
{
    if (side == GH_ParameterSide.Input)
    {
        if (index < 3)
            return true;
        else
            return false;
    }
    else
    {
        return false;
    }
}
public bool CanRemoveParameter(GH_ParameterSide side, int index)
{
    if (side == GH_ParameterSide.Input && index > 0)
        return true;
    else
        return false;
}
public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
    var p = new Param_Number();
    p.NickName = string.Format("radius{0}", index);
    return p;
}
public bool DestroyParameter(GH_ParameterSide side, int index)
{
    return true;
}
public void VariableParameterMaintenance()
{}
protected override System.Drawing.Bitmap Icon => null;
public override Guid ComponentGuid => throw new NotImplementedException("请自行申请GUID并替换");
}
}

欢迎大家留言互相交流。