Purge unused Via SendWait or AutomationElement

Purge unused Via SendWait or AutomationElement

david_rock
Enthusiast Enthusiast
985 Views
7 Replies
Message 1 of 8

Purge unused Via SendWait or AutomationElement

david_rock
Enthusiast
Enthusiast

Hello,

 

I am attempting to press the Purge Unused Ribbon button via SendWait or AutomationElement but am not having much luck. Following are my two attempts. Any assistance greatly appreciated.

 

<Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)> _
<Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)> _
<Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)> _
Public Class PurgeAllSendWaitCmd
    Implements Autodesk.Revit.UI.IExternalCommand

    <DllImport("USER32.DLL")>
    Public Shared Function FindWindow(lpClassName As String, lpWindowName As String) As IntPtr
    End Function

    <DllImport("USER32.DLL")> _
    Public Shared Function SetForegroundWindow(hWnd As IntPtr) As Boolean
    End Function

    Const _window_class_name_zero As String = "Afx:00400000:8:00010011:00000000:007B05A1"
    Const _window_class_name_project_open As String = "Afx:00400000:8:00010011:00000000:007B05A1"

    Public Function Execute(ByVal commandData As Autodesk.Revit.UI.ExternalCommandData, _
                             ByRef message As String, ByVal elements As Autodesk.Revit.DB.ElementSet) _
                             As Autodesk.Revit.UI.Result Implements Autodesk.Revit.UI.IExternalCommand.Execute

        Try
            Dim revitHandle As IntPtr = FindWindow(_window_class_name_project_open, Nothing)
            If IntPtr.Zero = revitHandle Then
                revitHandle = FindWindow(_window_class_name_zero, Nothing)
            End If
            If IntPtr.Zero = revitHandle Then
                MsgBox("Unable to find Revit window." + " Is Revit Architecture up and running yet?")
                Return 1
            End If
            SetForegroundWindow(revitHandle)
            System.Threading.Thread.Sleep(1000)
            SendKeys.SendWait("%GU")
        Catch ex As Exception
            MsgBox(ex.message)
        End Try
    End Function
End Class
<Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)> _
<Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)> _
<Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)> _
Public Class RockitPurgeAllAutomationElementCmd
    Implements Autodesk.Revit.UI.IExternalCommand

    Public Function Execute(ByVal commandData As Autodesk.Revit.UI.ExternalCommandData, _
                             ByRef message As String, ByVal elements As Autodesk.Revit.DB.ElementSet) _
                             As Autodesk.Revit.UI.Result Implements Autodesk.Revit.UI.IExternalCommand.Execute

        Try
            Dim processes As Process() = Process.GetProcessesByName("Revit")
            Dim objMainWindowHandle As AutomationElement = AutomationElement.FromHandle(processes(0).MainWindowHandle)
            
            Dim nameRibbonCondition2 As New PropertyCondition(AutomationElement.NameProperty, "")
            Dim typeRibbonCondition2 As New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Pane)
            Dim objAndCondition2 As New AndCondition(typeRibbonCondition2, nameRibbonCondition2)
            Dim ribbonWnd2 As AutomationElement = objMainWindowHandle.FindFirst(TreeScope.Children, objAndCondition2)
            'Dim aIDPanelCondition As New PropertyCondition(AutomationElement.AutomationIdProperty, "Modify")

            Dim mainWndFromHandle As AutomationElement = objMainWindowHandle
            ' the revit window handle
            Dim nameRibbonCondition As New PropertyCondition(AutomationElement.NameProperty, "RibbonHostWindow")
            Dim typeRibbonCondition As New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Pane)
            Dim andCondition As New AndCondition(typeRibbonCondition, nameRibbonCondition)
            Dim ribbonWnd As AutomationElement = mainWndFromHandle.FindFirst(TreeScope.Children, andCondition)
            Dim aIDCondition As New PropertyCondition(AutomationElement.AutomationIdProperty, "Manage")
            Dim addinbutton As AutomationElement = ribbonWnd.FindFirst(TreeScope.Children, aIDCondition)
            ' show addin panel by pressing the tab header
            Dim invPattern As InvokePattern = TryCast(addinbutton.GetCurrentPattern(InvokePattern.Pattern), InvokePattern)
            invPattern.Invoke()
            ' pause, so ribbon panels can re-arrange
            System.Threading.Thread.Sleep(1000)
            Dim aIDPanelCondition As New PropertyCondition(AutomationElement.AutomationIdProperty, "PurgeUnused")
            Dim addinPanel As AutomationElement = ribbonWnd.FindFirst(TreeScope.Children, aIDPanelCondition)
            'Dim aIDTestPanelCondition As New PropertyCondition(AutomationElement.AutomationIdProperty, "CustomCtrl_%ADD_INS_TAB%TestPanel")
            'Dim testPanel As AutomationElement = addinPanel.FindFirst(TreeScope.Children, aIDTestPanelCondition)
            'Dim aIDContainerCondition As New PropertyCondition(AutomationElement.AutomationIdProperty, "CustomCtrl_%CustomCtrl_%ADD_INS_TAB%TestPanel%TestButton_RibbonItemControl")
            'Dim testContainer As AutomationElement = testPanel.FindFirst(TreeScope.Children, aIDContainerCondition)
            'Dim aIDTestButtonCondition As New PropertyCondition(AutomationElement.AutomationIdProperty, "CustomCtrl_%CustomCtrl_%ADD_INS_TAB%TestPanel%TestButton")
            'Dim testButton As AutomationElement = testContainer.FindFirst(TreeScope.Children, aIDTestButtonCondition)
            'Dim invPatternButton As InvokePattern = TryCast(testButton.GetCurrentPattern(InvokePattern.Pattern), InvokePattern)
            'invPatternButton.Invoke()
        Catch ex As Exception
            clsError.RockCADSupport_Exception(ex, , intPublicDebugPosition)
        End Try
    End Function
