Revit API Forum
Welcome to Autodesk’s Revit API Forums. Share your knowledge, ask questions, and explore popular Revit API topics.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Recording Executed Commands

20 REPLIES 20
SOLVED
Reply
Message 1 of 21
samyar
4195 Views, 20 Replies

Recording Executed Commands

I am trying to write a script that records the commands that a user executes. Basically, I want to record the time and the command Id in a text file. I have written an External Application and used DocumentChanged Events. But, these events only report whether elements are modified and I don't know how to figure out what command has been executed that resulted in the modification. I greatly appreciate it if you could help me with this issue. 

20 REPLIES 20
Message 2 of 21
matthew_taylor
in reply to: samyar

Hi @samyar,

If you want to record user interface (UI) actions, you really need to deal with the Revit.UI namespace.

The user interface and the database (DB) actions are quite clearly defined, and separate.

To record what the user is clicking/executing you should look at the Revit.UI.Events namespace. There is definitely something suitable there.

 

Take a look. I think ExecutedEventArgs is your key search item.

 

-Matt


Cheers,

-Matt
_______________________________________________________________________________
Marking a post as a 'solution' helps the community. Giving a post 'Kudos' is as good as saying thanks. Why not do both?
Message 3 of 21
FAIR59
in reply to: samyar

you have to subscribe to this event:

Autodesk.Windows.ComponentManager.ItemExecuted

Message 4 of 21
matthew_taylor
in reply to: FAIR59

@FAIR59 @samyar

Now that I have my code in front of me, I used Autodesk.Windows.ComponentManager.UIElementActivated.

I guess ItemExecuted probably works just as well!

 

-Matt


Cheers,

-Matt
_______________________________________________________________________________
Marking a post as a 'solution' helps the community. Giving a post 'Kudos' is as good as saying thanks. Why not do both?
Message 5 of 21
samyar
in reply to: FAIR59

Thanks for the help. I am new to Revit API, and I don't know how to register the event in the External Application. I tried using "application.ControlledApplication" to register the event in the "OnStartup" method. But, ControlledApplication does not have a ItemExecuted method. 

 

 

Message 6 of 21
erikeriksson5686
in reply to: samyar

You need to add the adwindows.dll as a reference to your project.
Is use this myself, but it is not supported by autodesk, as far as I know,

Erik Eriksson
White
Message 7 of 21
matthew_taylor
in reply to: samyar

@samyar

In VB.NET, you'd do it like this:

'in OnStartup 
AddHandler Autodesk.Windows.ComponentManager.UIElementActivated, AddressOf MyUiElementActivated

'in OnShutdown
RemoveHandler Autodesk.Windows.ComponentManager.UIElementActivated, AddressOf MyUiElementActivated

Private Sub MyUiElementActivated(ByVal sender As Object, ByVal e As Autodesk.Windows.UIElementActivatedEventArgs)
   'do your thing
End Sub

I would imagine @FAIR59's suggestion would work in a similar manner.

 

 

-Matt

 

p.s. and of course, add the reference as @erikeriksson5686 suggested. As @erikeriksson5686 mentioned it's not supported by Autodesk, but I use it for Revit 2011-2017 without issue.


Cheers,

-Matt
_______________________________________________________________________________
Marking a post as a 'solution' helps the community. Giving a post 'Kudos' is as good as saying thanks. Why not do both?
Message 8 of 21
samyar
in reply to: FAIR59

Thank you for the great help. Just a quick follow up, do you know how I can access the document through an ItemExecuted event handler? In addition to recording the executed commands, I want to record the selected elements (the elements that the command was executed on). 

I use the sender as application and access documents in other event handlers, but it seems I can't do the same with RibbonItemExecuted event handler. 

 

 

Message 9 of 21
erikeriksson5686
in reply to: samyar

Since its not a supported workflow you cannot expect to get the RevitAPI types.
I have not tried this myself, but I would suggest registering an ExternalEvent and raising that in the ItemExecuted event handler.
That is a great way of getting back into normal Revit context.

 

This will wait for the next idle moment to raise the event, however if your user unselectes the elements before the event is fired, you wont get an accurate selection.

A more compex way, if you find that you cant reliably capture the selected elements using a ExternalEvent, is using the IUpdater interface and make the updater trigger on the specific things you need to watch (in conjunction with the ItemExecuted event), then you would catch what command was executed and then you would get a valid context (in the updater) as the transaction is commited and therefore you can be sure of getting the selected elements.
This will propably require some testing to cover all scenarios.

 

