Creating New Nodes

Hi All,

Just wondering if anyone has any experience creating icon/nodes from functions.

To explain what I am trying to do:

I have created a few functions using the basic method shown in the 2019_02 GC SIG "The GC SDK Digging_Deeper".

Using just a script in simple string form, as a easy way to get into creating Add-ins. 

That all works fine as and we can utilise the function from the function dialog or using a function call.

I would however like to create an icon/node that I can link to this function.

I have created an icon/node and placed it into a NodeTypePaletteCateory etc 

Just wondering what how I would go about calling the function I created? I assume I would need to create a funcitoncall as an input and populate it with the new function I created. 

We are trying to make the custom function more accessible to new users that are more familiar with node based workflows.

Any help would be appreciated.

Will post the results, maybe someone will find it useful.

Thanks

Wayne

Parents
  • Hi Wayne,

    I'm not familiar with that SIG, but I assume you're talking about a custom function and a custom node type that you've implemented in a GC add-in, which you created in Visual Studio.

    From a GC user's point of view, there should be no difference between your custom function and any other GC function. Likewise, there should be no difference between your custom node type and any other GC node type. In other words, the user shouldn't need to know -- or care -- where that function and node type came from.

    Just as in any other scenario, you should be able to call your custom function within an expression that you provide to an input to your custom node. Or, you should be able to create a FunctionCall node that calls your custom function, and then reference that FunctionCall node in an expression that you provide as an input to your custom node.

    Or am I off track from what you're asking?

    Jeff

  • Hi Jeff,

    Thanks for responding.

    Sorry my question was a bit vague on what I am exactly trying to do.

    As an example I wrote a new add-in by just using gcscript language in Visual Studio. I will attache an example below.

    This makes it easy as someone that is not familiar with Visual Studio to get an add-in for delivering function to a team.

    Basically found the handy line:

    GCScriptTools.ExecuteInItsEntirety(GCTools.UIGCSpace(), scriptwayne, ScriptFlags.UserAuthored, BreakpointAcknowledgement.Ignore);

    What i would like to do now is take that function and present it as a icon/node to the user in a new PaletteCateory.

    I managed to create an icon and a new Palette Category but I am struggling on how to now call the function in Visual Studio context.

    As an example I have included a bit of script from Visual Studio I was using for the icon.

    Hope that makes sense

    Thanks again 
    Wayne

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Bentley.GenerativeComponents;
    using Bentley.GenerativeComponents.AddInSupport;
    using Bentley.GenerativeComponents.Features;
    using Bentley.GenerativeComponents.Features.Specific;
    using Bentley.GenerativeComponents.GeneralPurpose;
    using Bentley.GenerativeComponents.GCScript;
    using Bentley.GenerativeComponents.GCScript.FundamentalValues;
    using Bentley.GenerativeComponents.GCScript.GCTypes;
    using Bentley.GenerativeComponents.GCScript.NameScopes;
    using Bentley.GenerativeComponents.MicroStation;
    using Bentley.Interop.MicroStationDGN;
    using Bentley.GeometryNET;
    using System.ComponentModel;
    using Bentley.GenerativeComponents.View;
    
    
    namespace SampleAddIn
    {
        [GCNamespace("User")]                                   // The GCNamespace attribute lets us specify where this SimpleLine node
                                                                // type will appear within GCScript's namespace tree (that is, the
                                                                // namespaces that are perceived by the GC user). This namespace has no
                                                                // relation to our C# namespace, which is (in this case) SampleAddIn.
    
        [NodeTypePaletteCategory("Wayne")]              // The NodeTypePaletteCategory attribute lets us specify where this
                                                                // SimpleLine node type will appear within GC's Node Types dialog.
                                                                // So, it will appear within a group named "Sample Add-In".
    
        [NodeTypeIcon("Resources/SimpleLineNode.png")]
    
        [Summary("Is a script i wrote")]
    
    
    
    
        public class ScriptFunctions
    
    
        //static internal class ScriptFunctions
        {
            static internal void Load()
            {
                // This method is called from within the constructor of the class, Initializer (within this project). So, this
                // method will be called automatically whenever the user loads this assembly, SampleAddIn, into GC.
    
                IGCEnvironment environment = UniversalGCEnvironment.TheOnlyInstance;
                NameCatalog nameCatalog = environment.TopLevelNameCatalog();
    
                // To add a new function to the GCScript processor, we call the method, nameCatalog.AddNamespaceLevelFunction.
                //
                // 1. The first argument is the name of the new function, as the GC user will see it. (It's our responsibility
                //    to ensure that the name doesn't conflict with another top-level name in GC.)
                //
                // 2. The second argument is the type (also known as the signature) of the new function, expressed in GCScript
                //    language.
                //
                // 3. The third argument is the name of the C# method that implements the new function. That method can have any
                //    name; however, we recommend that it have the same name as the function.
    
                
    
                DefineScriptFunctionsWrittenInScript();
            }
    
            // Each method that implements a script function may be prefaced by a 'Summary' attribute, which provides user
            // documentation for that function. The documentation text will appear in GC's Functions dialog. (The presence
            // or absence of a Summary attribute has no effect on how the function may be used in GC.)
    
           
    
    
    
            static void DefineScriptFunctionsWrittenInScript()
            {
                // The following script is copied from the contents of the script transaction that results if we define one or more
                // new script function in the Functions dialog, then commit that transaction.
    
                string scriptwayne = "global redeclare object WayneReverse(object List)\n" +
                                "{\n" +
                                "object NewList = Reverse(List);\n" +
                                "return NewList;\n" +
                                "}\n";
    
    
                // Now run the aforementioned script, which results in the script functions (in this case, the one function 'Angle360') being defined,
                // as though we were running a script transaction containing that script.
    
                GCScriptTools.ExecuteInItsEntirety(GCTools.UIGCSpace(), scriptwayne, ScriptFlags.UserAuthored, BreakpointAcknowledgement.Ignore);
                
            }
            
           
    
            
    
        }
    }
    

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using Bentley.GenerativeComponents;
    using Bentley.GenerativeComponents.AddInSupport;
    using Bentley.GenerativeComponents.Features;
    using Bentley.GenerativeComponents.GCScript;
    using Bentley.GenerativeComponents.GCScript.NameScopes;
    using Bentley.GenerativeComponents.GeneralPurpose;
    using Bentley.GenerativeComponents.MicroStation;
    using Bentley.GenerativeComponents.View;
    using Bentley.Interop.MicroStationDGN;
    using Bentley.GenerativeComponents.GCScript.FundamentalValues;
    using Bentley.GenerativeComponents.GCScript.GCTypes;
    using Bentley.GeometryNET;
    using Bentley.GenerativeComponents.UtilityNodes;
    
    namespace SampleAddIn
    {
        [GCNamespace("Waynetest")]                              // The GCNamespace attribute lets us specify where this SimpleLine node
                                                                // type will appear within GCScript's namespace tree (that is, the
                                                                // namespaces that are perceived by the GC user). This namespace has no
                                                                // relation to our C# namespace, which is (in this case) SampleAddIn.
    
        [NodeTypePaletteCategory("Wayne")]              // The NodeTypePaletteCategory attribute lets us specify where this
                                                        // SimpleLine node type will appear within GC's Node Types dialog.
                                                        // So, it will appear within a group named "Sample Add-In".
    
        [NodeTypeIcon("Resources/SimpleLineNode.png")]          // The NodeTypeIcon attribute lets us specify the graphical image (icon)
                                                                // that will appear on the SimpleLine node type's button within GC's Node
                                                                // Types dialog.
    
        [Summary("A line that connects two points in space.")]  // The Summary attribute lets us provide a brief description of this
                                                                // node's intended purposed. The text will be displayed when the user
                                                                // hovers over our node type in GC's Node Types dialog.
    
        public class WayneSimpleLine: UtilityNode
        {
    
           
        } // class
    
    } // namespace
    
    

  • Hi Wayne,

    I'm afraid your approach is a bit misguided. Regardless of how they've been implemented, GC functions are intended to be called only from within (other) script code, not from C# code.

    Alternatively, I recommend this approach for writing your add-in:

    1. Implement your custom function as a purely-C# method, to be called only from C# code. It will take appropriate C#-level arguments, and return an appropriate C#-level value.

    2. Within your custom node type, of course you can directly call that purely-C# method.

    3. To expose that functionality to GC as a script-callable function, you will write a C# wrapper method having the same general appearance as the sample method, ScriptFunctions.ConvertTemperature, which is in GC's sample add-in, VS2013.sln. Within the body of your wrapper method, you'll "unbox" the CallFrame arguments, then call your purely-C# method. Then you'll return the C# result to GC by calling callFrame.SetFunctionResult. (Again, this is all shown in that sample method.)

    Thus, the same underlying functionality will be shared by both your custom node type and your custom script-callable function.

    HTH

    Jeff

  • Hi Jeff,

    Thanks for the info, I have "headed back to school" on C# and learning a lot more.

    So I started with a goal of creating a node that will filter a list with another Boolean list.

    in C# I created something using my very basic C# skills which looked like this:

                static void Main(string[] args)
            {
    
                var mylist = new string[] {"a1","a2","a3","a4","a5","a6","a7","a8","a9" };
                var myfilter = new Boolean[] { true, false, true, true, true, false, false, true, true };
    
                List<string> myfilteredlist = new List<string>();
                
                for (int i = 0; i < mylist.Length; i++)
                {
                    if (myfilter[i])
                    {
                        myfilteredlist.Add(mylist[i]);
                    }
    
                }
                foreach (var myitems in myfilteredlist)
                {
                    Console.WriteLine(myitems);
                }
                
            }

    very basic but I appears to do the job.

    Now trying to implement that into a GC node by taking 2 inputs (mylist,myfilter) and output a new filtered list.

    reading and re-reading the Calculator example I am up to here so far but a bit stuck now as I am not getting anything appearing in GC yet. I know it will not work yet but I was expecting it to display the node icon even if it didn't do anything yet.

    If you have any time I would appreciate any tips.

    using System;
    using System.Collections.Generic;
    using Bentley.GenerativeComponents;
    using Bentley.GenerativeComponents.GCScript;
    using Bentley.GenerativeComponents.GCScript.GCTypes;
    using Bentley.GenerativeComponents.GCScript.NameScopes;
    using Bentley.GenerativeComponents.GCScript.ReflectedNativeTypeSupport;
    using Bentley.GenerativeComponents.GeneralPurpose;
    using Bentley.GenerativeComponents.UtilityNodes;
    using Bentley.GenerativeComponents.View;
    
    
    
    namespace FilterList
    {
        [GCNamespace("User")]
        [NodeTypePaletteCategory("wayne")]
        [NodeTypeIcon("Resource/wayne.png")]
        // The name of the Node Pallet and the icon
    
        class Program: UtilityNode // setting it as a Utility Node rather than a node node.
        {
            // below are required lines (I think)
            static readonly NodeGCType s_gcTypeOfAllInstances = (NodeGCType)GCTypeTools.GetGCType(UniversalGCEnvironment.TheOnlyInstance, typeof(Program));
    
            static public NodeGCType GCTypeOfAllInstances
            {
                get { return s_gcTypeOfAllInstances; }
            }
            static void AddAdditionalMembersToGCType(IGCEnvironment environment, GCType gcType, NativeNamespaceTranslator namespaceTranslator)
            {
            //setup the parameters and technique as default
            
                UtilityNodeTechnique technique1 = gcType.AddDefaultNodeTechnique("Default", DefaultTechnique);
                technique1.AddParameterDefinition(environment, "InputList", typeof(string[]), "",
                                                 Ls.Literal("The list to be filtered"));
    
                technique1.AddParameterDefinition(environment, "FilterList", typeof(Boolean[]), null,
                                                  Ls.Literal("The filter list (Boolean list)"));
    
                technique1.AddParameterDefinition(environment, "ResultList", typeof(string[]), "",
                                      Ls.Literal("The filtered list."), NodePortRole.TechniqueOutputOnly);
            }
            static NodeUpdateResult DefaultTechnique(UtilityNode node, NodeUpdateContext updateContext)
            {
            // this is just filler till i can work out this part
                Program myfilter = (Program) node;
                return NodeUpdateResult.Success;
            }
                static void Main(string[] args)
            {
    
                var mylist = new string[] {"a1","a2","a3","a4","a5","a6","a7","a8","a9" };
                var myfilter = new Boolean[] { true, false, true, true, true, false, false, true, true };
    
                List<string> myfilteredlist = new List<string>();
                
                for (int i = 0; i < mylist.Length; i++)
                {
                    if (myfilter[i])
                    {
                        myfilteredlist.Add(mylist[i]);
                    }
    
                }
                foreach (var myitems in myfilteredlist)
                {
                    Console.WriteLine(myitems);
                }
                
            }
        }
    }
    

    Thanks

    Wayne

  • Hi Wayne,

    Here's a working version of your new node type. (I copied and modified the 'Calculator' node from the sample add-in.)

    HTH

    Jeff

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using Bentley.GenerativeComponents;
    using Bentley.GenerativeComponents.GCScript;
    using Bentley.GenerativeComponents.GCScript.GCTypes;
    using Bentley.GenerativeComponents.GCScript.NameScopes;
    using Bentley.GenerativeComponents.GCScript.ReflectedNativeTypeSupport;
    using Bentley.GenerativeComponents.GeneralPurpose;
    using Bentley.GenerativeComponents.UtilityNodes;
    using Bentley.GenerativeComponents.View;
    
    namespace SampleAddIn
    {
        [GCNamespace("User")]
        [NodeTypePaletteCategory("wayne")]
        // [NodeTypeIcon("Resources/wayne.png")]
        public class ListFilter: UtilityNode
        {
            // Following are the names of all of the possible inputs and outputs to this ListFilter node type. Since we will be
            // referencing these names more than once herein, it's a good idea to define them as constants.
    
            internal const string NameOfInputListProperty  = "InputList";
            internal const string NameOfFilterListProperty = "FilterList";
            internal const string NameOfResultListProperty = "ResultList";
    
            static readonly NodeGCType s_gcTypeOfAllInstances = (NodeGCType) GCTypeTools.GetGCType(UniversalGCEnvironment.TheOnlyInstance, typeof(ListFilter));
    
            static public NodeGCType GCTypeOfAllInstances
            {
                get { return s_gcTypeOfAllInstances; }
            }
    
            static void AddAdditionalMembersToGCType(IGCEnvironment environment, GCType gcType, NativeNamespaceTranslator namespaceTranslator)
            {
                UtilityNodeTechnique technique1 = gcType.AddDefaultNodeTechnique("Default", DefaultTechnique);
    
                technique1.AddParameterDefinition(environment, NameOfInputListProperty, typeof(string[]), "",
                                                 Ls.Literal("The list to be filtered"));
    
                technique1.AddParameterDefinition(environment, NameOfFilterListProperty, typeof(Boolean[]), null,
                                                  Ls.Literal("The filter list (Boolean list)"));
    
                technique1.AddParameterDefinition(environment, NameOfResultListProperty, typeof(string[]), "",
                                      Ls.Literal("The filtered list."), NodePortRole.TechniqueOutputOnly);
            }
    
            static NodeUpdateResult DefaultTechnique(UtilityNode node, NodeUpdateContext updateContext)
            {
                ListFilter listFilter = (ListFilter) node;  // It's safe to assume that the given node is of this class type
                                                            // (ListFilter).
    
                string[]     mylist         = listFilter.InputList;
                bool[]       myfilter       = listFilter.FilterList;
                List<string> myfilteredlist = new List<string>();
    
                for (int i = 0; i < mylist.Length; i++)
                {
                    if (myfilter[i])
                    {
                        myfilteredlist.Add(mylist[i]);
                    }
                }
    
                listFilter.Result = myfilteredlist.ToArray();
    
                return NodeUpdateResult.Success;
            }
    
            // Beginning of instance members.
    
            // The following two methods -- ActiveNodeState and GetInitialNodeState -- are simply "must be defined"
            // methods that all Node-based node classes must have. These can simply be copied-and-pasted
            // into your own Node-based class (changing the name of the constructor, of course).
    
            internal new NodeState State
            {
                get { return (NodeState) base.State; }
            }
    
            protected override UtilityNode.NodeState GetInitialState(NodeTechniqueDetermination initialActiveTechniqueDetermination)
            {
                return new NodeState(this, initialActiveTechniqueDetermination);
            }
    
            public string[] InputList
            {
                get { return State.InputListProperty.GetNativeValue<string[]>(); }
                set { State.InputListProperty.SetNativeValueAndInputExpression(value); }
            }
    
            public bool[] FilterList
            {
                get { return State.FilterListProperty.GetNativeValue<bool[]>(); }
                set { State.FilterListProperty.SetNativeValueAndInputExpression(value); }
            }
    
            public string[] Result
            {
                get { return State.ResultListProperty.GetNativeValue<string[]>(); }
                set { State.ResultListProperty.SetNativeValueAndInputExpression(value); }
            }
    
            // Following is the embedded NodeState class that every Node-based class must have.
            //
            // This embedded class can be copied-and-pasted from this class to your own node type class, then you can replace the
            // list of properties with the inputs and outputs that are specific to your own class.
    
            public new class NodeState: UtilityNode.NodeState
            {
                // There must be one NodeProperty field for each unique input and output of your node type, regardless of which
                // technique(s) each input and output belongs to. The order of these fields is irrelevant; we suggest listing them
                // alphabetically.
    
                internal readonly UtilityNodeProperty InputListProperty;
                internal readonly UtilityNodeProperty FilterListProperty;
                internal readonly UtilityNodeProperty ResultListProperty;
    
                internal protected NodeState(ListFilter parentNode, NodeTechniqueDetermination initialActiveTechniqueDetermination):
                                             base(parentNode, initialActiveTechniqueDetermination)
                {
                    // IMPORTANT: This constructor calls 'AddProperty' to get each property field, whereas the following
                    // constructor calls 'GetProperty'.
    
                    InputListProperty  = AddProperty(NameOfInputListProperty);
                    FilterListProperty = AddProperty(NameOfFilterListProperty);
                    ResultListProperty = AddProperty(NameOfResultListProperty);
                }
    
                protected NodeState(NodeState source): base(source)
                {
                    // IMPORTANT: This constructor calls 'GetProperty' to get each property field, whereas the preceding
                    // constructor calls 'AddProperty'.
    
                    InputListProperty  = GetProperty(NameOfInputListProperty);
                    FilterListProperty = GetProperty(NameOfFilterListProperty);
                    ResultListProperty = GetProperty(NameOfResultListProperty);
                }
    
                protected new ListFilter UtilityNode
                {
                    get { return (ListFilter) base.UtilityNode(); }
                }
    
                public override UtilityNode.NodeState Clone()
                {
                    return new NodeState(this);
                }
    
                public override bool TryGetDefaultOutputProperty(out INodeProperty property)
                {
                    property = ResultListProperty;
                    return true;
                }
            }
        }
    }

  • Thanks Jeff,

    That is great. With my limited VS experience I didn't realise all the additional elements supplied in the sample solution are important in getting it to work. I had started a fresh solution and hence had more issues Slight smile

    I made an addition to the code to check if the input list is not longer than the filter list. (code below)

    Next Step

    So now that is only partially useful, so what I would like to do is filter geometry nodes not just strings.

    Before heading off on another tangent, is it possible to pass geometry though a "filter node" to then be processed by another node.

    For example maybe filter a list of polygons and then apply a gnt to the filtered list. 

    I started looking at using objects but not sure that will work. different data types etc.

    Here is the node so far: String filtering works well

    updated Code

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using Bentley.GenerativeComponents;
    using Bentley.GenerativeComponents.GCScript;
    using Bentley.GenerativeComponents.GCScript.GCTypes;
    using Bentley.GenerativeComponents.GCScript.NameScopes;
    using Bentley.GenerativeComponents.GCScript.ReflectedNativeTypeSupport;
    using Bentley.GenerativeComponents.GeneralPurpose;
    using Bentley.GenerativeComponents.UtilityNodes;
    using Bentley.GenerativeComponents.View;
    
    namespace SampleAddIn
    {
        [GCNamespace("User")]
        [NodeTypePaletteCategory("Lists")]
        [NodeTypeIcon("Resources/wayne.png")]
        public class ListFilter : UtilityNode
        {
            internal const string NameOfInputListProperty = "InputList";
            internal const string NameOfFilterListProperty = "FilterList";
            internal const string NameOfResultListProperty = "ResultList";
    
            static readonly NodeGCType s_gcTypeOfAllInstances = (NodeGCType)GCTypeTools.GetGCType(UniversalGCEnvironment.TheOnlyInstance, typeof(ListFilter));
    
            static public NodeGCType GCTypeOfAllInstances
            {
                get { return s_gcTypeOfAllInstances; }
            }
    
            static void AddAdditionalMembersToGCType(IGCEnvironment environment, GCType gcType, NativeNamespaceTranslator namespaceTranslator)
            {
                UtilityNodeTechnique technique1 = gcType.AddDefaultNodeTechnique("Default", DefaultTechnique);
    
                technique1.AddParameterDefinition(environment, NameOfInputListProperty, typeof(string[]), "",
                                                 Ls.Literal("The list to be filtered"));
    
                technique1.AddParameterDefinition(environment, NameOfFilterListProperty, typeof(Boolean[]), null,
                                                  Ls.Literal("The filter list (Boolean list)"));
    
                technique1.AddParameterDefinition(environment, NameOfResultListProperty, typeof(string[]), "",
                                      Ls.Literal("The filtered list."), NodePortRole.TechniqueOutputOnly);
            }
    
            static NodeUpdateResult DefaultTechnique(UtilityNode node, NodeUpdateContext updateContext)
            {
                ListFilter listFilter = (ListFilter)node;  // It's safe to assume that the given node is of this class type
                                                           // (ListFilter).
    
                string[] mylist = listFilter.InputList;
                bool[] myfilter = listFilter.FilterList;
                List<string> myfilteredlist = new List<string>();
    
                if (mylist.Length > myfilter.Length)
                    return new NodeUpdateResult.TechniqueInvalidArguments(NameOfFilterListProperty + " Error - Filter List is shorter than Input List");
    
                else
                {
                    for (int i = 0; i < mylist.Length; i++)
                    {
                        if (myfilter[i])
                        {
                            myfilteredlist.Add(mylist[i]);
                        }
                    }
    
                    listFilter.Result = myfilteredlist.ToArray();
    
                    return NodeUpdateResult.Success;
                }
            }
    
            // Beginning of instance members.
    
            // The following two methods -- ActiveNodeState and GetInitialNodeState -- are simply "must be defined"
            // methods that all Node-based node classes must have. These can simply be copied-and-pasted
            // into your own Node-based class (changing the name of the constructor, of course).
    
            internal new NodeState State
            {
                get { return (NodeState)base.State; }
            }
    
            protected override UtilityNode.NodeState GetInitialState(NodeTechniqueDetermination initialActiveTechniqueDetermination)
            {
                return new NodeState(this, initialActiveTechniqueDetermination);
            }
    
            public string[] InputList
            {
                get { return State.InputListProperty.GetNativeValue<string[]>(); }
                set { State.InputListProperty.SetNativeValueAndInputExpression(value); }
            }
    
            public bool[] FilterList
            {
                get { return State.FilterListProperty.GetNativeValue<bool[]>(); }
                set { State.FilterListProperty.SetNativeValueAndInputExpression(value); }
            }
    
            public string[] Result
            {
                get { return State.ResultListProperty.GetNativeValue<string[]>(); }
                set { State.ResultListProperty.SetNativeValueAndInputExpression(value); }
            }
    
            // Following is the embedded NodeState class that every Node-based class must have.
            //
            // This embedded class can be copied-and-pasted from this class to your own node type class, then you can replace the
            // list of properties with the inputs and outputs that are specific to your own class.
    
            public new class NodeState : UtilityNode.NodeState
            {
                // There must be one NodeProperty field for each unique input and output of your node type, regardless of which
                // technique(s) each input and output belongs to. The order of these fields is irrelevant; we suggest listing them
                // alphabetically.
    
                internal readonly UtilityNodeProperty InputListProperty;
                internal readonly UtilityNodeProperty FilterListProperty;
                internal readonly UtilityNodeProperty ResultListProperty;
    
                internal protected NodeState(ListFilter parentNode, NodeTechniqueDetermination initialActiveTechniqueDetermination) :
                                             base(parentNode, initialActiveTechniqueDetermination)
                {
                    // IMPORTANT: This constructor calls 'AddProperty' to get each property field, whereas the following
                    // constructor calls 'GetProperty'.
    
                    InputListProperty = AddProperty(NameOfInputListProperty);
                    FilterListProperty = AddProperty(NameOfFilterListProperty);
                    ResultListProperty = AddProperty(NameOfResultListProperty);
                }
    
                protected NodeState(NodeState source) : base(source)
                {
                    // IMPORTANT: This constructor calls 'GetProperty' to get each property field, whereas the preceding
                    // constructor calls 'AddProperty'.
    
                    InputListProperty = GetProperty(NameOfInputListProperty);
                    FilterListProperty = GetProperty(NameOfFilterListProperty);
                    ResultListProperty = GetProperty(NameOfResultListProperty);
                }
    
                protected new ListFilter UtilityNode
                {
                    get { return (ListFilter)base.UtilityNode(); }
                }
    
                public override UtilityNode.NodeState Clone()
                {
                    return new NodeState(this);
                }
    
                public override bool TryGetDefaultOutputProperty(out INodeProperty property)
                {
                    property = ResultListProperty;
                    return true;
                }
            }
        }
    }
    

    Thanks Again
    Wayne

  • Hi Wayne,

    I'm glad it's working for you.

    You can revise your ListFilter node to work with a list of any type of items, not just a list of strings, merely by replacing every occurrence of "string[]" with "object[]".

    Jeff

Reply Children
  • Excellent.

    Thanks Jeff.

    I got a little carried away and made a few list nodes.

    One that doesn't work however is the Split List node.

    I tried to take a list and at a index split it into 2 output lists.

    I must have something wrong in the results part. It doesn't error in VS but complains in GC about "The given key was not present in the dictionary."

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using Bentley.GenerativeComponents;
    using Bentley.GenerativeComponents.GCScript;
    using Bentley.GenerativeComponents.GCScript.GCTypes;
    using Bentley.GenerativeComponents.GCScript.NameScopes;
    using Bentley.GenerativeComponents.GCScript.ReflectedNativeTypeSupport;
    using Bentley.GenerativeComponents.GeneralPurpose;
    using Bentley.GenerativeComponents.UtilityNodes;
    using Bentley.GenerativeComponents.View;
    
    namespace SplitList
    {
        [GCNamespace("User")]
        [NodeTypePaletteCategory("Lists")]
        [NodeTypeIcon("Resources/ListSplit.png")]
        public class SplitList : UtilityNode
        {
            internal const string NameOfInputListProperty = "InputList";
            internal const string NameOfSplitListProperty = "SplitPoint";
            internal const string NameOfResultList1Property = "ResultList1";
            internal const string NameOfResultList2Property = "ResultList2";
    
            static readonly NodeGCType s_gcTypeOfAllInstances = (NodeGCType)GCTypeTools.GetGCType(UniversalGCEnvironment.TheOnlyInstance, typeof(SplitList));
    
            static public NodeGCType GCTypeOfAllInstances
            {
                get { return s_gcTypeOfAllInstances; }
            }
    
            static void AddAdditionalMembersToGCType(IGCEnvironment environment, GCType gcType, NativeNamespaceTranslator namespaceTranslator)
            {
                UtilityNodeTechnique technique1 = gcType.AddDefaultNodeTechnique("Default", DefaultTechnique);
    
                technique1.AddParameterDefinition(environment, NameOfInputListProperty, typeof(object[]), "",
                                                 Ls.Literal("The list to be filtered"));
    
                technique1.AddParameterDefinition(environment, NameOfSplitListProperty, typeof(int), null,
                                                  Ls.Literal("The index point of split"));
    
                technique1.AddParameterDefinition(environment, NameOfResultList1Property, typeof(object[]), "",
                                      Ls.Literal("The first part of the list."), NodePortRole.TechniqueOutputOnly);
    
                technique1.AddParameterDefinition(environment, NameOfResultList2Property, typeof(object[]), "",
                          Ls.Literal("The second part of the list."), NodePortRole.TechniqueOutputOnly);
            }
    
            static NodeUpdateResult DefaultTechnique(UtilityNode node, NodeUpdateContext updateContext)
            {
                SplitList splitList = (SplitList)node;  // It's safe to assume that the given node is of this class type
                                                           // (ListFilter).
    
                object[] mylist = splitList.InputList;
                int maxValue = splitList.SplitPoint;
                List<object> mylist1 = new List<object>();
                List<object> mylist2 = new List<object>();
    
                if (maxValue >= mylist.Length)
                    return new NodeUpdateResult.TechniqueInvalidArguments(NameOfSplitListProperty + " Error - Index is higher than InputList");
    
                else
                {
                    for (int i = 0; i < mylist.Length; i++)
                    {
                        if (mylist.Length - 1 <= maxValue)
                        {
    
                            mylist1.Add(mylist[i]);
                        }
                        else
                        {
                            mylist2.Add(mylist[i]);
                        }
                    }
    
                    splitList.Result1 = mylist1.ToArray();
                    splitList.Result2 = mylist2.ToArray();
    
                    return NodeUpdateResult.Success;
                }
            }
    
            // Beginning of instance members.
    
            // The following two methods -- ActiveNodeState and GetInitialNodeState -- are simply "must be defined"
            // methods that all Node-based node classes must have. These can simply be copied-and-pasted
            // into your own Node-based class (changing the name of the constructor, of course).
    
            internal new NodeState State
            {
                get { return (NodeState)base.State; }
            }
    
            protected override UtilityNode.NodeState GetInitialState(NodeTechniqueDetermination initialActiveTechniqueDetermination)
            {
                return new NodeState(this, initialActiveTechniqueDetermination);
            }
    
            public object[] InputList
            {
                get { return State.InputListProperty.GetNativeValue<object[]>(); }
                set { State.InputListProperty.SetNativeValueAndInputExpression(value); }
            }
    
            public int SplitPoint
            {
                get { return State.SplitListProperty.GetNativeValue<int>(); }
                set { State.SplitListProperty.SetNativeValueAndInputExpression(value); }
            }
    
            public object[] Result1
            {
                get { return State.ResultList1Property.GetNativeValue<object[]>(); }
                set { State.ResultList1Property.SetNativeValueAndInputExpression(value); }
            }
            public object[] Result2
            {
                get { return State.ResultList2Property.GetNativeValue<object[]>(); }
                set { State.ResultList2Property.SetNativeValueAndInputExpression(value); }
            }
    
            // Following is the embedded NodeState class that every Node-based class must have.
            //
            // This embedded class can be copied-and-pasted from this class to your own node type class, then you can replace the
            // list of properties with the inputs and outputs that are specific to your own class.
    
            public new class NodeState : UtilityNode.NodeState
            {
                // There must be one NodeProperty field for each unique input and output of your node type, regardless of which
                // technique(s) each input and output belongs to. The order of these fields is irrelevant; we suggest listing them
                // alphabetically.
    
                internal readonly UtilityNodeProperty InputListProperty;
                internal readonly UtilityNodeProperty SplitListProperty;
                internal readonly UtilityNodeProperty ResultList1Property;
                internal readonly UtilityNodeProperty ResultList2Property;
    
                internal protected NodeState(SplitList parentNode, NodeTechniqueDetermination initialActiveTechniqueDetermination) :
                                             base(parentNode, initialActiveTechniqueDetermination)
                {
                    // IMPORTANT: This constructor calls 'AddProperty' to get each property field, whereas the following
                    // constructor calls 'GetProperty'.
    
                    InputListProperty = AddProperty(NameOfInputListProperty);
                    SplitListProperty = AddProperty(NameOfSplitListProperty);
                    ResultList2Property = AddProperty(NameOfResultList2Property);
                }
    
                protected NodeState(NodeState source) : base(source)
                {
                    // IMPORTANT: This constructor calls 'GetProperty' to get each property field, whereas the preceding
                    // constructor calls 'AddProperty'.
    
                    InputListProperty = GetProperty(NameOfInputListProperty);
                    SplitListProperty = GetProperty(NameOfSplitListProperty);
                    ResultList1Property = GetProperty(NameOfResultList1Property);
                    ResultList2Property = GetProperty(NameOfResultList2Property);
                }
    
                protected new SplitList UtilityNode
                {
                    get { return (SplitList)base.UtilityNode(); }
                }
    
                public override UtilityNode.NodeState Clone()
                {
                    return new NodeState(this);
                }
    
                public override bool TryGetDefaultOutputProperty(out INodeProperty property)
                {
                    property = ResultList1Property;
                    return true;
                }
            }
        }
    }
    

    Aside from that the other nodes are working well. I have tried to include an error check in each so it will not cause too many issues. There are a few strange things with updates but that might be as I am re-building the dll and loading into the same file where I have existing nodes placed.

    Once I finish the split list I will upload, maybe someone will find it useful with training people with skills in that other software Slight smile

    Thanks

    Wayne

  • Hi Wayne,

    I'm glad you're successful with your new node types. Congratulations!

    Regarding your SplitList node type, I see the problem: In your NodeState's first constructor, you're missing this statement:

    ResultList1Property = AddProperty(NameOfResultList1Property);

    Sometimes it just takes a second set of eyes. Slight smile

    Jeff

    Answer Verified By: Wayne Dickerson 

  • Thanks Jeff,

    I missed that one!

    I did have another error in the loop but fixed that up and it is all working now.

    So another couple of questions.

    1. How do you make the output display on the node by default. ( rather then clicking the drop down and pinning it)

    2. How would l set the colour of the node by default. (I found a possible class, Bentley.GenerativeComponents.UtilityNodes.Specific.Color.Color that might be the place to set it, but I am just guessing)

    Thanks again.

    Wayne

  • Hi Wayne,

    1. To make an output port appear on the node by default, add ".SetIsAlwaysPinned(PropertyDirections.Output)" to the end of your call to 'AddParameterDefinition' for that property.

    Here I've added that phrase to the end of the two outputs on your SplitList node type:

    technique1.AddParameterDefinition(environment, NameOfResultList1Property, typeof(object[]), "",
        Ls.Literal("The first part of the list."), NodePortRole.TechniqueOutputOnly).SetIsAlwaysPinned(PropertyDirections.Output);

    technique1.AddParameterDefinition(environment, NameOfResultList2Property, typeof(object[]), "",
        Ls.Literal("The second part of the list."), NodePortRole.TechniqueOutputOnly).SetIsAlwaysPinned(PropertyDirections.Output);

    2. I don't recommend forcing a node's color, since the node-color facility is intended for users to color the nodes as they wish, based on the significance of those nodes in their own GC models.

    However, since you asked, here's how to force a node to be a particular color: Somewhere within the body of your class (but not within the embedded NodeState class), add this method:

    public override System.Windows.Media.Color GetNodeColor(bool enablePropertyCalculatorIfThereIsOne=true)
    {
        return Colors.Orange; // ...Or whichever color you want.
    }

    (Note: In order to use the types 'System.Windows.Media.Color' and 'Colors', your project must have a reference to the standard Framework assembly, PresentationCore. It probably already does.)

    Jeff

    Answer Verified By: Wayne Dickerson 

  • HI Jeff,

    Thanks again,

    Perfect. You are probably right. Setting the colour as a default would not be a great idea.

    I have just updated all the nodes and doing some testing. Will post a version here that people might find useful.

    Wayne