NullPointerException occures only when load the program and execute

Hello.

I am trying to develop my original MoveC Node (named “MyCircleMove”) that realizes circular move and having a problem on executing this program node.

I was able to get pose of WaypointNode of “via_point” and “end_point”, which compose the arc, and the configuration of MoveP Node.
Finally, the robot moved as it is programmed by generating movec command in URScript.
MyCircleMove

However, after I loaded the program and executed again, the NullPointerException occured. After checking, I found this error occurs when casting WaypointNodeConfig to FixedPositionDefinedWaypointNodeConfig in generateScript().
Error_in_MyCircleMove

I tried to investigate further, but I couldn’t figure out why this error happens only on loading the program.

The code is below…

package com.falfan.MyCircleMove.impl;

import com.ur.urcap.api.contribution.ProgramNodeContribution;
import com.ur.urcap.api.contribution.program.CreationContext;
import com.ur.urcap.api.contribution.program.ProgramAPIProvider;
import com.ur.urcap.api.domain.program.nodes.ProgramNodeFactory;
import com.ur.urcap.api.domain.program.nodes.builtin.WaypointNode;
import com.ur.urcap.api.domain.program.nodes.builtin.MoveNode;
import com.ur.urcap.api.domain.program.nodes.builtin.configurations.movenode.MovePMotionParameters;
import com.ur.urcap.api.domain.program.nodes.builtin.configurations.movenode.MovePMoveNodeConfig;
import com.ur.urcap.api.domain.program.nodes.builtin.configurations.movenode.builder.MovePConfigBuilder;
import com.ur.urcap.api.domain.program.nodes.builtin.configurations.waypointnode.FixedPositionDefinedWaypointNodeConfig;
import com.ur.urcap.api.domain.program.structure.TreeNode;
import com.ur.urcap.api.domain.program.structure.TreeStructureException;
import com.ur.urcap.api.domain.program.ProgramModel;
import com.ur.urcap.api.domain.script.ScriptWriter;
import com.ur.urcap.api.domain.undoredo.UndoRedoManager;
import com.ur.urcap.api.domain.value.blend.Blend;
import com.ur.urcap.api.domain.value.simple.Acceleration;
import com.ur.urcap.api.domain.value.simple.Angle;
import com.ur.urcap.api.domain.value.simple.Length;
import com.ur.urcap.api.domain.value.simple.Speed;
import com.ur.urcap.api.domain.ProgramAPI;
import com.ur.urcap.api.domain.data.DataModel;

public class MyCircleMoveProgramNodeContribution implements ProgramNodeContribution{
    private final ProgramAPIProvider apiProvider;
    private final ProgramAPI programAPI;
    private final UndoRedoManager undoRedoManager;
    private final MyCircleMoveProgramNodeView view;
    private final DataModel model;
    private final ProgramModel programModel;
    private final ProgramNodeFactory factory;
    private boolean hasNode;

    private MoveNode moveP;
    private MovePConfigBuilder movePConfigBuilder;

    private WaypointNode via, end;
    private Integer circleMoveMode;

    public MyCircleMoveProgramNodeContribution(
        ProgramAPIProvider apiProvider,
        MyCircleMoveProgramNodeView view,
        DataModel model,
        CreationContext context) {
        
        this.apiProvider = apiProvider;
        this.programAPI = apiProvider.getProgramAPI();
        this.undoRedoManager = apiProvider.getProgramAPI().getUndoRedoManager();
        this.view = view;
        this.model = model;
        this.programModel = programAPI.getProgramModel();
        this.factory = programModel.getProgramNodeFactory();

        if(context.getNodeCreationType() == CreationContext.NodeCreationType.NEW)
            createChildNode();
    }

    @Override
    public void openView() {
        
    }

    @Override
    public void closeView() {
        
    }

    @Override
    public String getTitle() {
        return "My Circle Move";
    }

    @Override
    public boolean isDefined() {
        return true;
    }

