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

Hello all,

I’m experiencing a couple of strange behaviors when implementing child program nodes from a parent one and when loading previously saved programs.
What I want to implement is a parent program node, from which I can add or remove specific child nodes thanks to dedicated check boxes. The check box is selected, a specific child node is added; the check box is not selected, this child node is removed. Ideally, I would like to control where the child nodes are located in the hierarchy, preserving the coherence of the hierarchy when one specific child node is removed.
I actually implemented this mechanism successfully but, as soon as I’m saving the program and loading it again, it crashes, showing me that I am developing it in a bad way.

First test
I have developed a first URCap, implementing a parent program node including a child node, which is a standard “Wait” node. The parent program node includes 2 check boxes, which are in this test not doing much.

image

The node hierarchy is created through the program node contribution:

TreeNode root = this.apiProvider.getProgramAPI().getProgramModel().getRootTreeNode(this);
WaitNode waitNode = this.programNodeFactory.createWaitNode();
Time oneSecondWait = this.programAPI.getValueFactoryProvider().getSimpleValueFactory().createTime(1, Time.Unit.S);
TimeWaitNodeConfig config = waitNode.getConfigFactory().createTimeConfig(oneSecondWait, ErrorHandler.AUTO_CORRECT);
this.programAPI.getProgramModel().getRootTreeNode(this).addChild(waitNode.setConfig(config));

When clicking on one of the check boxes, the data model is modified (within the context implemented by the UndoRedoManager) and the following line is executed (and only this one):

TreeNode root = this.apiProvider.getProgramAPI().getProgramModel().getRootTreeNode(this);
When starting the program from scratch, no problem. When saving the program and loading it again, when interacting when one of the check boxes, a new “Wait” node appears. I don’t understand this behavior. How come this new node appears?

image

Strangely enough, if I’m saving the program with one of the check boxes selected and loading it again, the check box is of course selected, and the additional node appears anyway if I am deselecting it. What is the reason behind that?

Second test
In the second test, the child node A is a standard “Wait” node with a 2s duration and the child node B is a standard “Wait” node with a 3s duration. Taking into account the different samples, the node hierarchy is created within the constructor. When the user is clicking on one of the check boxes, the sub tree is cleared (i just copied/pasted the clearSubtree() method from the samples) and the function creating the hierarchy is executed again.

public void setAddChildAState(final boolean state)
{
  this.apiProvider.getProgramAPI().getUndoRedoManager().recordChanges(new UndoableChanges()
  {
    @Override
    public void executeChanges()
    {
      model.setAddChildAState(state);
      // clear subtree
      clearSubtree();
      // create sub tree again
      createSubtree();
    }
  });
}

The node hierarchy is created as in the first test, adding child nodes relatively to data model values.
Here, no problem at all. I have the same behavior whether I am starting the program from scratch or I am loading a previously saved program. I’m clearing the whole sub-tree, not taking into account additional nodes eventually added by the user, but I guess I can solve that with a CustomAPI and the ProgramNodeVisitor.

Third and last test
Now, instead of standard “Wait” nodes, I want to add nodes of my own. For the sake of testing from something, which is initially working, I actually added to my URCap the GripperOpenNode from the “Pick and Place” sample. It is the same code as before, but instead of initializing my node hierarchy with a standard “Wait” node, the one with the 1s duration, I am initializing it with the GripperOpenNode.

TreeNode root = this.apiProvider.getProgramAPI().getProgramModel().getRootTreeNode(this);

WaitNode waitNode2 = this.programNodeFactory.createWaitNode();
Time oneSecondWait2 = this.programAPI.getValueFactoryProvider().getSimpleValueFactory().createTime(2, Time.Unit.S);
TimeWaitNodeConfig config2 = waitNode2.getConfigFactory().createTimeConfig(oneSecondWait2, ErrorHandler.AUTO_CORRECT);

WaitNode waitNode3 = this.programNodeFactory.createWaitNode();
Time oneSecondWait3 = this.programAPI.getValueFactoryProvider().getSimpleValueFactory().createTime(3, Time.Unit.S);
TimeWaitNodeConfig config3 = waitNode3.getConfigFactory().createTimeConfig(oneSecondWait3, ErrorHandler.AUTO_CORRECT);

root.addChild(this.programNodeFactory.createURCapProgramNode(GripperOpenProgramNodeService.class));

if (this.model.getAddChildAState())
  this.programAPI.getProgramModel().getRootTreeNode(this).addChild(waitNode2.setConfig(config2));

if (this.model.getAddChildBState())
  this.programAPI.getProgramModel().getRootTreeNode(this).addChild(waitNode3.setConfig(config3));

Once again, starting the program from scratch, everything is going well. When loading the program and beginning to interact with the check boxes, a crash occurs. I don’t manage to know why it is happening. I tried to catch this exception without success for now.

If I’m saving the program with one of the check boxes selected, the program crashes as soon as I am clicking on the “Parent” node.
1 remark here. If I’m executing the URCap from the first test, not implementing an initial “Wait” node with a 1s duration but with a GripperOpenNode, the same crash as the third test occurs.