End Class

 

 

Kind Regards

David

 

0 Likes
986 Views
7 Replies
Replies (7)
Message 2 of 8

jeremytammik
Autodesk
Autodesk

Dear David,

 

Have you tried using PostCommand?

 

http://thebuildingcoder.typepad.com/blog/about-the-author.html#5.3

 

If you wish to drive it from an external context, i.e., you are not inside a valid Revit API context, e.g., a Revit event handler of some kind, then you are driving Revit from outside:

 

http://thebuildingcoder.typepad.com/blog/about-the-author.html#5.28

 

In that case, you can make use of an external event to get yourself into a valid Revit API context.

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 3 of 8

david_rock
Enthusiast
Enthusiast

Thank you very much for the suggestion, this is now launching the Purge Unused command :-). Next I would like to press the OK button automatically.

I'm not sure if you intended this to occur in the ExternalEvent? I have something wrong here.

The trouble I am facing now is that the Purge Unused Command is not posted until after the api command has finished.

Following is my current code.

 

<Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)> _
<Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)> _
<Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)> _
Public Class PurgeAllCmd
    Implements Autodesk.Revit.UI.IExternalCommand

    Public Function Execute(ByVal commandData As Autodesk.Revit.UI.ExternalCommandData, _
                             ByRef message As String, ByVal elements As Autodesk.Revit.DB.ElementSet) _
                             As Autodesk.Revit.UI.Result Implements Autodesk.Revit.UI.IExternalCommand.Execute
        Try

            Dim objUIApplication As UIApplication = commandData.Application
            Dim objRevitCommandId As RevitCommandId = RevitCommandId.LookupPostableCommandId(PostableCommand.PurgeUnused)
            Dim ExternalEvent As ExternalEvent = ExternalEvent.Create(New ExternalEventExample)
            Dim objAddInCommandBinding As AddInCommandBinding = objUIApplication.CreateAddInCommandBinding(objRevitCommandId)
            AddHandler objAddInCommandBinding.BeforeExecuted, AddressOf ReactToPurgeUnused
            objUIApplication.PostCommand(objRevitCommandId)
        Catch ex As Exception
            MsgBox(ex.message)
        End Try
    End Function

    Public Shared Sub ReactToPurgeUnused(ByVal sender As Object, ByVal e As Autodesk.Revit.UI.Events.BeforeExecutedEventArgs)
        System.Threading.Thread.Sleep(1000)
        MsgBox("ReactToPurgeUnused")
        Throw New NotImplementedException
    End Sub
