c#_Create Value List GH Component for Custom GH Plugin

Hi,
How can I create value list like component in custom grasshopper plugin?
Normally Custom components are inherited from GH_Component but for value list like compoent what do I have to do? I have several value lists to create and i want to have them as components so they are always in the plugin. User shall not be able to edit the values of course.

Any direction to solve this would be really helpful.

Thanks!!!

1 Like
public override void AddedToDocument(GH_Document document)
        {

            base.AddedToDocument(document);


            //Add Value List
            int[] stringID = new int[] { 1 };

            for (int i = 0; i < stringID.Length; i++)
            {
                Grasshopper.Kernel.Parameters.Param_String in0str = Params.Input[stringID[i]] as Grasshopper.Kernel.Parameters.Param_String;
                if (in0str == null || in0str.SourceCount > 0 || in0str.PersistentDataCount > 0) return;
                Attributes.PerformLayout();
                int x = (int)in0str.Attributes.Pivot.X - 250;
                int y = (int)in0str.Attributes.Pivot.Y - 10;
                Grasshopper.Kernel.Special.GH_ValueList valList = new Grasshopper.Kernel.Special.GH_ValueList();
                valList.CreateAttributes();
                valList.Attributes.Pivot = new PointF(x, y);
                valList.Attributes.ExpireLayout();
                valList.ListItems.Clear();

                List<Grasshopper.Kernel.Special.GH_ValueListItem> materials = new List<Grasshopper.Kernel.Special.GH_ValueListItem>()
        {
          new Grasshopper.Kernel.Special.GH_ValueListItem("silver ag 999", "10.49"),
          new Grasshopper.Kernel.Special.GH_ValueListItem("silver ag 925", "10.40"),
          new Grasshopper.Kernel.Special.GH_ValueListItem("white gold Au750Pd130", "15.80"),
          new Grasshopper.Kernel.Special.GH_ValueListItem("white gold Au750Pd150", "	15.90"),
          new Grasshopper.Kernel.Special.GH_ValueListItem("white gold Au750Pd210", "	16.30"),
          new Grasshopper.Kernel.Special.GH_ValueListItem("pale gold 2N Au750Ag160", "	15.60"),
          new Grasshopper.Kernel.Special.GH_ValueListItem("yellow gold 3N Au750Ag125", "	15.40"),
          new Grasshopper.Kernel.Special.GH_ValueListItem("rosé gold 4N Au750Ag90", "15.30"),
          new Grasshopper.Kernel.Special.GH_ValueListItem("red gold 5N Au750Ag45", "15.10"),
          new Grasshopper.Kernel.Special.GH_ValueListItem("most red gold 6N Au750Ag5Pt4", "15.10"),
          new Grasshopper.Kernel.Special.GH_ValueListItem("platine 950", "20.40"),
          new Grasshopper.Kernel.Special.GH_ValueListItem("palladium 950", "11.80")
          };
                valList.ListItems.AddRange(materials);
                document.AddObject(valList, false);
                in0str.AddSource(valList);
            }
        }

the user CAN change the values though by double clicking on the created value list. this will attach a new value list with prefdefined values when you drop your element on the canvas.

If you want to recreate a value list without the changing options, you have to build your own dropdown list etc in SpecialIntegerObject. ( I think)

I got here the SingleClickBoolean code that shows how to make custom elements (it’s a bit of a mess though):

using System;
using System.Collections.Generic;
using Grasshopper.Kernel;
using System.Drawing;
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel.Types;

namespace projectPhoenix
{
    public class SpecialIntegerObject : GH_Param<GH_Boolean>
    {
        
        public SpecialIntegerObject() :
        base(new GH_InstanceDescription("toggle", "toggle", "True/False", "Params", "Input"))
        {

        }
        public override void CreateAttributes()
        {
            m_attributes = new SpecialIntegerAttributes(this);
        }
        protected override Bitmap Icon
        {
            get
            {
                return Properties.Resources.oneClick;
                ///return null;
            }
        }
        public override GH_Exposure Exposure
        {
            get
            {
                return GH_Exposure.primary | GH_Exposure.obscure;
            }
        }
        public override Guid ComponentGuid
        {
            get { return new Guid("140a4f90-b246-4c0d-9448-9e56212e5515"); }
        }
        private bool m_value;
        public bool Value
        {
            get { return m_value; }
            set { m_value = value; }
        }
        protected override void CollectVolatileData_Custom()
        {
            VolatileData.Clear();
            AddVolatileData(new Grasshopper.Kernel.Data.GH_Path(0), 0, new GH_Boolean(Value));
        }
        public override bool Write(GH_IO.Serialization.GH_IWriter writer)
        {
            writer.SetBoolean("SpecialInteger", m_value);
            return base.Write(writer);
        }
        public override bool Read(GH_IO.Serialization.GH_IReader reader)
        {
            reader.TryGetBoolean("SpecialInteger", ref m_value);
            return base.Read(reader);
        }
    }
    public class SpecialIntegerAttributes : GH_Attributes<SpecialIntegerObject>
    {
        public SpecialIntegerAttributes(SpecialIntegerObject owner)
        : base(owner)
        {
        }
        public override bool HasInputGrip { get { return false; } }
        public override bool HasOutputGrip { get { return true; } }
        private const int ButtonSize = 22;
        public bool bo;

