Crash when creating child nodes from and loading program - Custom API

Hi @ajp! Hi @mmi! Hi @zerom!

Thank you all for your help. I was fighting with similar problems. So I put here my summary in case it help any of you.

I was given this two examples by the UR R&D Department:

  TreeNode tree = this.programAPI.getProgramModel().getRootTreeNode(this);
  final GripperMarkerInterface[] gripperContributionInterface = {null};
      tree.traverse(new ProgramNodeVisitor() {
        @Override
        public void visit(URCapProgramNode programNode, int index, int depth) {
          if(programNode.canGetAs(GripperMarkerInterface.class)) {
            GripperMarkerInterface urCapProgramNode = programNode.getAs(GripperMarkerInterface.class);
            gripperContributionInterface[0] = urCapProgramNode;
            System.err.println("ProgramNodeVisitor.visit: " + urCapProgramNode + ", title " + ((GripperOpenProgramNodeContribution) urCapProgramNode).getTitle());
          }
        }
      });
      tree.traverse(new URCapProgramNodeInterfaceVisitor<GripperMarkerInterface>() {
        @Override
        public void visitURCapAs(GripperMarkerInterface urCapProgramNode, int index, int depth) {
          gripperContributionInterface[0] = urCapProgramNode;
          System.err.println("URCapProgramNodeInterfaceVisitor.visitURCapAs: " + urCapProgramNode + ", title " + ((GripperOpenProgramNodeContribution)urCapProgramNode).getTitle());
        }
      });
      return gripperContributionInterface[0];

I ended up using the first approach, just because I was in a hurry (and involved in several different projects). So as the first was working to perfection, I didn’t test the second option.