Erik Eriksson
White
Message 10 of 21

@samyar I agree with @erikeriksson5686

That said, you can access the db document if you use the ViewActivated event to store it (or something similar). Your UI event subroutine could then reference that. I can't see how you'd get to the uidocument (in order to get the current selection) though!

An iUpdater will give you the added/changed/deleted elements, which isn't technically the selection (as some of the selection may remain unchanged), but that may be as good as you get (or as good as you need).

 

How about you take a step back from the task and tell us exactly what you're trying to achieve so we can advise you better?

 

Cheers,

 

-Matt


Cheers,

-Matt
_______________________________________________________________________________
Marking a post as a 'solution' helps the community. Giving a post 'Kudos' is as good as saying thanks. Why not do both?
Message 11 of 21

@matthew_taylor, storing the document object and then accessing it from a non-revit-api context is not advisable. I stopped doing such things because there will be crashes.


I suggested the IUpdater because it will deliver the document being changed (from UpdaterData.GetDocument()) and from that you can construct your UIDocument and retrieve the selection.

 

But I agree with you @matthew_taylor:

@samyar: elaborate on what you want to accomplish and we can suggest something more powerful and simple, perhaps.

Erik Eriksson
White
Message 12 of 21
samyar
in reply to: matthew_taylor

Thank you for your response. I am trying to record all model development events, by which I mean the commands that the modeler executes and the corresponding changes to the elements in the model. I want to record the time, the type of command, element GUID, and the project that the modeler is working on. 

Message 13 of 21
matthew_taylor
in reply to: samyar

Hi @Anonymous,
I wonder whether what you're trying to do should be achieved by an iupdater as Erik said. I've not looked into ActiveAddinId, but I wonder whether you could assemble a dictionary of AddinIds and their associated ribbon data (in text form?) at startup, and then reference that with your iupdater, as you look like you should be able to reference the ActiveAddinId from within the updater.
I've not worked with ActiveAddinId as it's new in 2017, but it may be what you're after.

-Matt

Cheers,

-Matt
_______________________________________________________________________________
Marking a post as a 'solution' helps the community. Giving a post 'Kudos' is as good as saying thanks. Why not do both?
Message 14 of 21

@matthew_taylor, good tip on that ActiveAddinId, Im gonna take a look at that. It might come in handy to see what is actually executing when you're catching events.

However, looking at the documentation, that will only give you the external command or application Id.
What @samyar seems to want is to get all changes like the user creates a wall etc.

 

