Wrench value from your Creo Investments Home Measure the benefits Simple Automation can bring Why
Automate?
A personal way to do PDM Peer to Peer
PDM
3D Drawings Model Based
Design
Smooth out unwanted sharp edges Other
Articles
www.proetoolbox.co.uk - Simple Automation made Simple

Callee and Recursive Functions

Some Pro/Web.Link applications require the use of a Recursive Function to chase down the nodes of the Model Tree. Typically the application defines a global variable to hold an Array of objects which the application subsequently leverages. While the logic and result is perfectly valid, there is the messy complication of having to setup Global Variables. Global Variables can cause issues when one tries to copy and paste code from one application to another. There is an alternative method and this article explains it.

...
<script>
//Global Variables
var mGlob = null;
var oSession =null;
var WLAvailable = false;
var ActiveXAvailable = false;
var PathsArray = new Array(); 

//Initialization Commands
libCheckEnvironment();

//Current Assembly
var TopLevelAssy = oSession.CurrentModel;

//PROGRAM CODE TO EXECUTE ON PAGE LOAD HERE

//Recurse Assembly to set up PathsArray
var Ids = pfcCreate("intseq");
RecurseAssembly(TopLevelAssy,Ids);

var Out = "<TABLE>"+"<TR><TH>Level</TH><TH>Name</TH></TR>";
for (var i=0,z=PathsArray.length;i<z;i++)
{
  var CurPath = PathsArray[i];
  Out+="<TR><TD>"+
    CurPath.ComponentIds.Count+
    "</TD><TD>"+
    CurPath.Leaf.FileName+
    "</TD></TR>";
}
Out+="</TABLE>";
UI.innerHTML = Out;

//PROGRAM FUNCTIONS TO HANDLE 
function RecurseAssembly (assem,intSeq)
//examine assembly and populate 
//PathsArray array for each Component
{
  var subcomponent;
  var cmpPath = void null;
	
  if (intSeq.Count == 0)
  {
    subcomponent = assem;
  }
  else
  {
    cmpPath = pfcCreate ("MpfcAssembly").
      CreateComponentPath( assem, intSeq );
    try 
    {
      subcomponent = cmpPath.Leaf;
    }
    catch(er)
    {}
  }

  /*-----------------------*\ 
  Search for subcomponents, 
  and traverse each of them.
  \*-----------------------*/ 
  try
  {
    var components = subcomponent.
      ListFeaturesByType(true,pfcCreate ("pfcFeatureType").
          FEATTYPE_COMPONENT);
    for (var i=0;i<components.Count;i++)
    {
      //Operate on each comp sequentially
      var compFeat = components.Item (i);

      if (compFeat.Status!=0) continue;
      try
      {
        intSeq.Append (compFeat.Id);
        //Assign Path
        var CurItemCompPath = pfcCreate("MpfcAssembly").
            CreateComponentPath(TopLevelAssy, intSeq);
      }
      catch(er)
      {
        return;
      }

      PathsArray[PathsArray.length] = CurItemCompPath;

      if (compFeat.Status==pfcCreate("pfcFeatureStatus").
                             FEAT_ACTIVE)
      {
        RecurseAssembly (assem, intSeq);
      }
    }
  }
  catch(er)
  {
    return;
  }

  /*----------------------------*\ 
  Clean up the assembly ids
  at this level before returning.
  \*----------------------------*/ 
  if (intSeq.Count > 0)
  {
    intSeq.Remove (intSeq.Count - 1,intSeq.Count);
  } 
}

//Utility Functions
//===============
//Omitted for Clarity
....

The Conventional Method using Global Variables

In the example code (shown right) you can see that I've created an Array to hold Paths Information for the components of an Assembly. Get the full code for this 'typical' way here.


