【Grasshopper基础3】在SolveInstance中获取数据和传出数据

RegisterInputParamsRegisterOutputParames中添加数据的输入端/输出端之后的下一步就是我们如何在电池的SolveInstance中拿到我们的数据并进一步处理了。

我们先来看看SolveInstance方法重写(override)时的方法定义

protected override void SolveInstance(IGH_DataAccess DA) { }

方法的传入参数为IGH_DataAccess接口类,返回类型void。当看到返回类型是void时我们就想到了,电池的计算结果肯定不是直接通过这个方法本身返回的,而是在方法内通过某种方式将数据传递出去。那么,这个传入的IGH_DataAccess接口类就应该是实现电池内部逻辑与数据的交互的核心了。


认识IGH_DataAccess

正如它的名字一般,IGH_DataAccess是一个用来从GH_Structure中获取数据的一个接口类。

回忆一下我们在C#中创建自定义类的时候如果要创建一个属性,我们常常会定义一个非公开变量用来存储这个属性值,并实现一个setter方法和getter方法来提供对这个私有变量的值的设置和获取,如下例所示

class MyCustomizedClass
{
    private string _name;
    public string Name => _name;
    public void SetName(string name) => _name = name;
}

相较于直接把属于类实例的变量 _name 直接设置成为公开,上面的处理方式可以在设置变量值的时候对该变量的值进行验证,比如上述方法中就可以直接对字符串的长度进行一个简单的验证:

public void SetName(string name) 
{
    if (name.Length > 0) _name = name;
    else throw new ArgumentException("名字不能为空");
}

在Grasshopper中也是类似的,每个继承自GH_Component的电池都将内部的数据都是交由注册在电池上的参数实例 GH_Params 来管理的。参数实例把数据封包起来(底层对数据封装的实例为GH_Structure<T>),并将自己挂载在电池数据管理类GH_ComponentParamServer中,由电池内部的一个迭代器继承IGH_DataAccess接口类提供数据的setter方法和getter方法,并提供一些包含数据验证、迭代器、隐式类型转换等等功能。下面是上面各个类的关系图

WeChat Screenshot_20210113152629

于是,观察这个接口类会发现如下的一些方法

  • GetData
  • GetDataList
  • GetDataTree
  • SetData
  • SetDataList
  • SetDataTree

这些函数分别对应的就是以不同的方式获取电池输入/输出参数中的数据。值得注意点是,使用的获取数据以及给参数赋值的方法必须与之前参数的Access属性一致,否则会出错或出现异常现象,即:

  • 参数注册时Access属性设置为 item 时,需要使用 GetDataSetData
  • 参数注册时Access属性设置为 list 时,需要使用 GetDataListSetDataList
  • 参数注册时Access属性设置为 tree 时,需要使用 GetDataTreeSetDataTree

下面就是一个简单的例子,在SolveInstance中获取数据时,分别对具备不同的Access属性的参数进行数据获取。

protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{
    // “布尔”参数的Access属性设置为 item
    pManager.AddBooleanParameter("布尔", "BOOL", "一个布尔值", GH_ParamAccess.item);
    // “直线”参数的Access属性设置为 list
    pManager.AddLineParameter("直线", "LINE", "一个直线", GH_ParamAccess.list);
    // “浮点数”参数的Access属性设置为 tree
    pManager.AddNumberParameter("浮点数", "NUM", "一个浮点数值", GH_ParamAccess.tree);
}
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
{
    // “整数”参数的Access属性设置为 list
    pManager.AddIntegerParameter("整数", "INT", "一个整数值", GH_ParamAccess.list);
}
protected override void SolveInstance(IGH_DataAccess DA)
{
    // 对变量进行初始化
    var bValue = false;
    var lineList = new List<Line>();
    var numberTree = new GH_Structure<GH_Number>();
    // 使用IGH_DataAccess获取数据,并保存至相应变量
    DA.GetData(0, ref bValue);
    DA.GetDataList(1, lineList);
    DA.GetDataTree(2, out numberTree);
    
    // .... .... 
    // 处理数据、运算
    // .... ....
    var resultIntegerList = new List<int>();
    // .... .... 
    // 处理数据、运算
    // .... ....

    // 使用IGH_DataAccess将结果数据赋值到输出参数中
    DA.SetDataList(0, resultIntegerList);
}


隐式类型转换

我们都知道任何一个编程语言都会或多或少地存在隐式数据转换,比如在学C/C++中大家都会遇到的经典问题 —— 5 / 3 的结果与 5 / 3.0 的结果不一样,就是因为后者在进行计算时,整型5会被隐式地转换成了浮点数类型。