Then I suggest the following: register a IUpdater that triggers on the elements you want to record: I suggest inverted ElementIsElementTypeFilter to get all created instances and add ElementIsElementTypeFilter without inversion to get basically all element changes and trigger on addition, deletion, geometry and any (you'll know what I mean when you look at the documentation).

Also subscribe to the ItemExecuted event like we discussed earlier.
The IUpdater is going to fire first so in that event you cache all the info you want to store in persistable objects. No revit api types,  you can end up crashing revit if you ask for anything from any revit element out of context or you can get data that is out of date. Create new classes that hold the info in primitive types. When the ItemExecuted event fires you send the info to your database or logfile or what ever you are into.

Since you will be catching alot of events its a good idea to keep your implementation as lightweight as possible to minimize lag for the user.

Also when you send your data, send it in a separate Thread or Task, so that the handler returns fast, but like I wrote earlier, make sure you dont have any revit api types.

 

Thats about it, you might want to look at how the ItemExecuted behaves and filter the event accordingly because it fires quite alot. Almost everytime the user exists a command it goes to the Modify command (for instance).

 

Good luck!

Erik Eriksson
White
Message 15 of 21
samyar
in reply to: erikeriksson5686

Thank you all for the great suggestions. I will try the recommended methods and report the outcome. 

Message 16 of 21
matthew_taylor
in reply to: samyar

Hi Erik,
That was my intention, to reference the addin data from within the iupdater (that captures the changes). An iupdater would be overkill for *solely* tracking addin usage etc, as I'm sure you're aware. 😂

Cheers,

-Matt

Cheers,

-Matt
_______________________________________________________________________________
Marking a post as a 'solution' helps the community. Giving a post 'Kudos' is as good as saying thanks. Why not do both?
Message 17 of 21
n_mulconray
in reply to: samyar

I have recently been asked to perform an analysis of custom add-in usage, to determine where our efforts should be applied as in additional training, effort to improve and or deprecation of add-ins that are not used. Has anyone had any success with this as I don't have much time to devote to developing a bespoke solution and would only use it infrequently. I would ideally get an add-ins visible name and pair that with a dateTime field and record it.

Message 18 of 21
anton.shaw
in reply to: n_mulconray

Hi Nik

 

Did you develop this idea any further?

 

I'm just putting some research into something now to do a very similar thing around add-in use.

 

Thanks,

Anton

Message 19 of 21
n_mulconray
in reply to: samyar

Hey Anton are you a Kiwi? Are you friends with the Kiwi codes developers, I used to work at Meteir 3 in Melbourne with one of them. Anyway I used the following code below. This is not ideal and was just meant as a temporary solution as the output is to an delimited file that i visualize in powerbi. Ideally you would send to a database and could then leverage the data elsewhere.

 

 public void OnDocClosing(object sender, DocumentClosingEventArgs e)
{
try
{
//saves to a tab delimiter to .csv
RecordAddinData.WhatExternalCommandWasExecuted(e.Document, MySQLDateTimeConverter(DateTime.Now)); 

}

 

using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Analysis;
using System;
using System.Collections.Generic;
using System.IO;

namespace TechnoCore
{
internal static class RecordAddinData
{
internal static List<string> WhatExternalCommandWasExecuted(Document doc, DateTime dateTimeClosing)
{

List<string> commandsExecuted = new List<string>();

try
{
string userName = GetThreeInitials(doc);
bool isFamilyDoc = doc.IsFamilyDocument;
string projType;
string projStatus;


if (isFamilyDoc)
{
projType = "NULL";
projStatus = "NULL";
}
else
{
EnergyDataSettings energyDataSetting = EnergyDataSettings.GetFromDocument(doc);
projType = energyDataSetting.BuildingType.ToString();

ProjectInfo m_info = doc.ProjectInformation;
projStatus = !string.IsNullOrWhiteSpace(m_info.Status) ? m_info.Status : null;
}

 

string journalPath = doc.Application.RecordingJournalFilename;
List<string> JournalLines = new List<string>();
string searchString = "Jrn.RibbonEvent \"Execute external command:";

var fs = new FileStream(journalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using (var sr = new StreamReader(fs))
{
string line;
while ((line = sr.ReadLine()) != null)
{
if (line.Contains(searchString))
{
int pFrom = line.IndexOf(":PT_") + ":PT_".Length;
int pTo = line.LastIndexOf("\"");
string result = line.Substring(pFrom, pTo - pFrom);

if (line.ToUpperInvariant().Contains("DLM INTEGRATOR")) { result = "Delslack.Infiltrator";}
else if (line.ToUpperInvariant().Contains("UPREV")) { result = "Xrev.Uprev"; }
else if (line.ToUpperInvariant().Contains("REVIT LOOKUP")) { result = "Inspector.RevitLookup"; }

commandsExecuted.Add(
userName + "\t" +
result + "\t" +
projType + "\t" +
projStatus + "\t" +
dateTimeClosing + "\t" +
doc.IsFamilyDocument.ToString()
);
}
}
}

AppendTextLinesToNotepad(commandsExecuted, PATHS.ADDINS_SVR(doc, @"External-Command-Usage.csv"));
}
catch (Exception) { }

return commandsExecuted;
}

internal static string GetThreeInitials(Document _doc)
{
string userName = _doc.Application.Username.ToUpperInvariant().Replace(".", "");

switch (userName.Length)
{
case 0:
return "XYZ";
case 1:
return userName + "YZ";
case 2:
return userName + "Z";
default:
return userName.Substring(0, 3);
}
}

private static void AppendTextLinesToNotepad(List<string> list, string filename)
{
using (StreamWriter outputFile = new StreamWriter(filename, true))
{
foreach (string item in list)
{
outputFile.WriteLine(item);
}
}
}

}//cl
}//ns

Message 20 of 21
jeremy_tammik
in reply to: n_mulconray

You might be able to extract the required information quite easily by running a regular analysis on all the journal files. This possibility is mentioned among other approaches in the discussion on monitoring using the SLOG:

 

https://thebuildingcoder.typepad.com/blog/2021/12/logging-and-monitoring-deleted-data.html

  

Jeremy Tammik, Developer Advocacy and Support, The Building Coder, Autodesk Developer Network, ADN Open

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Rail Community