End Class

Public Class ExternalEventExample
    Implements IExternalEventHandler

    Public Sub Execute(app As UIApplication) Implements IExternalEventHandler.Execute
        MsgBox("ExternalEventExample")
    End Sub

    Public Function GetName() As String Implements IExternalEventHandler.GetName
        Return "External Event Example"
    End Function
End Class

Kind Regards

David

 

0 Likes
Message 4 of 8

jeremytammik
Autodesk
Autodesk

Dear David,

 

Well done!

 

The PostCommand launches the built-in Revit command in exactly the same way as if the end user had done so manually in the user interface.

 

Therefore, from now on, you are basically in user interface mode.

 

If you wish to continue driving this automatically with no manual user intervention at all, you will need to simulate the user input using the standard Windows or .NET API.

 

The Revit API does not help with this.

 

Obviously, this is only realistically feasible if the interaction remains at a pretty simple and minimal level.

 

Pushing a button is acceptable complexity and can be easily handled by implementing a dedicated button clicker application, such as JtClicker:

 

http://thebuildingcoder.typepad.com/blog/2009/10/dismiss-dialogue-using-windows-api.html

 

https://github.com/jeremytammik/JtClicker

 

Please let us know how it goes for you with this.

 

Thank you!

 

Cheers,

 

Jeremy

 

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 5 of 8

david_rock
Enthusiast
Enthusiast

I have managed to get all three methods to Launch the Purge Unused command: SendWait, AutomationElement & PostCommand. Unfortunately is seems that once the purge dialog appears it squashes any further response from the api command. My goal of Purging multiple models has been hindered 😞

 

It seems that the only way to achieve this would be to drive the opening, purging, saving and closing each model entirely from an external application. I'm not sure it is worth it.

 

Kind Regards

David

0 Likes
Message 6 of 8

jeremytammik
Autodesk
Autodesk

Dear David,

 

Congratulations on the great progress you made!

 

I would love to see those samples side by side.

 

Yes, absolutely.

 

Revit is single threaded, and the API shares the same thread.

 

If a Revit command is active, no API code can be so at the same time.

 

You could create a string of commands to execute, and return to the API context after each execution completes, by subscribing to an Idling event.

 

If you wish to execute a certain command on a whole series of models, the easiest way to go might be via the journaling system.

 

That enables you to fully automatically drive both built-in Revit commands and API code from outside:

 

http://thebuildingcoder.typepad.com/blog/2010/07/ifc-import-and-conversion-journal-script.html -- IFC Import and Conversion Journal Script

 

http://thebuildingcoder.typepad.com/blog/2011/11/loading-an-add-in-with-a-journal-file.html -- Loading an Add-in With a Journal File

 

Cheers,

 

Jeremy

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 7 of 8

david_rock
Enthusiast
Enthusiast

I'm considering programming an external application but I want to be able to call the external application from the Revit api. Upon running the api it would write out the information required such as element ids or file names. Then call the external application

 

But I guess I would have to build the External application as a .exe so as to run in another thread? A linked .dll would not work as this would run in the same Thread?

 

What do you think?

 

The to programs I have in mind is:

- Model purger, I have many Revit model which require purging. Purge not currently available in api.

- Explode multiple ImportInstances. Explode ImportInstances not currently available in api.

 

Kind Regards

David

 

0 Likes
Message 8 of 8

jeremytammik
Autodesk
Autodesk

Before I can say another word on this I must ensure that you know the difference between an external application (Revit API) and an external application (non-Revit). 

 

The former is a Revit API add-in implementing a class derived from IExternalApplication. It implements the OnStartup and OnShutdown methods, and optionally subscribes to various other Revit API events.

 

You seem to mean the latter when you say 'external application' in you last post.

 

I meant the former in my previous answers.

 

Actually, I always mean the former when I use this term.

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes