2022年3月至今,这段日子真是一段令人印象深刻的时光。
总之,居家这么久了,忙了一阵终于可以来写点东西了。首先要对上一期【基础13】内的两个小错误进行一个勘误:
在
class Pudge
中对IGH_Goo
接口的实现方法中,CastFrom(object source)
和CastTo<T>(out T target)
函数体内对于目标类型的判断有错误。不应该直接判断int
、double
或者Circle
类型,而是应该判断其GH_xxxx
类型,故依次应为GH_Integer
、GH_Number
和GH_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_Plural
和Prompt_Singular
,我们一会儿再讲它是干什么的。
直接对它进行一个编译,启动Grasshopper,我们发现,去Param
分类的Geometry
子分类,还真能找到我们刚刚的这个Param_Pudge
!
好家伙,拖出来看看,是熟悉的感觉哇。
点击它看看右键菜单:
这两个选项居然默认就有 “Set one Pudge” 和 “Set Multiple Pudges”,还贴心的加了复数形式s
。细节把控,极度舒适!
好了,上面提到的两个函数Prompt_Plural
和Prompt_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
实现了CastFrom
和CastTo<T>
,因此可以对实现的几种数据类型进行隐式数据转换啦(Pudge
至 GH_Integer
, GH_Integer
至 Pudge
, GH_Circle
至 Pudge
)。
我们可以看到隐式数据转换会存在数据转换后,数据不完整的情况(由Pudge
类转换为int
之后,再往回转换则会出现其他数据丢失的问题),这其实是隐式转换的一个弊端。在设计自有数据类型的时候,不光是考虑隐式数据转换对用户的便利性,也需要考虑到数据如果出现丢失,避免对用户造成困惑才可以更好地设计出好用的工具。
另外,由于 Parameter Pudge 本身其实也是一个类电池,它也是可以实现自定义右键菜单、自定义数据后处理等高级功能(比如,“曲线”类的出入口会有“Reparameterize”菜单项,用来实现归一化功能,还能出现一个额外的小图标)。这些,大概笔者会在后续的 【Grasshopper进阶】 系列慢慢为大家带来吧。
祝大家开心地传递自定义类型,Enjoy!🦀