        protected override void Layout()
        {
            Pivot = GH_Convert.ToPoint(Pivot);
            Bounds = new RectangleF(Pivot, new SizeF(Button().Width + 4 + 22, 22));
        }
        private Rectangle Button()
        {
            int x = Convert.ToInt32(Pivot.X);
            int y = Convert.ToInt32(Pivot.Y);
            return new Rectangle(x  +24, y + 2, 46, 18);
        }
        public override GH_ObjectResponse RespondToMouseDown(GH_Canvas sender, GH_CanvasMouseEvent e)
        {
            //On one click we'll set the owner value.
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                RectangleF button = Button();
                if (button.Contains(e.CanvasLocation))
                {
                    bo = bo ? false : true;
                    Owner.RecordUndoEvent("value Change");
                    Owner.Value = bo;
                    Owner.ExpireSolution(true);
                    return GH_ObjectResponse.Handled;
                }
            }
            return base.RespondToMouseDown(sender, e);
        }
        /// <summary>
        /// </summary>
        protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
        {
            bo = Owner.Value ? true : false;
            GH_CapsuleRenderEngine.RenderOutputGrip(graphics, canvas.Viewport.Zoom, OutputGrip, true);
            Rectangle button = Button();
            GH_Capsule cap = GH_Capsule.CreateCapsule(new RectangleF(Pivot, new SizeF(Button().Width+4+22, 22)), GH_Palette.Transparent,3,8);

            cap.Render(graphics, Color.LightSlateGray);
            cap.Dispose();

            GH_Capsule capsule = GH_Capsule.CreateTextCapsule(button, button, GH_Palette.Black, bo.ToString(), new Font("Courier New",10),1,1);
            capsule.HighlightShape.Reset();
            capsule.Render(graphics, Selected, Owner.Locked, false);
            capsule.Dispose();

            Rectangle rnd = new Rectangle(Convert.ToInt32(Pivot.X) + 2, Convert.ToInt32(Pivot.Y) + 2, ButtonSize - 4, ButtonSize - 4);
            Color color = Owner.Value ? Color.Green : Color.Red;
            GH_Capsule buttonb = GH_Capsule.CreateCapsule(rnd, GH_Palette.Transparent, ButtonSize/2, 10);
            buttonb.HighlightShape.Reset();
            buttonb.HighlightShape.AddPie(rnd, 180f, 180f);
            buttonb.Render(graphics, color);

            buttonb.Dispose();
        }
    }
}

hope this helps

Ben

1 Like

@benedict, I am having a hard time trying to run run the example you provided:

I think I am doing something wrong. Does the code you provided go inside a regular GH template Class? Do I need to still provide the Guid? What about the RegisterInputParams and the OutputParams? What about the Constructor?

using System;
using System.Collections.Generic;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Grasshopper.Kernel.Parameters;
using System.Drawing;
using SAFEv1;
using Herramientas;
using Rhino.Geometry;


namespace GH_SAFE
{
    public class Class1 : GH_Component
    {

        public Class1() : base("xx", "xx", "xx", "GH-SAFE", " Assign")
        {
        }

        public override Guid ComponentGuid => throw new NotImplementedException();

        protected override void RegisterInputParams(GH_InputParamManager pManager)
        {
            throw new NotImplementedException();
        }

        protected override void RegisterOutputParams(GH_OutputParamManager pManager)
        {
            throw new NotImplementedException();
        }

        protected override void SolveInstance(IGH_DataAccess DA)
        {
            throw new NotImplementedException();
        }
    }
}

These questions are very basic but I am fairly new in GH components development and I have been looking for long time for a way to create a List Value with the other components that I have been developing.

Any comments to point me in the right direction will be much appreciated.

Anders Deleuran ( @AndersDeleuran ) posted a ‘Populate Value List’ thing in Python here:

Very handy! Scroll down to see a small enhancement/bug fix here:

1 Like

how to do a plug-in with python?
thw idea is that the value list is added when the element is placed on the canvas, therefor a python code is not very useful


