Define output for a function

Hi All,

Just wondering if anyone has experience in defining additional outputs in a function.

For example I created a simple polygon array and used a polygon by function to perform an offset to those initial Polygons. During the function I created an array of points at the centroid of each polygon that I used as the point input in the offset. The result is a polygon. I would however like to also have an output from the node the array of points that were created along the way so they could be reused for anther operation.

Hopefully that makes sense. Below is the function I have, the return as you can see is the Polygon arrary MyPolyArray, I would also like to output the point array MyPointArray.

function (Polygon InputPoly, double ODistance)
{
Polygon MyPolyArray = {};
Point MyPointArray ={};
for (int i = 0; i < InputPoly.Count; ++i)
{   
    Polygon MypolyList = {};
    Point MyPointList = {};
    for (int j = 0; j < InputPoly[i].Count; ++j)
    {
    Polygon MyPoly = {};
    Point MyPoint = {};
    MyPoint = new Point().CentroidOfSet(InputPoly[i][j].Vertices);
    MyPoly = new Polygon().Offset(InputPoly[i][j], OffsetMethod.ByDistance,MyPoint, ODistance);
    MyPointList.Add(MyPoint);
    MypolyList.Add(MyPoly);   
    }
 MyPointArray.Add(MyPointList);
 MyPolyArray.Add(MypolyList);  
    
}

return MyPolyArray;
}

Below is the simple dgn file

Thanks
Wayne

Function-Output.dgn

