Dynamo Custom nodes, listening to Revit element events

Hi!

I hope someone could help with this pickle. I’m trying to make custom C# nodes listen to Revit events. Specifically I have a node that takes the ElementId of a GroupType and return the data about that group wrapped in a special object. Now I need to make it update when something changes about the GroupType. (a Zero Touch node won’t update unless I deconnect the input, run the node, connect the input back and run it again).

From reverse-engineering DynamoRevit nodes I have figured out that I need to listen to the RevitServicesUpdater.Instance.ElementsUpdated and then I can figure out if the element I’m looking for has changed. (Or do I have to listen to the UIControlledApplication.ControlledApplication.DocumentChanged straight from Revit?)

Now there’s two questions:
How should I save the input so that the event listener can access it? I tried assigning it to a variable in BuildOutputAst(), but that didn’t work. Some other elements are also listening to some PropertyChanged event, but I can’t figure how it works.

What method should I call in the event listener if I want the node to re-evaluate? From some other topics on this forum I figured it is OnNodeModified(true) but that doesn’t seem to work.

Thanks,
Taavi

You are going have to paste some code here, so we can re-create.

Now, just a bit of a general advice. You don’t want to subscribe to UIControlledApplication.ControlledApplication.DocumentChanged because that thing will fire on every change. I think the only solution for you is to use the event system that Dynamo has pre-made for you with the RevitServicesUpdated, but that doesn’t use ZeroTouch but a NodeModel. Look into that.

Also, here’s an example of what such node might have to look like and how you can subscribe to events in Revit from the NodeModel:

https://github.com/DynamoDS/DynamoRevit/issues/1533

Cheers!

2 Likes

What I have is this.

I store the Id of the groupType to follow (as int) to the currentElementId inside BuildOutputAst (assuming it is run every time the inputs update). The debugger shows that the (int)((inputAstNodes[0] as IntNode).Value) produces the error. Where should I store the input to the private variable?

[NodeName("Generate PartData")]
[NodeCategory("RevitConfigurator")] 
[NodeDescription("Generates the PartData from the ElementId")]

[InPortNames("Id")]
[InPortTypes("int")]
[InPortDescriptions("Id of the Model Group")]
[OutPortNames("P")]
[OutPortTypes("PartData")]
[OutPortDescriptions("PartData for this Model Group")]
[IsDesignScriptCompatible]
public class GeneratePartData : NodeModel
{
    int currentElementId;

    public GeneratePartData()
    {
        RegisterAllPorts();
        RevitServicesUpdater.Instance.ElementsUpdated += Updater_ElementsUpdated;
    }

    private void Updater_ElementsUpdated(object sender, ElementUpdateEventArgs e)
    {
        if(e.Operation == ElementUpdateEventArgs.UpdateType.Modified || 
            e.Operation == ElementUpdateEventArgs.UpdateType.Deleted)
        {
            if(e.Elements.Contains(new ElementId(currentElementId)))
            {
                OnNodeModified(true);
            }
        }
    }

    public override void Dispose()
    {
        RevitServicesUpdater.Instance.ElementsUpdated -= Updater_ElementsUpdated;
        base.Dispose();
    }

    public override IEnumerable<AssociativeNode> BuildOutputAst(List<AssociativeNode> inputAstNodes)
    {
        if (!HasConnectedInput(0))
        {
            return new[] { AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), AstFactory.BuildNullNode()) };
        }

        currentElementId = (int)((inputAstNodes[0] as IntNode).Value);

        var functionCall = AstFactory.BuildFunctionCall(
            new Func<int, PartData>(PartData.ByGroupTypeId),
            new List<AssociativeNode> { inputAstNodes[0] }
        );

        return new[] { AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), functionCall) };
    }
}

Here’s also a visual description of what I’m trying to achieve. The question is about the node PartData.ByGroupTypeId (it’s the internal Zero Touch function that I’m writing a custom NodeModel wrapper around). I pass a GroupType ElementId into it and expect out a special object with information about that GroupType.

I’m want the node to reevaluate when something changes about that GroupType in the Revit model. This does not happen with Zero Touch, because it only reevaluates when the input’s value changes. Therefor I’m looking into explicit nodes but have not figured it out yet.

what behavior does this code produce-
you mentioned an error?
what doesn’t work?

I think one thing you should note is that the buildOutputAST method is not an onExecute method - it will not be called everytime the node runs so it is quite possible that your currentElement will be null or an old value.

What will run is the code produced by your AST - you should put this function call into your AST not into the build output AST method -

see some samples here: https://github.com/nonoesp/DynamoNodeModelsEssentials

I think you should look at the data bridge examples.

I feel my explanation was a bit poor - let me try again:

The buildOutputAST method does not run when the node executes. It runs before the node executes and it does not run every time the node executes.

What executes every time the node executes is the AST which you returned in your outputAST function.

The AST represents code to be executed.

I figured out that this line is the one causing the error. Value is not set when BuildOutputAst run.
currentElementId = (int)((inputAstNodes[0] as IntNode).Value);
As you said the BuildOutputAst only runs before the actual execution of the node.

So the question is: how do I store the value that is coming into the node inside the object and update it when the input changes?

I want to store it so that the event callback can use it to filter the args for modification to this element I’m interested in. The other part of the question seems to work out: the event listener runs correctly in the debugger when I change something about the model.