SDK Support

Hey all, I am dying to sink my teeth into the GH2 SDK and start tinkering around with the back end of GH2 to start getting my head around the changes between GH1 and GH2.

I just can’t seem to find anything to get started with. Any pointers (see what I did there) would be really helpful.

There is no official SDK yet since Grasshopper 2 is still in alpha and there are too many ongoing changes to provide anything stable for third party developers.

At this stage it’s wasted effort as you’ll be chasing changes between very frequent WIP releases. Everything you need to go digging and tinkering is in your local package install dir (e.g. AppData\Roaming\McNeel\Rhinoceros\packages\8.0\Grasshopper2).

1 Like

Thanks Guido,

I’ll have a look around there, just wanna hang out in the back end and run some simple components to just fill in some gaps in GH2‘s library while the core builds itself up along with staying familiar with the library as it develops.

Matthew

1 Like

I’ve had some success using some .dll decompliers, however, the resulting RHP file I compile mentions I have not exposed ‘Grasshopper.Framework.Plugin type’. The closest framework attribute line is,

[assembly:global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.8", FrameworkDisplayName = “.NET Framework 4.8”)]

but this seems fundamental to the actual thing working. Any further help on this last stage would be amazingly helpful.

How/where are you compiling your .rhp file?

You will need an info class:

using Grasshopper.Framework;
using Grasshopper.UI.Icon;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PancakeForGh2.GhInterop
{
    public class PluginInfo : Plugin
    {
        public static readonly Guid PluginId = new("{4B8EE19F-878C-4B9A-BFF3-DD5CC938B9B3}");
        public PluginInfo()
            : base(PluginId,
                  new("Pancake", "Provides tools for teamworking"),
                  typeof(PluginInfo).Assembly.GetName().Version)
        {
            var bitmap = Eto.Drawing.Bitmap.FromResource("PancakeForGh2.Resources.Pancake48.png");
            _icon = AbstractIcon.FromBitmap(bitmap);
        }
        private IIcon _icon;
        public override string Author => "Keyu Gan";
        public override IIcon Icon => _icon;
        public override string LicenseDescription => "Free for (non-)commercial use";
        public override string LicenseAgreement => "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.";
    }
}

and an example component:

using Grasshopper.Components;
using Grasshopper.UI;
using GrasshopperIO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace PancakeForGh2.Internal
{
    [IoId("220FFD77-4253-493F-A0C2-517FB9AFBA98")]
    public sealed class SleepComponent : Component
    {
        public SleepComponent()
            : base(new Nomen("Sleep", "Sleep for a designated time.", "Pancake", "Test"))
        {
            Threading = ThreadingState.UiSingleThreaded;
        }
        protected override void AddInputs(InputAdder inputs)
        {
            inputs.AddInteger("Time (ms)", "T (ms)", "Milliseconds to sleep");
            inputs.AddYesNo("OK", "OK", "Yes to Run")
                .Set(false);
        }

        protected override void AddOutputs(OutputAdder outputs)
        {
            outputs.AddBoolean("Completed", "OK", "The action is completed");
        }
        protected override void Process(IDataAccess access)
        {
            access.GetItem<int>(0, out var ms);
            access.GetItem<bool>(1, out var ok);

            if (!ok)
            {
                access.SetItem(0, false);
                return;
            }

            if (ms <= 0)
            {
                access.SetItem(0, false);
                access.AddError("Invalid time.", "Time cannot be equal to or smaller than zero.");
                return;
            }

            Thread.Sleep(ms);

            access.SetItem(0, true);
        }
    }
}

1 Like

Cheers guys, thanks for the answers! I ended up getting there and finding that issue in building up without an SDK. This has been a fun problem.

My new question is that on Windows, the panel that Rutten suggests ‘GHIcon’ doesnt exist, instead there are a couple, the most promising is (off the top of my head), G2Coder, but doesnt give you the sweet GUI. any tips on how to get this?

I’ve also noticed that setTwig() doesnt render, worth looking at as it seems it should.

You should have that. :thinking:

G2IconCoder is a command, not a panel. You can show the panel from the same docker as the Rhino object properties or Layers panel:

Be sure to run the Setup Command (button at the top of the panel) at least once in a 3dm file, before that there’s no icon data.

Another useful command is G2IconSymbol as it lets you place predefined icon shapes. Saves you from drawing a lot of boxes and spheres yourself.

I highly recommend using colours from the Icon Context mode in the colour picker. As that means the colours will be background aware and eventually theme-able.

1 Like

Here’s the code for one of the simplest standard components:

Note that if your assembly contains an embedded resource with extension *.ghicon whose name is the same as the class name, the icon will be associated automatically. If that’s not the case you’ll have to override the IconInternal property and return the correct icon there.

Also note that the Process() method is called simultaneously from multiple threads, possibly even from multiple solutions. So do not rely on class level variables, let alone static fields without using proper locks.

4 Likes

Is it still safe to assume components will be emitted per document? I’m asking because I don’t know why Process() would be called from multiple solutions. For example one new solution before the previous solution ends?

And here’s a slightly more complicated component which has inputs with presets and adds graphics to the tooltip:

using System;
using Eto.Drawing;
using GrasshopperIO;
using Grasshopper.Components;
using Grasshopper.Doc.Attributes;
using Grasshopper.Parameters.Standard;
using Grasshopper.UI;
using Grasshopper.UI.Graphing;

namespace MathsComponents.Analysis
{
  [IoId("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]
  public sealed class AiryFunction : Component, ICustomTooltipPainter
  {
    #region constructor
    public AiryFunction()
      : base(new Nomen("Airy(z)", "Compute the real or complex aspect of an Airy function.", "Maths", "Special", 10, Rank.Obscure))
    { }
    public AiryFunction(IReader reader) : base(reader) { }

    protected override void AddInputs(InputAdder inputs)
    {
      inputs.AddNumeric("Argument", "x", "Real or complex argument.").ExoticFilter = NumericFilter.Complex;
      inputs.AddInteger("Function", "f", "Airy function.").SetEnum(Grasshopper.Maths.AiryKind.A);
    }
    protected override void AddOutputs(OutputAdder outputs)
    {
      outputs.AddNumeric("Result", "y", "Result of Airy(x).").ExoticFilter = NumericFilter.Complex;
    }
    #endregion

    #region implementation
    protected override void Process(IDataAccess access)
    {
      access.GetItem(0, out object x);
      access.GetItem(1, out int f);

      access.RectifyEnum(ref f, Grasshopper.Maths.AiryKind.A, "f", "Airy function");

      var typeA = NumberLikeData.CastOrConvert(x, out var int32, out var num64, out _, out var cpx128, out _);
      switch (typeA)
      {
        case NumberLikeType.Integer:
        case NumberLikeType.Decimal:
          access.SetItem(0, Grasshopper.Maths.Airy((Grasshopper.Maths.AiryKind)f, num64));
          return;

        case NumberLikeType.Complex:
          access.SetItem(0, Grasshopper.Maths.Airy((Grasshopper.Maths.AiryKind)f, cpx128));
          return;

        case NumberLikeType.None:
          return;

        default:
          throw new ArgumentOutOfRangeException();
      }
    }

    Action<Context, Rectangle> ICustomTooltipPainter.TooltipPainter
    {
      get { return FxBundle.CreateStandardFunction("airy").Draw; }
    }
    #endregion
  }
}
4 Likes

A single component can only ever be part of a single document. Although sometimes a component is not part of any document. In these cases it should be considered ‘inactive’.

It may be that solution A has been cancelled but hasn’t realised it yet and it’s still doing a little bit of work. In the meantime solution B has started.

The access object lets you test for yourself if the solution you’re currently in has been cancelled, but unless your component takes a long time to complete I don’t recommend checking it yourself.

2 Likes

:crazy_face: Great! A modern version of GH_Document.IsEscapeKeyDown

Thanks so much David. I’m an idiot! This is so good! no more clicking individual pixels making pixel art for the icons! Such a time saver!

One last question, I’ve noticed that icons arnt associated how they used to be. How do I associate a ghicon file resource to a component?

You either embed the ghicon with the same name of component, which would be automatically discovered during the loading procedure by GH2.

Or you can override Icon/IconInternal and use AbstractIcon.FromXXXX

1 Like

Thanks Keyu, Worked a treat!

Hi,
I was trying to get the examples above to run in Rhino 8 WIP and was wondering if I am doing it correctly:

  • Pointing to the folder with the .dll/.rhp (I created a .net core 6 project) in GrasshopperDeveloperSettings?
  • I am assuming .rhp is the new .gha format, which is just a renamed .dll?
  • Anything else worth noting, that behaves different to a Rhino/Grasshopper 7 plugin?

能提供一个G2的c#开发模板学习下?