Parents
  • For whatever reason, I'm not able to open that DGN file. Please re-submit it as a GCT (GC Transaction) file.
     
    If you don't know how to create a GCT file from a DGN file, here's how:
    1. Open the DGN file.
    2. From GC's 'Transactions' dialog, select the rightmost toolbar command: Edit this entire transaction list.
    3. Within the Editor, select the menu command: File -> Save Current Document to Transaction (GCT) File.
    4. A standard file-save dialog will appear. Navigate to the location to which you want to save the GCT file, give the file an appropriate name, and click Save.
    5. Finally, post that resultant GCT file to this forum thread.
    Also, I have no idea whether your model is large and complex, but, if it is: It would be very helpful if you could (firstly) reduce your model to the smallest essence that still demonstrates the problem.
    Thanks!
    Jeff

  • Hi Jeff,

    Apologies I should have sent the gct file originally. It is a very small example file.

    I just changed it to a txt format as the forum didn't like the gct extension.

    Thanks

    Wayne

    // Bentley GenerativeComponents Transaction File -- File structure version 1.41. (Please do not delete or change this line.)
    
    environment
    {
        GCVersion                 = '10.05.00.77';
        MSVersion                 = '10.12.00.41';
        MSProject                 = '';
        MSDesignFile              = 'C:\Users\wayne.dickerson\Desktop\function-output.dgn';
        MSMasterUnit              = {Meter, 'mm', Metric, 1.0, 1000.0};
        MSSubUnit                 = {Meter, 'mm', Metric, 1.0, 1000.0};
        MSStorageUnit             = {Meter, '', Metric, 1.0, 1000.0};
        MSUorsPerStorageUnit      = 1000.0;
    }
    
    transaction 1 stateChange 'Add function MyFunction'
    {
        script
        {
            global redeclare object MyFunction(Polygon InputPoly, double ODistance)
            {
            Polygon MyPolyArray = {};
            Point MyPointArray ={};
            for (int i = 0; i < InputPoly.Count; ++i)
            {   
                Polygon MypolyList = {};
                Point MyPointList = {};
                for (int j = 0; j < InputPoly[i].Count; ++j)
                {
                Polygon MyPoly = {};
                Point MyPoint = {};
                MyPoint = new Point().CentroidOfSet(InputPoly[i][j].Vertices);
                MyPoly = new Polygon().Offset(InputPoly[i][j], OffsetMethod.ByDistance,MyPoint, ODistance);
                MyPointList.Add(MyPoint);
                MypolyList.Add(MyPoly);   
                }
             MyPointArray.Add(MyPointList);
             MyPolyArray.Add(MypolyList);  
            
            }
            
            //return MyPolyArray;
            return{polys=MyPolyArray,points=MyPointArray};
            }
        }
    }
    
    transaction 2 stateChange 'Add baseCS'
    {
        gcModel
        {
            node User.Objects.baseCS Bentley.GC.NodeTypes.CoordinateSystem
            {
                Technique                 = 'AtDGNModelOrigin';
                DGNModelName              = 'Design Model';
                SymbolSize                = 1;
                GraphLocation             = <auto> {40.0, 40.0};
            }
        }
    }
    
    transaction 3 stateChange 'Add point1'
    {
        gcModel
        {
            node User.Objects.point1 Bentley.GC.NodeTypes.Point
            {
                Technique                 = 'ByCartesianCoordinates';
                CoordinateSystem          = baseCS;
                XTranslation              = Series(0,30000,1500);
                YTranslation              = 0;
                ZTranslation              = Series(0,30000,3000);
                Replication               = ReplicationOption.AllCombinations;
                GraphLocation             = <auto> {314.0, 40.0};
            }
        }
    }
    
    transaction 4 stateChange 'Add polygon1, polygon2'
    {
        gcModel
        {
            node User.Objects.polygon1 Bentley.GC.NodeTypes.Polygon
            {
                Technique                 = 'ByPointGrid';
                Points                    = point1;
                FacetOption               = FacetOption.Quads;
                PlaneIndex                = 1;
                UClosed                   = false;
                VClosed                   = false;
                GraphLocation             = {603.279, -87.643, 0.0, 115.76};
            }
            node User.Objects.polygon2 Bentley.GC.NodeTypes.Polygon
            {
                Technique                 = 'ByFunction';
                Function                  = function (Polygon InputPoly, double ODistance)
                                            {
                                            Polygon MyPolyArray = {};
                                            Point MyPointArray ={};
                                            for (int i = 0; i < InputPoly.Count; ++i)
                                            {   
                                                Polygon MypolyList = {};
                                                Point MyPointList = {};
                                                for (int j = 0; j < InputPoly[i].Count; ++j)
                                                {
                                                Polygon MyPoly = {};
                                                Point MyPoint = {};
                                                MyPoint = new Point().CentroidOfSet(InputPoly[i][j].Vertices);
                                                MyPoly = new Polygon().Offset(InputPoly[i][j], OffsetMethod.ByDistance,MyPoint, ODistance);
                                                MyPointList.Add(MyPoint);
                                                MypolyList.Add(MyPoly);   
                                                }
                                             MyPointArray.Add(MyPointList);
                                             MyPolyArray.Add(MypolyList);  
                                                
                                            }
                                             
                                            return MyPolyArray;
                                            };
                DebuggerTrigger           = DebuggerTriggerLevel.Breakpoints;
                InputPoly                 = polygon1;
                ODistance                 = 200;
                GraphLocation             = {1049.101, -31.987, 0.0, 141.393};
            }
        }
    }
    
    transaction 5 stateChange 'Add functionCall1, functionCall2, SpecialFunctionCall'
    {
        gcModel
        {
            node User.Objects.functionCall1 Bentley.GC.NodeTypes.FunctionCall
            {
                Technique                 = 'Default';
                Function                  = function (Polygon InputPoly, double ODistance)
                                            {
                                            Polygon MyPolyArray = {};
                                            Point MyPointArray ={};
                                            for (int i = 0; i < InputPoly.Count; ++i)
                                            { 
                                                Polygon MypolyList = {};
                                                Point MyPointList = {};
                                                for (int j = 0; j < InputPoly[i].Count; ++j)
                                                {
                                                Polygon MyPoly = {};
                                                Point MyPoint = {};
                                                MyPoint = new Point().CentroidOfSet(InputPoly[i][j].Vertices);
                                                MyPoly = new Polygon().Offset(InputPoly[i][j], OffsetMethod.ByDistance,MyPoint, ODistance);
                                                MyPointList.Add(MyPoint);
                                                MypolyList.Add(MyPoly);   
                                                }
                                             MyPointArray.Add(MyPointList);
                                             MyPolyArray.Add(MypolyList);  
                                                
                                            }
                                             
                                            return MyPolyArray;
                                             
                                            };
                InputPoly                 = polygon1;
                ODistance                 = 200;
                GraphLocation             = {837.525, -173.347, 0.0, 141.577};
            }
            node User.Objects.functionCall2 Bentley.GC.NodeTypes.FunctionCall
            {
                Technique                 = 'Default';
                Function                  = MyFunction;
                InputPoly                 = polygon1;
                ODistance                 = 200;
                GraphLocation             = {885.108, 61.114, 174.0, 141.577};
            }
            node User.Objects.SpecialFunctionCall Bentley.GC.NodeTypes.FunctionCall
            {
                Technique                 = 'Default';
                Function                  = MyFunction;
                InputPoly                 = polygon1;
                ODistance                 = 200;
                GraphLocation             = {395.074, 269.335, 174.0, 141.577};
            }
        }
    }
    
    transaction 6 stateChange 'Add polygon3'
    {
        gcModel
        {
            node User.Objects.polygon3 Bentley.GC.NodeTypes.Polygon
            {
                Technique                 = 'ByFunction';
                Function                  = function ()
                                            {
                                            Polygon MyPoly = SpecialFunctionCall.Value.polys;
                                            return MyPoly;
                                            };
                DebuggerTrigger           = DebuggerTriggerLevel.Breakpoints;
                GraphLocation             = {632.848, 269.265, 174.0, 128.577};
            }
        }
    }
    
    transaction 7 stateChange 'Add polygon4'
    {
        gcModel
        {
            node User.Objects.polygon4 Bentley.GC.NodeTypes.Polygon
            {
                Technique                 = 'ByFunction';
                Function                  = function (Polygon InputPoly, double InputDistance)
                                            {
                                            record result = MyFunction(InputPoly, InputDistance);
                                            Polygon MyPoly = result.polys;
                                            return MyPoly;
                                            };
                DebuggerTrigger           = DebuggerTriggerLevel.Breakpoints;
                InputPoly                 = polygon1;
                InputDistance             = 200;
                GraphLocation             = {-257.082, 192.291};
            }
        }
    }
    

  • Sorry for the extremely-long-delayed response. I discovered what the issue is.

    Firstly, let's look "under the hood" at what happens when a ByFunction's script function is called. (It doesn't matter if the Function property contains a local script function or the name of a global script function.) The ByFunction mechanism stores the current node (i.e., the node that's currently being updated via its ByFunction technique) into a special location named 'this'. You can access 'this' directly in your code to refer to that node; however, even if you don't refer to 'this' explicitly, it still plays an important role.

    Specifically: Whenever you create a new node within a script function, and the value of 'this' is non-null, that node is created in such a way that it can become a child of whichever node 'this' refers to. (Technically, it doesn't actually become a child at the moment it's created; it's parented to the 'this' node after the script function returns, as part of the 'this' node's ByFunction mechanism.)

    In other words: If a node that's created within a script function is intended to become a child of some parent node, 'this' must refer to that parent node at the time the child node is created.

    So, getting back to your example GCT file…

    -- The nodes 'polygon2' and 'polygon4' work fine, because their ByFunction techniques run script code that creates child nodes in a context where 'this' refers to the appropriate parent node ('polygon2' or 'polygon4', respectively).

    -- But 'polygon3' refers to the value of a FunctionCall node ('SpecialFunctionCall') that's already been updated. So, 'polygon3' is getting the result of having called the global script function 'MyFunction' at a previous time, in a previous context, when 'this' was null.

    (In general, there's no harm in creating a new node while 'this' is null. Such a node can serve perfectly well as a temporary node. However, it can't be parented to any other node.)

    The workaround, as you know, is to call 'MyFunction' directly, like 'polygon4' is doing. I know you don't like that, because of the performance. We (the GC team) are well aware that performance is often an issue. It's something we're always striving to improve, at least incrementally.

    I hope I've explained this well enough.

    Jeff

  • Hi Jeff,

    Thank you for the response. I must say I had to have a look through the code to get back into what the issues were.

    I think I understand the issues with polygon3 calling a state where it was calling a function at a different state.

    So to try and break down what I think the original intent was, which was to find a way to return more than just one result from a functioncall I thought I would do a bit more tinkering.

    I removed the global script function so there was no confusion and just used a functioncall node.

    Looking at the advice you gave in the discussion I could create a function that (update7) shows the results in the node. Excellent!

    Not sure if this was the case in the previous versions.

    This could be a complex script that need only be run once to get multiple outputs.

    So then onto how to use this.

    I attempted to use a byfunction method say for the points but for some reason I still can’t get that one to work.

    So I tried a different approach and just placed a new point node and utilized the XYZ information from the functioncall value to create a series of points.

    For the polygon output I just used the copy transform geometric contents on the Polygons and they worked as well.

    That would work for most situations I think. WIN WIN!

    To close out this topic, just wondering if there is a better way to create the polygon without doing the copy transform.

    Thinking about the way the polygon 2 works from copy transform from the MyFunctionCall I had expected it to work with a polygon ByFunction but that maybe what you were referring to with the state of the functioncall node.

    I thought I might get past this by utilizing the MyFunctionCall.Value.polys as this would be referring to the results of the functioncall node (this worked with the polygon by CopyTransformGeometricContents) but I had no luck with the ByFunction.

    I just wanted to confirm the best method as the ByFunction would be a simple approach.

    Hope that makes sense.

    Thanks

    Wayne

    Here is an update 7 script

    transaction 1 stateChange 'Add baseCS'
    {
        gcModel
        {
            node User.Objects.baseCS Bentley.GC.NodeTypes.CoordinateSystem
            {
                Technique                 = 'AtDGNModelOrigin';
                DGNModelName              = 'Design Model';
                SymbolSize                = 1;
                GraphLocation             = <auto> {40.0, 40.0};
            }
        }
    }
    
    transaction 2 stateChange 'Add point1'
    {
        gcModel
        {
            node User.Objects.point1 Bentley.GC.NodeTypes.Point
            {
                Technique                 = 'ByCartesianCoordinates';
                CoordinateSystem          = baseCS;
                XTranslation              = Series(0,30000,1500);
                YTranslation              = 0;
                ZTranslation              = Series(0,30000,3000);
                Replication               = ReplicationOption.AllCombinations;
                GraphLocation             = <auto> {314.0, 40.0};
            }
        }
    }
    
    transaction 3 stateChange 'Add polygon1'
    {
        gcModel
        {
            node User.Objects.polygon1 Bentley.GC.NodeTypes.Polygon
            {
                Technique                 = 'ByPointGrid';
                Points                    = point1;
                FacetOption               = FacetOption.Quads;
                PlaneIndex                = 1;
                UClosed                   = false;
                VClosed                   = false;
                GraphLocation             = {567.652, 37.13};
            }
        }
    }
    
    transaction 4 stateChange 'Add MyFunctionCall'
    {
        gcModel
        {
            node User.Objects.MyFunctionCall Bentley.GC.NodeTypes.FunctionCall
            {
                Technique                 = 'Default';
                Function                  = function (Polygon InputPoly, double ODistance)
                                                    {
                                                    Polygon MyPolyArray = {};
                                                    Point MyPointArray ={};
                                                    for (int i = 0; i < InputPoly.Count; ++i)
                                                    {   
                                                        Polygon MypolyList = {};
                                                        Point MyPointList = {};
                                                        for (int j = 0; j < InputPoly[i].Count; ++j)
                                                        {
                                                        Polygon MyPoly = {};
                                                        Point MyPoint = {};
                                                        MyPoint = new Point().CentroidOfSet(InputPoly[i][j].Vertices);
                                                        MyPoly = new Polygon().Offset(InputPoly[i][j], OffsetMethod.ByDistance,MyPoint, ODistance);
                                                        MyPointList.Add(MyPoint);
                                                        MypolyList.Add(MyPoly);   
                                                        }
                                                     MyPointArray.Add(MyPointList);
                                                     MyPolyArray.Add(MypolyList);  
                                                    
                                                    }
                                                    
                                                    //return MyPolyArray;
                                                    return{polys=MyPolyArray,points=MyPointArray};
                                                    };
                InputPoly                 = polygon1;
                ODistance                 = 300;
                GraphLocation             = {825.035, 38.831, 215.28, 403.21};
            }
        }
    }
    
    transaction 5 stateChange 'Add point3'
    {
        gcModel
        {
            node User.Objects.point3 Bentley.GC.NodeTypes.Point
            {
                Technique                 = 'ByCoordinateList';
                CoordinateSystem          = baseCS;
                XYZTranslation            = MyFunctionCall.Value.points.XYZ;
                GraphLocation             = {1422.155, 55.151};
            }
        }
    }
    
    transaction 6 stateChange 'Add polygon2'
    {
        gcModel
        {
            node User.Objects.polygon2 Bentley.GC.NodeTypes.Polygon
            {
                Technique                 = 'CopyTransformGeometricContents';
                NodeToCopy                = MyFunctionCall.Value.polys;
                CopyFrom                  = baseCS;
                CopyTo                    = baseCS;
                ReverseDirection          = false;
                GraphLocation             = {1424.075, 219.311, 0.0, 118.33};
            }
        }
    }
    
    transaction 7 stateChange 'Add point2'
    {
        gcModel
        {
            node User.Objects.point2 Bentley.GC.NodeTypes.Point
            {
                Technique                 = 'ByFunction';
                Function                  = function()
                                            {
                                            Polygon MyPoint = MyFunctionCall.Value.points;
                                            return MyPoint;
                                            };
                DebuggerTrigger           = DebuggerTriggerLevel.Breakpoints;
                GraphLocation             = {1129.355, 214.511, 0.0, 156.43};
            }
        }
    }
    
    transaction 8 stateChange 'Add polygon3'
    {
        gcModel
        {
            node User.Objects.polygon3 Bentley.GC.NodeTypes.Polygon
            {
                Technique                 = 'ByFunction';
                Function                  = function()
                                            {
                                            Polygon MyPoly = MyFunctionCall.Value.polys;
                                            return MyPoly;
                                            };
                DebuggerTrigger           = DebuggerTriggerLevel.Breakpoints;
                GraphLocation             = {1126.475, 57.071};
            }
        }
    }
    

  • Hi Wayne,

    (I'm referring to the version of your transaction list that you included in your previous post.)

    To review, the issue is:

    1. MyFunctionCall is updated -- that is, its function is called, and the results of that call are stored into MyFunctionCall's output -- before polygon2 and polygon3 are updated. So, polygon2 and polygon3 have no control over how MyFunctionCall's nodes were created; those nodes have already been created.

    2. Because those nodes were created outside the context of either polygon2 or polygon3, they cannot serve as child nodes of either Polygon.

    However, your use of CopyTransformGeometricContents is a clever way to circumvent the issue! CopyTransformGeometricContents doesn't care how its 'NodeToCopy' nodes were created; it always generates a new set of child nodes that are specific to polygon2.

    However, polygon3 is using ByFunction, and we can see that its Function is not doing anything that would circumvent the issue:

    function()
    {
    Polygon MyPoly = MyFunctionCall.Value.polys;
    return MyPoly;
    };

    The only way to achieve the same result within polygon3 would be to revise its Function so that it calls the CopyTransformGeometricContents technique, though that seems a bit silly. Slight smile

    function()
    {
    Polygon MyPoly = MyFunctionCall.Value.polys;
    this.CopyTransformGeometricContents(MyPoly, baseCS, baseCS);
    }

    HTH

    Jeff

    Answer Verified By: Wayne Dickerson 

  • Hi Jeff,

    Thank you for the update. 

    I was probably a little too happy when I found a way to work around the process. With your explanation however I have a better understanding of how the system works which is always a good thing.

    Thanks again for taking the time to run though it.

    Thanks

    Wayne 

  • though that seems a bit silly. 

    Yea.. it does seem a bit odd.

    Being finicky here but maybe the Help section should be updated a little bit.

    Global Functions: "GCScript global functions can be used in any script context."

    "But 'polygon3' refers to the value of a FunctionCall node ('SpecialFunctionCall') that's already been updated. So, 'polygon3' is getting the result of having called the global script function 'MyFunction' at a previous time, in a previous context, when 'this' was null."

    OK, this seems to make sense. OTOH, what's the point of having these global functions if they are almost always 'out of context'?

    Writing our own global functions "This creates a new global function that's just like the built-in functions. It's not associated with the current graph, and (therefore) it persists through the entire session, regardless of opening/creating transaction files."

    ...unless combined with CopyTransformGeometricContents? Makes you wonder if this technique or a more generalised version of the copy technique should be built in as a switch?

    Using a Global Script Function:"The Function property can specify the name of global function, or an "in-place" anonymous function. Here, a global script function is defined. This creates an identical line as the above examples. By using a global script function to create the line, the user is able to call that same function many times to create other lines using different arguments."

    "Starting with the keywords 'global redeclare' and following with the desired function, a global function is made meaning it can be accessed at anytime by simply calling the function name and specifying the arguments."

    Or should GC's Dependency Analyzer snoop deeper into the functions or the global function node(s) appear in the arguments list as inputs?

    FunctionArguments: "The analyzer does not look into the body of the function, itself.

    Therefore, if a function references other top-level nodes, you should not simply use those node names directly in their function body. If this is done, the dependency analyzer won't 'see' those names, and won't make any dynamic links to those nodes."

    OK, the 'global' function node could then be initialised like a local node and tie up memory. This is where something like the CopyTransformGeometricContents switch would be helpful? Null the contents when the function finishes and returns 'success'?

    Hmmm.. are Global Function nodes dependent on the transaction sequence? Or does the GC DA swoop in and initialise the Global nodes when the file is opened?

    Should Global Function Nodes be 'dynamically linked' like Scripted Transactions?

    "Normally, when an expression is passed as an argument to a node technique, the expression itself is passed along with its resultant value. "Both the expression and its value are recorded into the corresponding node property. Subsequently, GC's dependency analyzer will examine the expression, and will create dynamic links based on whichever node names appear in the expression

    "In the following example, the raisedPoint will update only when point01 moves in the Y Direction, not when it moves in the X or Z directions."

    So if I understand correctly, a when a Point node is changed, then the changed values are passed to the Scripted Node. The Point node is not a child Node of the Scripted Node. This seems to differ when using Global Nodes. Are the Point nodes given special treatment by the GC DA?

    Might be good to have a table listing when 'dynamic links' are provided between different nodes, methods / techniques etc?

Reply
  • though that seems a bit silly. 

    Yea.. it does seem a bit odd.

    Being finicky here but maybe the Help section should be updated a little bit.

    Global Functions: "GCScript global functions can be used in any script context."

    "But 'polygon3' refers to the value of a FunctionCall node ('SpecialFunctionCall') that's already been updated. So, 'polygon3' is getting the result of having called the global script function 'MyFunction' at a previous time, in a previous context, when 'this' was null."

    OK, this seems to make sense. OTOH, what's the point of having these global functions if they are almost always 'out of context'?

    Writing our own global functions "This creates a new global function that's just like the built-in functions. It's not associated with the current graph, and (therefore) it persists through the entire session, regardless of opening/creating transaction files."

    ...unless combined with CopyTransformGeometricContents? Makes you wonder if this technique or a more generalised version of the copy technique should be built in as a switch?

    Using a Global Script Function:"The Function property can specify the name of global function, or an "in-place" anonymous function. Here, a global script function is defined. This creates an identical line as the above examples. By using a global script function to create the line, the user is able to call that same function many times to create other lines using different arguments."

    "Starting with the keywords 'global redeclare' and following with the desired function, a global function is made meaning it can be accessed at anytime by simply calling the function name and specifying the arguments."

    Or should GC's Dependency Analyzer snoop deeper into the functions or the global function node(s) appear in the arguments list as inputs?

    FunctionArguments: "The analyzer does not look into the body of the function, itself.

    Therefore, if a function references other top-level nodes, you should not simply use those node names directly in their function body. If this is done, the dependency analyzer won't 'see' those names, and won't make any dynamic links to those nodes."

    OK, the 'global' function node could then be initialised like a local node and tie up memory. This is where something like the CopyTransformGeometricContents switch would be helpful? Null the contents when the function finishes and returns 'success'?

    Hmmm.. are Global Function nodes dependent on the transaction sequence? Or does the GC DA swoop in and initialise the Global nodes when the file is opened?

    Should Global Function Nodes be 'dynamically linked' like Scripted Transactions?

    "Normally, when an expression is passed as an argument to a node technique, the expression itself is passed along with its resultant value. "Both the expression and its value are recorded into the corresponding node property. Subsequently, GC's dependency analyzer will examine the expression, and will create dynamic links based on whichever node names appear in the expression

    "In the following example, the raisedPoint will update only when point01 moves in the Y Direction, not when it moves in the X or Z directions."

    So if I understand correctly, a when a Point node is changed, then the changed values are passed to the Scripted Node. The Point node is not a child Node of the Scripted Node. This seems to differ when using Global Nodes. Are the Point nodes given special treatment by the GC DA?

    Might be good to have a table listing when 'dynamic links' are provided between different nodes, methods / techniques etc?

Children
  • "But 'polygon3' refers to the value of a FunctionCall node ('SpecialFunctionCall') that's already been updated. So, 'polygon3' is getting the result of having called the global script function 'MyFunction' at a previous time, in a previous context, when 'this' was null."

    OK, this seems to make sense. OTOH, what's the point of having these global functions if they are almost always 'out of context'?

    A global function is "out of context" when it's called from within a FunctionCall node whose result is then later accessed by another node's ByFunction technique. However, if that same global function is assigned directly to a ByFunction technique's Function input, then that global function will be called in context (that is, within the same context that its result will be used).

    So, the out-of-context issue isn't with global functions, per se, but in using the result of a global function that was previously called under different circumstances.

    Writing our own global functions "This creates a new global function that's just like the built-in functions. It's not associated with the current graph, and (therefore) it persists through the entire session, regardless of opening/creating transaction files."

    ...unless combined with CopyTransformGeometricContents? Makes you wonder if this technique or a more generalised version of the copy technique should be built in as a switch?

    It sounds like you're assuming that every global function is intended to serve as a child-node generator for another node. However, a global function can be used for any purpose, such a performing a complex calculation, or processing data from an XML file.

    GC makes no attempt to "guess" what the user's intentions are. If an occurrence of CopyTransformGeometricContents is warranted, then the user needs to include that explicitly.

    FunctionArguments: "The analyzer does not look into the body of the function, itself.

    Actually, that's no longer true. If a global function refers to nodes in the GC model (that are not passed as arguments), the DA will establish dependency relationships between those nodes and the global function.

    However, nevertheless, including explicit nodes in a global function is not recommended, since it limits the utility of the function. If, instead, all nodes are passed as arguments, then the caller has a free choice as to which nodes the function will apply to. The same function can be called in different contexts with different nodes.

    Hmmm.. are Global Function nodes dependent on the transaction sequence? Or does the GC DA swoop in and initialise the Global nodes when the file is opened?

    Actually, global functions are now stored separately from the transaction list.

    In any case, the DA reanalyzes the relationships among a set of nodes whenever anything changes within those nodes that would impact those relationships. That reanalysis includes, if warranted, considering nodes that are specified within global function definitions.

    So if I understand correctly, a when a Point node is changed, then the changed values are passed to the Scripted Node. The Point node is not a child Node of the Scripted Node. This seems to differ when using Global Nodes. Are the Point nodes given special treatment by the GC DA?

    Might be good to have a table listing when 'dynamic links' are provided between different nodes, methods / techniques etc?

    I'm not sure what you mean by "Scripted Node", but, in any case...

    All techniques of all nodes store their input expressions along with their input values. Therefore, the DA can create dependency relationships in the same consistent manner between any nodes. There's nothing special about Point nodes in this regard.

  • I'm not sure what you mean by "Scripted Node", but, in any case...

    Apologies, I was refering to the top-level Scripted node here.

    It sounds like you're assuming that every global function is intended to serve as a child-node generator for another node. However, a global function can be used for any purpose, such a performing a complex calculation, or processing data from an XML file.

    No, I think that the expectation is that the Global Function is kind of like a neighbourhood 'car wash' that you set up when you know your Nodes will need to repeat a certain kind of processing. Once set up, there would be no need to initialise etc. Your other nodes pass it stuff and it 'washes' the arguments through and returns the results.

    Kind of like of like the way some of the pre-defined or Script functions in GC are used. Of course, you are going to say that these are all constructed by the overarching node that contain them :-)

    However, if that same global function is assigned directly to a ByFunction technique's Function input, then that global function will be called in context (that is, within the same context that its result will be used).

    How would you directly assign a Global Function to a ByFunction technique's Function input? Bearing in mind multiple nodes could be calling the Global Function. Sounds like there is an alternative to the "CopyTransformGeometricContents" hack?

  • Why set up a carwash and not allow GC to generate one on demand? I suppose it is to keep the mayor happy as he can control how many car washes he has to deal with and budget for them?

  • Ah, now I'm starting to think we're using the same terminology to refer to two different things.

    When I say "global function", I'm referring to an encapsulation of functionality that has a name and can be invoked (called) from any number of contexts any number of times, with each call being able to specify different parameters.

    A global function could be a built-in function, such as "Sqrt" (square root), or a function the user writes, themself, in script code. In the latter case, the user creates and manages their function using GC's Functions panel. (To use your analogy, this is where the user creates their own carwashes.)

    (Additionally, GC's Packager panel allows users to export and import their script functions between themselves and other users.)

    Regardless of how it's been created, a global function isn't intrinsically involved in the GC model/graph. It doesn't appear as a node. It's relevant to the GC model only as much as it's referenced by one or more nodes.

    There are multiple ways that a node can reference a global function, but the two most common are:

    • FunctionCall node: If a global function is assigned (by name) to a FunctionCall node's Function input, then, whenever that node is updated, that global function will be called.
    • Any geometry-type node, using its ByFunction technique: If a global function is assigned (by name) to the node's Function input, then, whenever that node is updated, that global function will be called.

    The consistent rule is that a global function is called when, and only when, a node referencing that function is updated.

    Does that clarify anything?

  • Thanks for taking the time, Jeff. Much appreciated.

    I will need to take a step back and review.

    If I understand correctly, global functions should ideally be generated as Scripted Functions. The carwashes would be created in the Functions Panel and not part of the Node graph. No need to think about parent - child relationships and what is in context or not when they are generated this way. These global functions can be always be called from any Node, using the means you mentioned, but only updates when the Node is updated and calls the 'carwash' global function causing it to process and return its arguments.

    OTOH, the Global Script Functions that uses the 'global redeclare' keyword can be part of a transaction, therefore a Node and subject to the parent:child / context rules. When the GSF is called in a Node, it just passes whatever it has ('this'?) without doing any processing because it will always be upstream of the calling Node and is not required to update.