there are two points to respect: 1. the position of the input must be correct and correspond ti the input parameters.
2. the code is added in a normal file containing all the usual overides for icon, inpit parameters etc.
I’m with my family now, and can hardly leave the party, but can reply more detailed tomorrow.
in advance: if you can describe what you are trying to achieve, with an example, it will be easier. otherwise I can provide the simplest example possible. but as said, won’t be today any morr.

happy new year!

1 Like

@benedict, the idea is to have Value list on one of the GH tabs and Panels so the user can drag and drop the list (In this case the list has multiple unit combinations) at any time he/she wants to do it. The list will be something like the image below:

image

The idea is to have something very similar like the pre-defined Value List shown in grasshopper:

image

The goal is to have the list value in a panel ready to be drag in to the canvas.

Thanks for your help! Happy New Year and enjoy the Party! :partying_face:

Hi again,

these are two different things. the code I provided is an override to add and populate a value list to the element when the eement is added to the document. this can be helpful if you want to avoid many clicks if in any case only this value list can be added to make your element work.
e.g. the first code I provided is a weight calculator and the value list that is added automatically when you drag the calculator on the canvas is useful for this very element only. therefor it makes no sense (for me in this case) to separately add a prefilled value list, but to connect the value list to the element in the first place.

your request is slightly different. you basically want to reprogram the value list element with preset values. Instead of recreating the whole thing, I’d have a look at the grasshopper.dll in ILSpy. In Grasshopper.Kernel.Special you can find the GH_ValueList, which gives you the basic code. You can pretty simply analyse it and make changes due to your needs, put it in VS and go. I avoid posting the entire code here for obvious reasons. Value list will be a bit more tricky than just a boolean toggle (second code provided).

sorry if my first post was confusing, as it adresses two different scenarios.

if(!helps) return more questions;)

Ben

Couldn’t you just create the value lists in GH and just save them as user objects? You’d have to bundle them with the plugin rather than being one single file but it will do them job far more easily and not really be any different for the end user. End user would be able to edit the values once placed but they would not affect values saved in the user object.

indeed, you are absolutely correct! I am obviously stuck in my c# habits. :sweat_smile:

@benedict, thanks for your response. Based on your explanation it make sense to follow the approach have the list added automatically when you drag and drop the component that needs the list.

I did not know about ILSpy. It might be an educational exercise to explore that route.

This is helpful. Thank you.

Rolando

Thanks @dom.beer43

This sounds like the easiest path for now!

in this case you can use exactly the code provided, but add it in your element under the icon or whereever , just not inside, but outside of SolveInstance.

this line basically identifies, where the value list will be added at. the “1” inside the curvy brackets points on the second input parameter. it works, if you have something like this:

 protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.AddBrepParameter("Brep", "B", "the model to calculate", GH_ParamAccess.list);
            pManager.AddTextParameter("Material", "M", "select a value list to M to get a list of precious gems OR connect your own denisty value", GH_ParamAccess.item);
            pManager.AddBooleanParameter("Total", "T", "total weight if true, else single weight", GH_ParamAccess.item, true);
        }

your input parameters are numbered after their order they’ve been added. note that the second parameter is text and can accept strings that are given out from the value list.
the curvy brackets can be used like this as well {0,1,2}, which would give you three times the same list.

here you got a element code using the logic, you should be able to copy paste your code from here:


using Grasshopper.Kernel;
using Rhino.Geometry;
using System;
using System.Collections.Generic;
using System.Drawing;

namespace RhineGem
{
    public class GemWeight : GH_Component
    {
        /// <summary>
        /// Initializes a new instance of the MyComponent1 class.
        /// </summary>
        public GemWeight()
          : base("Gem Weight Calculator", "GmWeightCalc",
              "choose from a list of gems and calculates the total weight of gems",
              "RhineGem", "Analysis")
        {
        }

        /// <summary>
        /// Registers all the input parameters for this component.
        /// </summary>
        protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.AddBrepParameter("Brep", "B", "the model to calculate", GH_ParamAccess.list);
            pManager.AddTextParameter("Material", "M", "select a value list to M to get a list of precious gems OR connect your own denisty value", GH_ParamAccess.item);
            pManager.AddBooleanParameter("Total", "T", "total weight if true, else single weight", GH_ParamAccess.item, true);
        }

        /// <summary>
        /// Registers all the output parameters for this component.
        /// </summary>
        protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
        {
            pManager.AddTextParameter("Weight in carats", "W", "gem weight in carat in the selected gem type", GH_ParamAccess.item);
        }

        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            List<Brep> Bs = new List<Brep>();
            if (!DA.GetDataList(0, Bs)) return;
            string M = "";
            if (!DA.GetData(1, ref M)) return;
            bool t = true;
            if (!DA.GetData(2, ref t)) return;