    @Override
    public void generateScript(ScriptWriter writer) {
        // Get Configuration of "via_point" and "end_point" WaypointNode
        FixedPositionDefinedWaypointNodeConfig via_cfg = (FixedPositionDefinedWaypointNodeConfig)via.getConfig();   // <-- NullPointer Exception occured
        FixedPositionDefinedWaypointNodeConfig end_cfg = (FixedPositionDefinedWaypointNodeConfig)end.getConfig();

        // Set 
        final double[] viaPose = via_cfg.getPose().toArray(Length.Unit.M, Angle.Unit.RAD);
        final double[] endPose = end_cfg.getPose().toArray(Length.Unit.M, Angle.Unit.RAD);

        // Get Parameters of "MoveP" ProgramNode
        final MovePMotionParameters motionParameters = getParameters();
        final Speed toolSpeed = motionParameters.getToolSpeed();
        final Acceleration toolAcceleration = motionParameters.getToolAcceleration();
        final Blend blend = motionParameters.getBlend();
        circleMoveMode = view.getCircleMode();
        
        // generate movec command
        writer.appendLine(
            "movec(p["+viaPose[0] +", "+viaPose[1]+", "+viaPose[2]+", "+viaPose[3]+", "+viaPose[4]+", "+viaPose[5]+"], "
            + "p["+endPose[0]+", "+endPose[1]+", "+endPose[2]+", "+endPose[3]+", "+endPose[4]+", "+endPose[5]+"], "
            + "a=" + toolAcceleration.getAs(Acceleration.Unit.M_S2) + ", " 
            + "v=" + toolSpeed.getAs(Speed.Unit.M_S) + ", " 
            + "r=" + blend.getRadiusLength().getAs(Length.Unit.M) + ", "
            + "mode=" + circleMoveMode + ")"    
        );

    }

    // get MotionParameters of MoveP
    private MovePMotionParameters getParameters() {
        MovePMoveNodeConfig movePconfig = (MovePMoveNodeConfig)moveP.getConfig();
        return movePconfig.getMotionParameters();
    }
    
    
    // create childNodes after MyCiecleMoveNode
    private void createChildNode() {
        moveP = factory.createMoveNodeNoTemplate();
        movePConfigBuilder = moveP.getConfigBuilders().createMovePConfigBuilder();
        moveP.setConfig(movePConfigBuilder.build());

        via = factory.createWaypointNode("via_point");
        end = factory.createWaypointNode("end_point");
        
        TreeNode treeNode = programModel.getRootTreeNode(this);
        try {
            TreeNode movePTreeNode = treeNode.addChild(moveP);
            movePTreeNode.addChild(via);
            movePTreeNode.addChild(end);
        } catch (TreeStructureException e) {
            e.printStackTrace();
        }
    }
}

Is there any solution for this error?
Any suggestion will be appreciated!
Thank you.

