Defining plugin-wide variables for Grasshopper plugin

Dear Grasshopper devs,

We have a Grasshopper plugin that provides some extra components and we need some sort of global settings dictionary that is accessible from within our plugins namespace.

However, even with a simple int variable I cannot figure out how to implement this.
The aim is to be able to do

ELiSE.MyVar = 42;

or

int myVar = ELiSE.MyVar;

(where ELiSE is the namespace of our plugin) from anywhere within my plugin code, be it from components or widgets.

Where would I instantiate such a variable in order to make it accessible from everywhere? I’ve tried placing it in our AssemblyPriority method but it does not show up anywhere else…

As always, your help would be greatly appreciated!
Thanks and best regards,
Paul

Create a singleton class with static accessor. Then just normal fields.

namespace ELiSE {
  public sealed class Settings {
    private Settings() {} // private constructor, should only access through Instance. Any necessary initialization here, too.
   
    Settings Instance => { get; }  = new Settings(); // The gate into your settings.

    public int MyVar { get; set; } = Int.MinValue; // initialize to min value of int. Can use any valid value here. Or init in the private constructor.
  }
}

usage:

ELiSE.Instance.MyVar = 12;
Console.WriteLine(ELiSE.Instance.MyVar);
1 Like

Cool @jesterking, the singleton approach seems to be a good one (although your code did not compile for me, something around Settings Instance => ... seems to be wrong.

I found this stackoverflow post on singletons and modified it a bit so it works like the Grasshopper.Instances.Whatever code.

namespace ELiSE
{
    	public class Instances
	{
		// Settings server instance.
		private static SettingsServer _settingsServer;

		// Room for other instaces, e.g. FooServer or BarServer

		// Private constructor.
		private Instances() { }

		// Settings server accessor that allows only one instance.
		public static SettingsServer SettingsServer
		{
			get
			{
				if (_settingsServer == null)
					_settingsServer = new SettingsServer();

				return _settingsServer;
			}
		}
	}

	public class SettingsServer
	{
		// Constructor
		public SettingsServer(){}
	}
}

I’m not sure if I’m bending the concept too much but it works :slight_smile:

Its C# 6.0+

What jesterKing proposes does not allow you to change the values and keep them in another session of gh. If you need this, you can use grasshopper_kernel.xml to store your configuration using Grasshopper.Instances.Settings.GetValue()/SetValue() methods and use (although not necessary) the Settings class to easily access/edit them, for example (not tested):

public sealed class Settings {
private int myVar;
private Settings() {
   //Load settings;
   myVar = Grasshopper.Instances.Settings.GetValue("ELiSE.Settings.MyVar", int.MinValue);
}

Settings Instance => { get; }  = new Settings(); // The gate into your settings.

public int MyVar { 
  get { return myVar; }
  set { 
     if(myVar == value) return;
     Grashopper.Instances.Settings.SetValue("ELiSE.Settings.MyVar", myVar);
     myVar = value;
 }
 public int OtherVar{ 
  get { return Grashopper.Instances.Settings.GetValue("ELiSE.Settings.OtherVar", 0); }
  set { Grashopper.Instances.Settings.SetValue("ELiSE.Settings.OtherVar", value); }
 }
}

I’d actually prefer if you’d use your own settings file. Something like this:

GH_SettingsServer settings = new GH_SettingsServer("ELiSE", true);
settings.SetValue("Value A", "A");
settings.SetValue("Value B", 56.2);
settings.WritePersistentSettings();

It puts an ELiSE.xml file in the same folder as grasshopper_kernel.xml and you can always access the values inside that file by creating a new settings server with the correct name and accessing its values. Just be sure to always call WritePersistentSettings() whenever you make a change to the settings, because the server class doesn’t update if the file changes.

2 Likes

Oh, nice, I didn’t know that I could use the GH_SettingsServer class so easily… Thanks @Dani_Abalde and @DavidRutten!

I was planning to do my own settings class that writes a json to keep settings across sessions and also allows to write document specific settings to gh files using this approach…

Why wouldn’t you use user or app settings: https://docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/using-application-settings-and-user-settings ?

That’s also a nice one @fraguada, I wasn’t aware of that.
However, this will not allow me to attach settings to individual grasshopper files I guess?

Settings are either app-global or per-file. Wanting both seems like a contradiction.

Is there “common” provision to store settings within a grasshopper document?
I have two groups of settings, one that is global and different settings that are document specific.

No, you have to store data within your component during the Write() method. Technically you could cast the writer to a GH_Chunk and navigate towards the top of the hierarchy and append a new chunk there, but it sounds awfully risky. And you’d have to deal with potentially conflicting components, all trying to be the one true global standard.

Here’s a way to store ‘global’ data in the *.gh file, but it is only available during Write() and Read() processes. Still, during runtime you can have your own data class running which associates with specific documents.

The component below will create a new chunk in the gh file and store some data in it:

using System;
using System.Windows.Forms;
using GH_IO.Serialization;
using Grasshopper.Kernel;

namespace IoHackComponents
{
  public sealed class IoHackComponent : GH_Component
  {
    public IoHackComponent()
      : base("IO hacking test", "IOHack", "Test for reading and writing data into a GH file.", "IO", "Test")
    {
      FileData = null;
    }

    public override Guid ComponentGuid => new Guid("{0A9FAE7F-541B-4B34-90CC-C1AE3382CB57}");
    protected override void RegisterInputParams(GH_InputParamManager pManager) { }
    protected override void RegisterOutputParams(GH_OutputParamManager pManager)
    {
      pManager.AddTextParameter("Data", "D", "Data stored in file.", GH_ParamAccess.item);
    }
    protected override void SolveInstance(IGH_DataAccess access)
    {
      access.SetData(0, FileData);
    }
    public string FileData { get; set; }

    protected override void AppendAdditionalComponentMenuItems(ToolStripDropDown menu)
    {
      Menu_AppendItem(menu, "Set File Data", SetFileDataClick);
    }
    private void SetFileDataClick(object sender, EventArgs eventArgs)
    {
      if (Rhino.UI.Dialogs.ShowEditBox(
        "Global File Data",
        "Edit the global file data.",
        FileData,
        true,
        out string newFileData))
      {
        if (string.Equals(FileData, newFileData))
          return;

        FileData = newFileData;
        ExpireSolution(true);
      }
    }

    #region IO hacking
    private const string GlobalChunkName = "GlobalFileData";
    private const string GlobalValueName = "GlobalString";

    public override bool Write(GH_IWriter writer)
    {
      if (FileData != null)
        if (writer is GH_Chunk chunk)
        {
          var root = chunk.Archive.GetRootNode;
          if (root.FindChunk(GlobalChunkName) == null)
          {
            var globalChunk = chunk.Archive.GetRootNode.CreateChunk(GlobalChunkName);
            globalChunk.SetString(GlobalValueName, FileData);
          }
        }

      return base.Write(writer);
    }
    public override bool Read(GH_IReader reader)
    {
      FileData = null;
      if (reader is GH_Chunk chunk)
      {
        var globalChunk = chunk.Archive.GetRootNode.FindChunk(GlobalChunkName);
        if (globalChunk != null)
          FileData = globalChunk.GetString(GlobalValueName);
      }

      return base.Read(reader);
    }
    #endregion
  }
}
4 Likes