            double volcm = 0.0;
            if (t)
            {
                foreach (Brep B in Bs)
                {
                    volcm += B.GetVolume();
                }

                volcm /= 1000;
                double[] weight = { Math.Round(volcm * (Convert.ToDouble(M)), 4) };
                DA.SetDataList(0, weight);
            }
            else
            {
                List<double> dOut = new List<double>();
                for (int i = 0; i < Bs.Count; i++) dOut.Add(Math.Round(Bs[i].GetVolume() / 1000 * Convert.ToDouble(M), 4));
                DA.SetDataList(0, dOut);
            }
        }

        public override void AddedToDocument(GH_Document document)
        {

            base.AddedToDocument(document);


            //Add Value List
            int[] stringID = new int[] { 1 };

            for (int i = 0; i < stringID.Length; i++)
            {
                Grasshopper.Kernel.Parameters.Param_String in0str = Params.Input[stringID[i]] as Grasshopper.Kernel.Parameters.Param_String;
                if (in0str == null || in0str.SourceCount > 0 || in0str.PersistentDataCount > 0) return;
                Attributes.PerformLayout();
                int x = (int)in0str.Attributes.Pivot.X - 250;
                int y = (int)in0str.Attributes.Pivot.Y - 10;
                Grasshopper.Kernel.Special.GH_ValueList valList = new Grasshopper.Kernel.Special.GH_ValueList();
                valList.CreateAttributes();
                valList.Attributes.Pivot = new PointF(x, y);
                valList.Attributes.ExpireLayout();
                valList.ListItems.Clear();

                List<Grasshopper.Kernel.Special.GH_ValueListItem> materials = new List<Grasshopper.Kernel.Special.GH_ValueListItem>()
                 {
                    new Grasshopper.Kernel.Special.GH_ValueListItem("Actinolite","3.05"),
                    new Grasshopper.Kernel.Special.GH_ValueListItem("Uvarovite","3.465"),
                    new Grasshopper.Kernel.Special.GH_ValueListItem("Vanadium ","3.74"),
                    new Grasshopper.Kernel.Special.GH_ValueListItem("Williamsite","2.57"),
                    new Grasshopper.Kernel.Special.GH_ValueListItem("Zircon","4.33")

          };
                valList.ListItems.AddRange(materials);
                document.AddObject(valList, false);
                in0str.AddSource(valList);
            }
        }
        /// <summary>
        /// Provides an Icon for the component.
        /// </summary>
        protected override System.Drawing.Bitmap Icon
        {
            get
            {
                //You can add image files to your project resources and access them like this:
                // return Resources.IconForThisComponent;
                return Properties.Resources.gmbalance24;
            }
        }

        /// <summary>
        /// Gets the unique ID for this component. Do not change this ID after release.
        /// </summary>
        public override Guid ComponentGuid
        {
            get { return new Guid("!!!!!!!!!!!!!!!!!!!!!!!!myGUID!!!!!!!!!!!!!!"); }
        }
    }
}

add your own GUID!!

2 Likes

Hello Ben,
When I run your code, two components are created on the Grasshopper canvas. How can I modify the code so that only the value list remains?

Hi Lyudmyla,

after saving and reopening? I think there was a change with grasshopper lately, I also have double elements on saved files now. IDK why grasshopper would call the AddedToDocument method when opening the file I haven’t looked deeper into this up to now, as I don’t have time at all right now. I’m sorry this old version isn’t working correctly any more.

If you’re speaking in general, this line:

int[] stringID = new int[] { 1 };

determines the number and position of the value list.

Please note that if you want to add the value list only (not connected to anything) then @dom.beer43 's solution is waaay easier and the code isn’t fit for your needs. this code provides a value list connected to your custom node and not a value list only.

1 Like

You’re welcome. It works correctly, but I just need to have the Value List as a component. I want the algorithm for the Value List component. :pray:

Thank you Ben!

try @dom.beer43 's solution then from this post.

1 Like

Thank you Ben

1 Like

I dont want use user bject. What should I do?

What’s the reason you don’t want to work with user objects?

Anyway, if you want to code it: if you place the element, inside the OnAddedToDocument method of this element is everything you need to construct a populated value list attached to itelement only. you would take that as a base and code the rest around it using the SpecialIntegerObject provided in the solution thread.

Alternative would be using some disassembler and modify the code used in grasshopper.dll to your needs, you should be aware though that I don’t know anything about your countries laws or how Rhino conditions exclude your possibilities to disassemble for your specific purpose. If you plan to sell the code afterwards, I’d rather go with the first option.