【Grasshopper基础14】创建可在画布上自由传递的自定义类型数据(下)—— 电池与自定义IGH_Goo的交互

2022年3月至今,这段日子真是一段令人印象深刻的时光。

总之,居家这么久了,忙了一阵终于可以来写点东西了。首先要对上一期【基础13】内的两个小错误进行一个勘误:

class Pudge 中对 IGH_Goo 接口的实现方法中,CastFrom(object source)CastTo<T>(out T target) 函数体内对于目标类型的判断有错误。不应该直接判断intdouble或者Circle类型,而是应该判断其GH_xxxx类型,故依次应为GH_IntegerGH_NumberGH_Circle
仅当 需要转换的数据类型不存在Grasshopper自由类型对应时,才可以直接使用该类型作为判断依据。对应代码已经在【基础13】中更正,同时,也添加了一些对于序列化期间的异常处理的逻辑,详见原文代码。

接下来,就是本期的正文了:在【基础13】中我们已经建立了一个自己的数据类,实现了IGH_Goo接口,向Grasshopper表明了我们对于该数据类型支持Grasshopper画布上流转表明了意愿,那么,怎么才能真正地实现Grasshopper的精髓 —— 自动数据隐式转换?

制作属于自己数据类型的电池出/入口

相信大家在【基础2】中就已经知道,如何向我们的自定义Component中添加出入口了,那么我们现在回过头来看一段熟悉的不能再熟悉的代码:

protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
{
    pManager.AddIntegerParameter("整数", "INT", "一个整数值", GH_ParamAccess.item);
}

在这个往电池添加出口的函数里,调用 AddIntegerParameter 函数就是向出口添加了一个 Parameter,类型为 Integer。所有的出/入口都需要是Parameter类型,我们要创建一个属于自己数据类型的Parameter才可以把它作为出口添加到电池上去。

相信熟悉Grasshopper开发的读者已经想到了,是的,我们创建自定义Parameter类,那在Grasshopper对应也是有一个基类需要继承,它就是GH_PersistentParam<T>.

不多谈,直接上代码

using Grasshopper.Kernel;
// ... ...

public class Param_Pudge : GH_PersistentParam<Pudge>
{
    public Param_Pudge() : 
        base("Pudge", "Pud", "Contains a collection of pudges", 
             "Params", "Geometry") { }

    public override Guid ComponentGuid
        => Guid.Parse("{2737B093-AE35-40BE-B0BE-6E2D401C8E78}");

    protected override GH_GetterResult Prompt_Plural(ref List<Pudge> values)
    {
        return GH_GetterResult.cancel;
    }

    protected override GH_GetterResult Prompt_Singular(ref Pudge value)
    {
        return GH_GetterResult.cancel;
    }
}

看起来挺不错的,其实大部分工作已经是由基类GH_PersistentParam<T>完成了,我们需要做的只是把这个基类里的具体泛型改成我们自己需要的自定义数据类型即可。

乍看之下,还挺像个电池的,有熟悉的base(...)方法,熟悉的ComponentGuid属性,但是有两个函数Prompt_PluralPrompt_Singular,我们一会儿再讲它是干什么的。

直接对它进行一个编译,启动Grasshopper,我们发现,去Param分类的Geometry子分类,还真能找到我们刚刚的这个Param_Pudge

在这里插入图片描述

好家伙,拖出来看看,是熟悉的感觉哇。

在这里插入图片描述

点击它看看右键菜单:

在这里插入图片描述

这两个选项居然默认就有 “Set one Pudge” 和 “Set Multiple Pudges”,还贴心的加了复数形式s。细节把控,极度舒适!

好了,上面提到的两个函数Prompt_PluralPrompt_Singular就是这俩菜单项的回调函数,里面决定了这俩菜单项被用户点击之后,怎么样去生成一个Pudge数据,和多个Pudge数据。具体怎么写嘛… 今天就不多提及了,直接一个return GH_GetterResult.cancel。期待后面的章节吧,嘿嘿。

制作“创作Pudge”、“分解Pudge”的电池

有了这个Pudge类的Parameter,我们在写自己的电池的时候,就可以将它加到我们的出入口里啦。作为演示,我们需要一对电池来创作能够在画布上游动的Pudge数据类(创作Pudge和分解Pudge),显然,做电池这种【基础1】里就讲了的东西我就不说没用的了,直接上代码:

public class CreatePudge : GH_Component
{
    public CreatePudge() : base("CreatePudge", "CPud", "Make a Pudge", "Params", "DigitalCrab")
    {

    }
    public override Guid ComponentGuid => Guid.Parse("{7E5CEC1E-DAB1-4605-B310-CA23A965AD43}");