隐式类型转换可以认为是程序(编译器也是一种程序)在最大程度上揣测用户的用意。这种隐式地转换类型发生地悄然无声,如果我们对其不够了解的话,那么对于程序运行的过程也会存在一定程度上的危险,导致一些奇怪的结果出现。

在GH中,隐式转换无处不在,可以说GH的易用性不光是靠各种快速的集合运算,更是因为这种“自然”的数据隐式转换。字符串转整数和浮点型、字符串转GH_Path、曲线转浮点型、直线转字符串,等等。

convertion

这些隐式类型转换虽然不是保证数据的双向完整传递(比如曲线转浮点型就默认只有长度,没法直接逆操作),但从用户体验的角度来说的确是省去了使用大量额外的电池来完成简单类型转换的操作。

GH中的隐式数据类型转换发生于两个层面:

  1. 输入/输出参数层面
  2. 参数拆箱层面

WeChat Screenshot_20210113152630

上图中的靠左侧虚线处会发生输入参数层面的隐式类型转换,靠中间虚线处会发生参数拆箱层面的隐式类型转换。

输入/输出参数层面的隐式类型转换

首先需要明确的一点是,仅有 GH自有类型 可以被作为数据传递与各个电池之间。于是,所有 非GH自有类型 的数据在GH画布中传递时都会被转换为GH自有的类型。所谓GH自有类型是实现了IGH_Goo接口的类。“数据传递”可以认为是从GH画布上电池的一端通过线输送到另一端的过程.

GH封装了许多常用的类,做成了自己的数据类型体系,均是以 “GH_” 开头,很多常见的Rhino几何类型都包含在内,例如 GH_LineGH_CurveGH_Surface 是封装的Rhino几何类LineCurveSurface。对于代码层面的基础类,比如 stringinteger ,也有对应的 GH_StringGH_Integer 与它们对应。所有其他未涵盖的类都通过GH_ObjectWrapper<T>泛型进行简单装箱。

假设我们现在将一个字符串类型(GH_String)输入到另一个电池的一个注册为GH_Number的参数中。当我们在使用 RegisterInputParameter 或者 RegisterOutputParameter 时,已经声明了这个参数的类型,于是所对应的GH_Param就固定了是字符串类型。当这个声明类型为GH_Number的参数接收到来自GH_String类型的数据时,会发现类型不匹配,于是乎,它就会调用GH_Convert的函数帮忙转换一下,转换成功则电池继续运行,如果无法转换,则直接报错,如下图所示

WeChat Screenshot_20210113172544

参数拆箱层面的隐式类型转换

在GH_Param成功获取到数据后,数据即可确认成为相对应的GH自有类型(以“GH_”开头的类型)。

当我们真正开始在SolveInstance中使用GetData系列函数获取该参数内部的值时,我们需要将GH自有类型进行拆箱,才能正常进行操作。例如,如果我们直接对两个GH_Integer实例进行相加的操作会直接报错(可以在C# Script电池中尝试下面一段代码)

GH_Integer a = new GH_Integer(1);
GH_Integer b = new GH_Integer(2);
var c = a + b; // 报错

函数内部值可以直接通过IGH_Goo接口中的属性Value获取,即

GH_Integer packed = new GH_Integer(1);
int unpacked = packed.Value;

但我们如果在代码中每次都要手动拆箱这的确十分麻烦,所以GH提供了参数拆箱层面上的隐式类型转换,即我们使用GetData系列函数获取参数的值时,可以自动实现对应类型的拆箱。也就是说,如果IGH_Goo.Value的返回值类型与GetData需要赋予的变量的值类型相同,可实现自动拆箱。

protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{
    pManager.AddLineParameter("直线", "", "", GH_ParamAccess.item);
}
// ... ... 
protected override void SolveInstance(IGH_DataAccess DA)
{
    Line line = default(Line);
    GH_Line ghLine = new GH_Line
    DA.Getdata(0, ref GH_Line); // 不拆箱直接获取
    DA.GetData(0, ref line);    // 隐式类型转换拆箱
}

值得注意的是,这个“拆箱”的类型转换必须类型完全相同,将浮点型double转换为整型int也是不允许的(GH_Number->int直接报错)。下列代码就会直接报错

protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{
    pManager.AddIntegerParameter("整型", "", "", GH_ParamAccess.item);
}
// ... ...
protected override void SolveInstance(IGH_DataAccess DA)
{
    double d = 0.0;
    DA.GetData(0, ref d);   // 报错,无法将装箱的整型拆箱成浮点型
}

WeChat Screenshot_20210113184228


以上就是本次关于在SolveInstance中有关数据I/O的一些小细节的内容了,欢迎关注下期

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值