In order to even run the function we have to setup the following variables in the global scope:

  • PathsArray - to receive each ComponentPath that we find.
  • TopLevelAssy - because the function needs to generate a Path from the top level assembly.
  • Ids - because we need to feed the function with a starting point from a paths perspective.
  •  

    Having to setup these Global Variables is a source of danger in our applications, hence the interest in a different approach.

     

    ...
    RecurseAssembly(TopLevelAssy);
    ...
    function RecurseAssembly (assem)
    //examine the assembly and create an array
    //called Paths on the Function itself
    //that holds all component Ids
    {
      if (!RecurseAssembly.intSeq)
      {
        RecurseAssembly.intSeq = pfcCreate ("intseq");
        RecurseAssembly.assembly = assem;
      }
    
      if (!RecurseAssembly.Paths)
        RecurseAssembly.Paths = new Array();
    
      var subcomponent;
      var cmpPath = void null;
    	
      if (RecurseAssembly.intSeq.Count == 0)
      {
        subcomponent = assem;
      }
      else
      {
        cmpPath = pfcCreate ("MpfcAssembly").
          CreateComponentPath( RecurseAssembly.assembly, 
            RecurseAssembly.intSeq );
        try 
        {
          subcomponent = cmpPath.Leaf;
        }
        catch(er)
        {}
      }
    
      /*-----------------------*\ 
      Search for subcomponents
      and traverse each of them.
      \*-----------------------*/ 
      try
      {
        var components = subcomponent.ListFeaturesByType(true, 
          pfcCreate ("pfcFeatureType").FEATTYPE_COMPONENT);
        for (var i = 0; i < components.Count; i++)
        {
          //Operate on each component within the list sequentially
          var compFeat = components.Item (i);
    
          if (compFeat.Status!=0) continue;
          try
          {
            RecurseAssembly.intSeq.Append (compFeat.Id);
            //Assign Path
            var CurItemCompPath = pfcCreate("MpfcAssembly").
              CreateComponentPath(RecurseAssembly.assembly, 
              RecurseAssembly.intSeq);
          }
          catch(er)
          {
            return;
          }
    
          RecurseAssembly.Paths[RecurseAssembly.Paths.length] = 
            CurItemCompPath;
    
          if (compFeat.Status==
            pfcCreate("pfcFeatureStatus").FEAT_ACTIVE)
          {
            RecurseAssembly (assem);
          }
        }
      }
      catch(er)
      {
        return;
      }
    
      /*----------------------------*\ 
      Clean up the assembly ids
      at this level before returning.
      \*----------------------------*/ 
      if (RecurseAssembly.intSeq.Count > 0)
      {
        RecurseAssembly.intSeq.Remove (
          RecurseAssembly.intSeq.Count - 1, 
          RecurseAssembly.intSeq.Count
        );
      } 
    }
    ...
    

    Adding Variables to the Function Object Way

    We can take a step towards self-contained code by re-writing the function to reference properties of itself. In case this is making your head hurt, just remember that JavaScript considers functions as objects of the page itself. That means you can add properties to the Function just as you can add variables (properties) to the page itself.

    Now we don't have to setup variables in the Global Scope to re-use this function. However should we have a need to change the name of the function itself, we'd have to do a search and replace to change all the references to itself. Not ideal and a situation that can be addressed with the 'arguments.callee' property. Get the full code for this 'alternate' way here.

     

    ...
    function RecurseAssembly (assem)
    //examine the assembly and create an array
    //called Paths on the Function itself
    //that holds all component Ids
    {
      var thisFunction = arguments.callee;
      if (!thisFunction.intSeq)
      {
        thisFunction.intSeq = pfcCreate ("intseq");
        thisFunction.assembly = assem;
      }
    
      if (!thisFunction.Paths)
        thisFunction.Paths = new Array();
    
      var subcomponent;
      var cmpPath = void null;
    
      if (thisFunction.intSeq.Count == 0)
      {
        subcomponent = assem;
      }
      else
      {
        cmpPath = pfcCreate ("MpfcAssembly").
          CreateComponentPath( thisFunction.assembly, 
            thisFunction.intSeq );
        try 
        {
          subcomponent = cmpPath.Leaf;
        }
        catch(er)
        {}
      }
    
      /*-----------------------*\ 
      Search for subcomponents
      and traverse each of them.
      \*-----------------------*/ 
      try
      {
        var components = subcomponent.ListFeaturesByType(true, 
          pfcCreate ("pfcFeatureType").FEATTYPE_COMPONENT);
        for (var i = 0; i < components.Count; i++)
        {
          //Operate on each component within the list sequentially
          var compFeat = components.Item (i);
    
          if (compFeat.Status!=0) continue;
          try
          {
            thisFunction.intSeq.Append (compFeat.Id);
            //Assign Path
            var CurItemCompPath = pfcCreate("MpfcAssembly").
              CreateComponentPath(thisFunction.assembly, 
                thisFunction.intSeq);
          }   
          catch(er)
          {
            return;
          }
    
          thisFunction.Paths[thisFunction.Paths.length] = 
            CurItemCompPath;
    
          if (compFeat.Status==
            pfcCreate("pfcFeatureStatus").FEAT_ACTIVE)
          {
            thisFunction (assem);
          }
        }
      }
      catch(er)
      {
        return;
      }
      /*----------------------------*\ 
      Clean up the assembly ids
      at this level before returning.
      \*----------------------------*/ 
    
      if (thisFunction.intSeq.Count > 0)
      {
        thisFunction.intSeq.Remove (
          thisFunction.intSeq.Count - 1,
          thisFunction.intSeq.Count
        );
      } 
    }
    ...
    

    The Callee Property

    If the code isn't clear enough, let me point out that 'arguments.callee' is allowing us to get a reference to the function itself. This is important because it makes the name of the function irrelevant (and so you can change it as your needs dictate). Get the full code for this 'callee' way here.

    Conclusion

    Using the Callee property to add the Paths direct to the 'recurseAssy' function makes the code self-contained with no messy Global Variables to worry about. Building self-contained functions is a stepping stone to producing a flexible and re-usable code library.