    protected override void RegisterInputParams(GH_InputParamManager pManager)
    {
        pManager.AddIntegerParameter("Int", "i", "integer", GH_ParamAccess.item);
        pManager.AddNumberParameter("DoubleList", "d", "double list", GH_ParamAccess.list);
        pManager.AddTextParameter("CurveDictKeys", "k", "curve dictionaray, keys", GH_ParamAccess.list);
        pManager.AddCurveParameter("CurveDictVals", "cr", "curve dictionaray, values", GH_ParamAccess.list);
        pManager.AddCircleParameter("Circle", "ci", "circle", GH_ParamAccess.item);
        pManager.AddGenericParameter("TextEntity", "t", "text entity", GH_ParamAccess.item);
        for (int i = 0; i < 6; i++)
        {
            pManager[i].Optional = true;
        }
    }

    protected override void RegisterOutputParams(GH_OutputParamManager pManager)
    {
        pManager.AddParameter(new Param_Pudge(), "Pudge", "Pud", "The created Pudge", GH_ParamAccess.item);
    }

    protected override void SolveInstance(IGH_DataAccess DA)
    {
        int i = 0;
        List<double> dl = new List<double>();
        List<string> sl = new List<string>();
        List<Curve> cl = new List<Curve>();
        Circle cir = default;
        TextEntity te = default;

        Pudge p = new Pudge();

        if (DA.GetData(0, ref i))
            p.PudgeInt = i;
        if (DA.GetDataList(1, dl))
            p.PudgeDoubleList = dl;

        if (DA.GetDataList(2, sl) && DA.GetDataList(3, cl))
        {
            var guids = new List<Guid>();
            foreach (var item in sl)
            {
                guids.Add(Guid.Parse(item));
            }

            if (guids.Count == cl.Count)
                p.PudgeCurveDictionary = guids.Zip(cl, (s, c) => new { k = s, v = c })
                                              .ToDictionary(k => k.k, v => v.v);
            else
                throw new Exception("Number of Guids does not match number of Curves.");
        }

        if (DA.GetData(4, ref cir))
            p.PudgeCircle = cir;

        if (DA.GetData(5, ref te))
            p.PudgeTextEntity = te;

        DA.SetData(0, p);
    }
}

public class DeconstructPudge : GH_Component
{
    public DeconstructPudge() : base("DeconstructPudge", "DPud", "Deconstruct a Pudge", "Params", "DigitalCrab")
    {

    }
    public override Guid ComponentGuid => Guid.Parse("{5F2C82C5-7E05-4A03-A7B5-0CA8E6284E2A}");

    protected override void RegisterInputParams(GH_InputParamManager pManager)
    {
        pManager.AddParameter(new Param_Pudge(), "Pudge", "Pud", "The created Pudge", GH_ParamAccess.item);
    }

    protected override void RegisterOutputParams(GH_OutputParamManager pManager)
    {
        pManager.AddIntegerParameter("Int", "i", "integer", GH_ParamAccess.item);
        pManager.AddNumberParameter("DoubleList", "d", "double list", GH_ParamAccess.list);
        pManager.AddTextParameter("CurveDictKeys", "k", "curve dictionaray, keys", GH_ParamAccess.list);
        pManager.AddCurveParameter("CurveDictVals", "cr", "curve dictionaray, values", GH_ParamAccess.list);
        pManager.AddCircleParameter("Circle", "ci", "circle", GH_ParamAccess.item);
        pManager.AddGenericParameter("TextEntity", "t", "text entity", GH_ParamAccess.item);
    }

    protected override void SolveInstance(IGH_DataAccess DA)
    {
        Pudge p = null;
        if (!DA.GetData(0, ref p)) return;

        DA.SetData(0, p.PudgeInt);
        DA.SetDataList(1, p.PudgeDoubleList ?? Enumerable.Empty<double>());
        DA.SetDataList(2, p.PudgeCurveDictionary?.Keys.Select(k => k.ToString()) ?? Enumerable.Empty<string>());
        DA.SetDataList(3, p.PudgeCurveDictionary?.Values ?? Enumerable.Empty<Curve>());
        DA.SetData(4, p.PudgeCircle);
        DA.SetData(5, p.PudgeTextEntity);
    }
}

注释我也省了,毕竟电池都写这么多了,读者肯定一看就懂啦,最终看最后结果就好了:)

Ok, everything’s ready, let’s ROCK!

在这里插入图片描述

最终结果已经上图了,点开大图食用。Pudge实现了CastFromCastTo<T>,因此可以对实现的几种数据类型进行隐式数据转换啦(PudgeGH_Integer, GH_IntegerPudge, GH_CirclePudge)。

我们可以看到隐式数据转换会存在数据转换后,数据不完整的情况(由Pudge类转换为int之后,再往回转换则会出现其他数据丢失的问题),这其实是隐式转换的一个弊端。在设计自有数据类型的时候,不光是考虑隐式数据转换对用户的便利性,也需要考虑到数据如果出现丢失,避免对用户造成困惑才可以更好地设计出好用的工具。

另外,由于 Parameter Pudge 本身其实也是一个类电池,它也是可以实现自定义右键菜单、自定义数据后处理等高级功能(比如,“曲线”类的出入口会有“Reparameterize”菜单项,用来实现归一化功能,还能出现一个额外的小图标)。这些,大概笔者会在后续的 【Grasshopper进阶】 系列慢慢为大家带来吧。

祝大家开心地传递自定义类型,Enjoy!🦀

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值