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};
            }
        }
    }
    

  • 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?

  • "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?

Reply
  • 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?

Children
  • 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.

  • It sounds like you've got it. But, something you should be aware of:

    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.

    In newer versions of GC, global script functions are no longer defined within transactions. Instead, they're defined and maintained within the Functions panel, with no relationship to which transactions have been played or not played. (Well, except that, if you revise the definition of a script function that's currently referenced by one or more nodes, those nodes will be updated automatically.)

    If you were to load the transaction list shown at "the" into the latest version of GC, GC would automatically remove transaction 1's "script" block, and move that function, BasicLine, into a separate storage location where script functions reside.