I am of course developing it in a wrong way but where is the mistake? I tried as well to implement it thanks to TreeNodes and playing with the insertChildBefore() and insertChildAfter() methods - ideally, that’s how I would like to implement it - but the same bug occurs. I am working with the sdk 1.5.0. I don’t want to work with templates, as in the “Pick and place” sample, as the overall goal is to have more than 2 check boxes / child nodes. It will quickly be non-maintainable.

I implemented that the 3 tests as URCaps that can be compiled and installed, if anyone is interested to get the whole implementation.

com.dti.test1.tar.gz (48.8 KB)
com.dti.test2.tar.gz (48.8 KB)
com.dti.test3.tar.gz (59.1 KB)

Any fresh ideas/fixes/new tests appreciated. Thank you very much in advance!

2 Likes

Hi @fpi1, did you manage to resolve this issue/figure out what was going on?

Hi @ajp
Never receive an answer from it and was compelled to implement what I wanted in another way. The issue can be closed, even if it is not solved, if it is what you mean.

Hi @fpi1, apologies that you didn’t get an answer from us. Someone else is experiencing the same issue, would you mind writing a few words about your alternative approach? Thanks.

Hi @ajp

What I implemented is the following:

Node contribution:

public class MyNodeToAddAndRemoveContribution implements ProgramNodeContribution, MyNodeToAddAndRemoveCustomAPI
{
// this is my node contribution
}

Node custom api interface:

public interface MyNodeToAddAndRemoveCustomAPI
{
// this is my custom api interface
// I have myself nothing inside this interface
}

  • In my root node, from which I will be able thanks to a checkbox to add and remove the node mentioned previously:
  1. I am using the ProgramNodeVisitor to retrieve the index of the MyNodeToAddAndRemove in my program hierarchy:

private int getMyNodeToAddAndRemoveIndex()
{
final int childIndex = new int[1];
childIndex[0] = -1;

TreeNode root = apiProvider.getProgramAPI().getProgramModel().getRootTreeNode(this);

root.traverse(new ProgramNodeVisitor()
{
  @Override
  public void visit(URCapProgramNode programNode, int index, int depth)
  {
    if(programNode.canGetAs(MyNodeToAddAndRemoveCustomAPI.class))
    {
      //System.out.println("Index: " + index);
      childIndex[0] = index;
    }
  }
});

// return -1 if the node does not exist
return childIndex[0];

}

  1. When the user enables or disables the check box:

public void setMyNodeToAddAndRemoveState(final boolean state)
{
this.undoRedoManager.recordChanges(new UndoableChanges()
{
@Override
public void executeChanges()
{

    // set the model state accordingly here
   // ...

    TreeNode root = this.programAPI.getProgramModel().getRootTreeNode(this);

    if (state)
    {
      // add the node (in try-catch)
      if (ProgramNodeHelper.getChildrenNb(root) > 0)
        root.insertChildBefore(root.getChildren().get(0), this.programNodeFactory.createURCapProgramNode(MyNodeToAddAndRemoveService.class));
      else
        root.addChild(this.programNodeFactory.createURCapProgramNode(MyNodeToAddAndRemoveService.class));
    }
    else
    {
      // remove the node (in try-catch)
      int myIndex = this.getMyNodeToAddAndRemoveIndex();
      if (myIndex < 0)
            return;
      root.removeChild(root.getChildren().get(myIndex));
    }
  }
});

}

  1. If some sub nodes must be added anyway and are not interactable through checkboxes, they must be added from the root node constructor only if the context is a node creation:

public MyRootNodeContribution(ProgramAPIProvider apiProvider, MyRootNodeView view,
DataModel model, CreationContext context)
{
// constructor
// do something
if (context.getNodeCreationType() == NodeCreationType.NEW)
{
TreeNode root = this.programAPI.getProgramModel().getRootTreeNode(this);
// in a try-catch
root.addChild(this.programNodeFactory.createURCapProgramNode(MyPermanentNodeService.class));
}
}

Sorry for the bad indentation. Other than that, that should do the trick. Let me know if you have any questions.

1 Like

Thanks very much for that. @dmartin I heard you’re facing a similar issue, does this help you at all?

Issues with child nodes will be fixed in the next public release (after 5.4/3.10).
Thank you for great description of the problem and examples, it helped a lot in finding out root cause.

2 Likes

A similar problem still occurs in URSim v5.4 / sdk 1.7.0.
Is the problem fixed??

In parent node constructor, I added child node and call child node api.

	try {
		root.addChild(programNodeFactory.createURCapProgramNode(ChildNodeService.class));
		setChildNodeAPI();
	} catch (TreeStructureException e) {
		e.printStackTrace();
	}

.

private void setIfNodeAPI() {
	final TreeNode root = apiProvider.getProgramAPI().getProgramModel().getRootTreeNode(this);
	
	root.traverse(new ProgramNodeVisitor() {
		public void visit(URCapProgramNode programNode, int index, int depth) {
			if(programNode.canGetAs(ChildNodeAPI.class)) {
				// call API function
			}
		}
	});
}

There is no problem when adding a parent node in URCaps.
But when I save the program in .urp file and open it again, I get a problem.

When I added a parent node,

  1. called parent node constructor -> addChild function called
  2. called child node constructor
  3. called child node API

But when I open .urp file,

  1. called parent node constructor -> addChild function called
  2. called child node API
  3. called child node constructor

So… in setChildNodeAPI() function, I can’t get as (ChildNodeAPI.class)
What can I do?

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