I’m assuming the method “createChildNodes” is called from some button press on your CAP node? What’s happening is that when the program loads, “via” is being declared but not initialized (ie it has a value of null). It looks like it only takes on a value in the “createChildNodes” method, which won’t have been called at any point between loading and playing. So you’re calling “null.getConfig()” which is going to throw the error (so its failing on the .getConfig(), not the type casting.

As to how to fix that, there’s probably a lot of ways. If you can store the information to the datamodel somehow, that would work. Otherwise you need to implement some form of solution that can rebuild the tree when context.creationType == LOAD. I’ve found that dealing with the tree is unnecessarily difficult, as the API doesn’t provide many good methods. For example, if you have your node check how many children it has on load, it will tell you it has 0 (the parent is loaded before the children). Which means if you try to traverse to the child node(s) to pull the data in, it won’t do anything. This forces you to do things like set the method in a timer that won’t activate until it sees that the node has children.

2 Likes

Hi @eric.feldmann
Thanks for the quick reply.

Sorry for the lack of explanation, but the method createChildNode() is only used in constructor of contribution node class. That means, I didn’t use any buttons to insert the node.

I tried to invoke createChildNode() without if(context.creationType == NEW)as below code, but the same problem occured.

 public MyCircleMoveProgramNodeContribution(
        ProgramAPIProvider apiProvider,
        MyCircleMoveProgramNodeView view,
        DataModel model,
        CreationContext context) {
        
        this.apiProvider = apiProvider;
        this.programAPI = apiProvider.getProgramAPI();
        this.undoRedoManager = apiProvider.getProgramAPI().getUndoRedoManager();
        this.view = view;
        this.model = model;
        this.programModel = programAPI.getProgramModel();
        this.factory = programModel.getProgramNodeFactory();

        //if(context.getNodeCreationType() == CreationContext.NodeCreationType.NEW)
            createChildNode();
    }

So, you mean that if I could put the pose or configuration data into the DataModel somehow,
these are stored and can get them when I load the program, right?
I’ll check it out.

I checked about DataModel, and found the explanation from URCap SoftWare Development Tutorial Swing.(In Chapter 8.6)

When a program is loaded, the method createNode(…) is called for each persisted program node
contribution to re-create the program tree. In contrast to newly creating the program node, the
data model now contains the data from the persisted node and the CreationContext object will
reflect this situation also. All modications to the data model from the program node construc-
tor are ignored. This means that ideally the program node constructor should not set anything
in the data model.

As it says, I’ll try pass those data by using DataModel object.

I got a problem, again.

I tried to put some data into DataModel in generateScript() method like below code, but URSim told me not to modify DataModel outside of UI thread.(IllegalStateException)

@Override
public void generateScript(ScriptWriter writer) {
    if(context.getNodeCreationType() == CreationContext.NodeCreationType.NEW){
        // Get Configuration of "via_point" and "end_point" WaypointNode
        final FixedPositionDefinedWaypointNodeConfig via_cfg = (FixedPositionDefinedWaypointNodeConfig)via.getConfig();
        final FixedPositionDefinedWaypointNodeConfig end_cfg = (FixedPositionDefinedWaypointNodeConfig)end.getConfig();

        // Set 
        viaPose = via_cfg.getPose().toArray(Length.Unit.M, Angle.Unit.RAD);
        endPose = end_cfg.getPose().toArray(Length.Unit.M, Angle.Unit.RAD);

        // Get Parameters of "MoveP" ProgramNode
        motionParameters = getParameters();
        toolSpeed = motionParameters.getToolSpeed();
        toolAcceleration = motionParameters.getToolAcceleration();
        blendRadius = motionParameters.getBlend().getRadiusLength().getAs(Length.Unit.M);
        circleMoveMode = view.getCircleMode();

        undoRedoManager.recordChanges(new UndoableChanges() {
            @Override
            public void executeChanges() {
                model.set("via_Pose", via_cfg.getPose());
                model.set("end_Pose", end_cfg.getPose());
                model.set("toolSpeed", motionParameters.getToolSpeed().getAs(Speed.Unit.M_S));
                model.set("toolAcceleration", motionParameters.getToolAcceleration().getAs(Acceleration.Unit.M_S2));
                model.set("blendRadius", motionParameters.getBlend().getRadiusLength().getAs(Length.Unit.M));
                model.set("circleMode", view.getCircleMode());
            }
        });
    }

    else if(context.getNodeCreationType() == CreationContext.NodeCreationType.LOAD){
        System.err.println("This node has created in CreationContext.NodeCreationType.LOAD");
        viaPose          = model.get("via_Pose", (Pose)null).toArray(Length.Unit.M, Angle.Unit.RAD);
        endPose          = model.get("end_Pose", (Pose)null).toArray(Length.Unit.M, Angle.Unit.RAD);
        toolSpeed        = model.get("toolSpeed", (Speed)null);
        toolAcceleration = model.get("toolAcceleration", (Acceleration)null);
        blendRadius      = model.get("blendRadius", (double)0);
        circleMoveMode   = model.get("circleMode", (Integer)0);
    }

    else{
        viaPose = null;
        endPose = null;
        toolSpeed = null;
        toolAcceleration = null;
        blendRadius = 0.0;
        circleMoveMode = 0;
    }

    // generate movec command
    writer.appendLine(
        "movec(p["+viaPose[0] +", "+viaPose[1]+", "+viaPose[2]+", "+viaPose[3]+", "+viaPose[4]+", "+viaPose[5]+"], "
        + "p["+endPose[0]+", "+endPose[1]+", "+endPose[2]+", "+endPose[3]+", "+endPose[4]+", "+endPose[5]+"], "
        + "a=" + toolAcceleration + ", " 
        + "v=" + toolSpeed + ", " 
        + "r=" + blendRadius + ", "
        + "mode=" + circleMoveMode + ")"    
    );
}

Is there another way to store these data to DataModel?
Is it possible to store to DataModel outside of the generateScript()?

Any ideas would be welcome!

So yeah the data model should only be written to as a result of an action performed by a UI component. Clicking “Set Position” or selecting an element from a dropdown, etc. Regardless, you’d still get the nullPointer on load, because you’d be calling “model.set(“via_Pose”, null.getPose());” (because again, via_cfg is not initialized to a value on load).

Your problem is that the code being generated by the MyCircleNode is trying to access data inside one of its child nodes, the via. I’m not really sure what UR’s official implementation for fixing this is, as I’ve asked and seen it asked and not seen a response. So you need, upon load, for the My Circle Move to traverse to it’s children, and assign the declared variables via and end as the two nodes themselves.

However, as I mentioned, if you were to System.out.println() getChildCount() in the My Circle Move’s constructor, it will always tell you it has 0 children upon load, because the children get written AFTER the parent constructor.

The only way I’ve found around this is to start a Timer in the node’s constructor, have it wait until getChildCount() is greater than 0, then traverse() to the nodes and assign data. In your case, you need to assign the entire Waypoint Node to via, and the end_node to end.

Ultimately, you’re falling victim to the many woes of trying to maintain a tree structure inside your CAP. You’re finding out that it sucks. It lets you construct a tree, but doesn’t provide methods for properly utilizing it. I have no idea what kind of data structure doesn’t include “getParent” or “onDelete” methods, but it blows. You may also run into issues if you try copy/pasting these nodes, as there’s no distinction between creationContext.LOAD and copy/cut/paste.

I have no idea the full scope of what you’re trying to accomplish here, but you may want to consider just using the built-in moveC commands lol. Working with the program tree really is a nightmare.

1 Like

Hi @eric.feldmann .
Thank you for your kind reply again.

As you told, it looks so difficult to get the configuration of the child Waypoint Node upon loading the program…
However, I finally found the method to store to the DataModel in generateScript() by using SwingUtilities.invokeLater() like below.

@Override
public void generateScript(ScriptWriter writer) {
    if(context.getNodeCreationType() == CreationContext.NodeCreationType.NEW {
        // Get Parameters of "MoveP" ProgramNode
        motionParameters = getParameters();
        
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                undoRedoManager.recordChanges(new UndoableChanges() {
                    @Override
                    public void executeChanges() {
                        //model.set(VIA_POSE_KEY, via_cfg.getPose());
                        //model.set(END_POSE_KEY, end_cfg.getPose());
                        //model.set(VIA_POINT_NAME_KEY, via.toString());
                        //model.set(END_POINT_NAME_KEY, end.toString());
                        model.set(TOOL_SPEED_KEY, motionParameters.getToolSpeed().getAs(Speed.Unit.M_S));
                        model.set(TOOL_ACCELERATION_KEY, motionParameters.getToolAcceleration().getAs(Acceleration.Unit.M_S2));
                    }
                });
            }
        });
    }
     ...
}

I confirmed that those data can be stored when context.creationType == NEW, and load when context.creationType == LOAD by using this.

I’ll keep trying to implement my ideal custom node.
Thank you so much for your advice!