Regarding the overall problem, and trying to summarize and close things.
One of the difficulties was that I was having up to three different causes of problems, two of them closely related. This made even more difficult to know clearly what was the problem. The problems, solutions and comments are:

  • The need to get root TreeNode every time I need to use it.
    Problem: I was getting my base root TreeNode just once in the class constructor. I thought it was a ‘static pointer’ all the time the program model is ‘alive’. I expected getting it just this once, I would see the children even if they were added afterwards.
    Solution: Getting my root TreeNode every time I need it, using this three code lines:

    ProgramModel programModel = api.getProgramModel();
    ProgramNodeFactory nf = programModel.getProgramNodeFactory();
    TreeNode root = programModel.getRootTreeNode(this);
    

    Note: I was monitoring (System.out.println) my root object every time I was getting it throughout my URCap node class. And all the time showed the same pointer address. For example:

    root: com.ur.urcap.domain.program.TreeNodeImpl@307e3b98
    root: com.ur.urcap.domain.program.TreeNodeImpl@307e3b98
    root: com.ur.urcap.domain.program.TreeNodeImpl@307e3b98
    

    That reinforced the idea that it was enough to get it once. Which is false conclusion.

  • The need to manually manage initial children creation, to create them just when the URCap node is new.
    Problem: When a previously saved .urp is loaded, containing some of my URCap nodes, I was getting a variety of exceptions, mainly NullPointer exeptions, when trying to modify the inner structure of URCap. The nodes where shown in the program tree correctly, as they were saved. But trying to add/delete children nodes was always generation an exception.
    I quote an information from UR R&D Department: “When program is being loaded your constructor is called, and after it’s done all children are removed, and replaced with nodes loaded from urp file.” Well, I just confirmed this mechanism is not fully working, at least in my SDK simulator SW3.6.0.30512.
    Solution attempts: While confirming if this is an API bug or not, and which versions are fixed, I tried a couple of ways:

    • Using “context – nodeCreationType.New”. This is not available for html URCaps (please correct me), which is the my case. (The next I’m working on is using java swing.) For java swing URCaps I found this code lines in the main URCap node class constructor, in the Vision example:

    • Half solution: Trying to count children size. If no children, then I create the initial structure. I couldn’t, since I was getting always ‘0’ children, due to the first error, getting root TreeNode just once in the class constructor. But I ended finding a good solution:

    My solution: In this case end up using a easy management system that works fine for me. Basically, using a model variable to flag ‘initialStructureAlreadyCreated’:

    public class WeldingTechnologySeamProgramNodeContribution implements ProgramNodeContribution {
    ...
           private final String IS_NEW_NODE__KEY = "isNewNode";
           
           public WeldingTechnologySeamProgramNodeContribution(URCapAPI api, DataModel model) {
                  this.api = api;
                  this.model = model;
                  ...
                  Boolean isNewNode = model.get(IS_NEW_NODE__KEY, true);
                  if (isNewNode) {
                        createInitialSubtree();
                        model.set(IS_NEW_NODE__KEY, false);
                  }
    }
    
    TreeNode root = programModel.getRootTreeNode(this);
    

    Note/Question: I’m not sure if this is fixed in newer versions (please, update me on this). For now, I just confirmed this behavior in my SDK simulator SW3.6.0.30512.
    Note: My feeling here was as if the program tree was loaded ok, but not the pointers for each node. I also was leaded to think this because at some point, I started getting my base root TreeNode just once in the class constructor. Then I couldn’t see any children in it. It was like getting a snapshot when the constructor is called, and therefore my root exists, but children are not created yet. So crossed misdiagnosis.
    Note: A similar case I’m still checking, but I thing is solved together: I observed URCap user field data being lost when moving between different nodes and coming back to the command screen. Of course they weren’t saved. This happened too on a loaded .urp that was previously saved.

  • Right usage of traverse, and how to use it with URCap nodes.
    Problem: I couldn’t find enough examples for traverse usage, and none for using it for URCap nodes.
    Solution: Using your example code now I know how to use traverse. I also found this customAPI example that helped with the interface creation:

    • Forum: URCap Sample: URCap CustomAPI

    • GitHub: GitHub - BomMadsen/URCap-CustomAPI: URCap demonstrating adding a custom API to a child node, to configure it from a parent node

      package com.galagar.weldingtechnology.impl;
      public interface EndNodeMarkerInterface {
      }
      
      
      Prog package com.galagar.weldingtechnology.impl;
      ...
      public class WeldingTechnologySeamProgramNodeContribution implements ProgramNodeContribution {
      ...
             private TreeNode getEndProgramNode() { 
                    ProgramModel programModel = api.getProgramModel();
                    TreeNode root = programModel.getRootTreeNode(this);
                    final EndNodeMarkerInterface[] endNodeContributionInterface = {null};
                    final int[] endNodeChildIndex = new int[1];
                    endNodeChildIndex[0] = -1;
                    
                    root.traverse(new ProgramNodeVisitor() {
                          @Override
                          public void visit(URCapProgramNode programNode, int index, int depth) {
                                 System.out.println("ProgramNodeVisitor.visit: ");
                                 System.out.println("    * index: " + index);
                                 System.out.println("    * depth: " + depth);
                                 if(programNode.canGetAs(EndNodeMarkerInterface.class)) {
                                        EndNodeMarkerInterface urCapProgramNode = programNode.getAs(EndNodeMarkerInterface.class);
                                 endNodeContributionInterface[0] = urCapProgramNode;
                                 System.out.println("    * ****** EndNode urCapProgramNode: " + urCapProgramNode + ", title " + ((WeldingTechnologyEndProgramNodeContribution) urCapProgramNode).getTitle());
                                 endNodeChildIndex[0] = index;
                              }
                          }
                    });
      
      // The second option
      //            root.traverse(new URCapProgramNodeInterfaceVisitor<EndNodeMarkerInterface>() {
      //              @Override
      //              public void visitURCapAs(EndNodeMarkerInterface urCapProgramNode, int index, int depth) {
      //                    endNodeContributionInterface[0] = urCapProgramNode;
      //                    System.err.println("URCapProgramNodeInterfaceVisitor.visitURCapAs: " + urCapProgramNode + ", title " + ((WeldingTechnologyEndProgramNodeContribution) urCapProgramNode).getTitle());
      //              }
      //            });
      
      //return endNodeContributionInterface[0];
      
                    if (endNodeChildIndex[0] > -1) {
                          return root.getChildren().get(endNodeChildIndex[0]);
                    } else {
                          return null;
                    }
             }
      }
      

      Of course, many lines are just tests and System logs for debug purposes, but its working to perfection!

I’ve been polishing the code and I’m still running final tests. But so forth, it is working to perfection. So I hope it can help you too.
And UR crew, please keep us up to date with your findings or solutions in this matter.

Thank you all!
David Martin

3 Likes