Giter VIP home page Giter VIP logo

revit.async's Introduction

Revit.Async

Use Task-based asynchronous pattern (TAP) to run Revit API code from any execution context.

中文说明

NuGet Link

Background

If you have ever encountered a Revit API exception saying, "Cannot execute Revit API outside of Revit API context", typically when you want to execute Revit API code from a modeless window, you may need this library to save your life.

A common solution for this exception is to wrap the Revit API code using IExternalEventHandler and register the handler instance to Revit ahead of time to get a trigger (ExternalEvent). To execute the handler, just raise the trigger from anywhere to queue the handler to the Revit command loop. But there comes another problem. After raising the trigger, within the same context, you have no idea when the handler will be executed and it's not easy to get some result generated from that handler. If you do want to make this happen, you have to manually yield the control back to the calling context.

This solution looks quite similar to the mechanism of "Promise" if you are familiar with JavaScript ES6. Actually, we can achieve all the above logic by making use of task-based asynchronous pattern (TAP) which is generally known as Task<T> in .NET. By adopting Revit.Async, it is possible to run Revit API code from any context, because internally Revit.Async wraps your code automatically with IExternalEventHandler and yields the return value to the calling context to make your invocation more natural.

If you are unfamiliar with the task-based asynchronous pattern (TAP), here is some useful material on it provided by Microsoft:

Here is a diagram comparing the Revit API external event mechanism with Revit.Async and screenshots of the two main parts:

Revit API External Event

Revit API

Revit.Async

Revit.Async

Revit.Async and Multithread

I was frequently asked about whether Revit.Async runs Revit API in a background thread.

Let's clarify it. The answer is NO!!!!! Don't be misled by the word "Async".

The word "Async" is actually innocent here. It is .NET who names a bunch of multithread methods with "Async" ending that results in the general misunderstanding.

This question can be explained starting from the differences between Asynchronous Programming and Multithread Programming.

A word from stackoverflow:

"Threading is about workers; asynchrony is about tasks".

An analogy from the same stackoverflow answer:

You are cooking in a restaurant. An order comes in for eggs and toast.

Synchronous: you cook the eggs, then you cook the toast.

Asynchronous, single threaded: you start the eggs cooking and set a timer. You start the toast cooking, and set a timer. While they are both cooking, you clean the kitchen. When the timers go off you take the eggs off the heat and the toast out of the toaster and serve them.

Asynchronous, multithreaded: you hire two more cooks, one to cook eggs and one to cook toast. Now you have the problem of coordinating the cooks so that they do not conflict with each other in the kitchen when sharing resources. And you have to pay them.

The reason why people have the "asynchronous == multithread" misunderstanding is that asynchronous has a big chance to come with multithread. In most UI applications(STA), when we use multithread to run a background task, the result of that task needs to "go back" to the UI thread to be presented. Asynchronous takes its part in the "go back" phase.

In a windows form application, if you want to update the UI from a worker thread, you need to use Invoke method to queue a Delegate to main thread to perform the UI updates.

In a WPF application, if you want to update the UI from a worker thread, you need to use Dispatcher object to queue a Delegate to main thread to perform the UI updates.

In Revit world, it is almost the same. Revit API is used to update the models. Revit performs model updates on the main thread and it requires all the APIs to be called on the main thread too, for thread safety I think.

If you want to update the models from a worker thread, you need to use ExternalEvent object to queue(Raise()) an IExternalEventHandler instance to main thread to call Revit API. This is the asynchronous pattern that Revit provides to schedule new API calls.

As to Revit.Async, it is just a wrapper around the above asynchronous pattern. The goal of this library is to provide an out of the box experience for asynchronous Revit API.

There is definitely NO multithread thing in Revit.Async.

Usage

Initialize

In any valid Revit API context, initialize RevitTask before you use any functionality of RevitTask.

RevitTask.Initialize(app);

Some of the valid Revit API contexts are:

  • IExternalCommand.Execute method
  • IExternalApplication.OnStartup method
  • IExternalEventHandler.Execute method
  • Revit API event handlers
  • IUpdater

RunAsync

The main functionality of Revit.Async is exposed by RevitTask.RunAsync() method. There are multiple overloads for RevitTask.RunAsync() method.

Execute sync code, without return value

  • Task RunAsync(Action action)
await RevitTask.RunAsync(() =>
{
    // sync function without return value
})
  • Task RunAsync(Action<UIApplication> action)
await RevitTask.RunAsync((uiApp) =>
{
    // sync function without return value, with uiApp paramter to access Revit DB
})

Execute sync code, with return value

  • Task<T> RunAsync<T>(Func<T> func)
var result = await RevitTask.RunAsync(() =>
{
    // sync function with return value
    return 0;
})
// result will be 0
  • Task<T> RunAsync<T>(Func<UIApplication, T> func)
var result = await RevitTask.RunAsync((uiApp) =>
{
    // sync function with return value, with uiApp paramter to access Revit DB
    return 0;
})
// result will be 0

Execute async code, without return value

  • Task RunAsync(Func<Task> func)
await RevitTask.RunAsync(async () =>
{
    // async function without return value
})
  • Task RunAsync(Func<UIApplication, Task> func)
await RevitTask.RunAsync(async (uiApp) =>
{
    // async function without return value, with uiApp paramter to access Revit DB
})

Execute async code, with return value

  • Task<T> RunAsync<T>(Func<Task<T>> func)
var result = await RevitTask.RunAsync(async () =>
{
    // async function with return value, http request as an example
    var httpResponse = await http.Get("server api url");
    //
    return httpResponse;
})
// result will be the http response
  • Task<T> RunAsync<T>(Func<UIApplication, Task<T>> func)
var result = await RevitTask.RunAsync(async (uiApp) =>
{
    // async function with return value, with uiApp paramter to access Revit DB, http request as an example
    var httpResponse = await http.Get("server api url");
    //
    return httpResponse;
})
// result will be the http response

Examples

Standard approach (without Revit.Async)

[Transaction(TransactionMode.Manual)]
public class MyRevitCommand : IExternalCommand
{
    public static ExternalEvent SomeEvent { get; set; }
    public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
    {
        //Register MyExternalEventHandler ahead of time
        SomeEvent = ExternalEvent.Create(new MyExternalEventHandler());
        var window = new MyWindow();
        //Show modeless window
        window.Show();
        return Result.Succeeded;
    }
}

public class MyExternalEventHandler : IExternalEventHandler
{
    public void Execute(UIApplication app)
    {
        //Running some Revit API code here to handle the button click
        //It's complicated to accept argument from the calling context and return value to the calling context
        var families = new FilteredElementCollector(app.ActiveUIDocument.Document)
                            .OfType(typeof(Family))
                            .ToList();
        //ignore some code
    }
}

public class MyWindow : Window
{
    public MyWindow()
    {
        InitializeComponents();
    }

    private void InitializeComponents()
    {
        Width                 = 200;
        Height                = 100;
        WindowStartupLocation = WindowStartupLocation.CenterScreen;
        var button = new Button
        {
            Content             = "Button",
            Command             = new ButtonCommand(),
            VerticalAlignment   = VerticalAlignment.Center,
            HorizontalAlignment = HorizontalAlignment.Center
        };
        Content = button;
    }
}

public class ButtonCommand : ICommand
{    
    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        //Running Revit API code directly here will result in a "Running Revit API outside of Revit API context" exception
        //Raise a predefined ExternalEvent instead
        MyRevitCommand.SomeEvent.Raise();
    }
}

Revit.Async Approach

[Transaction(TransactionMode.Manual)]
public class MyRevitCommand : IExternalCommand
{
    public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
    {
        //Always initialize RevitTask ahead of time within Revit API context
        // version 1.x.x
        // RevitTask.Initialze();

        // version 2.x.x
        RevitTask.Initialize(commandData.Application);
        var window = new MyWindow();
        //Show modeless window
        window.Show();
        return Result.Succeeded;
    }
}

public class MyWindow : Window
{
    public MyWindow()
    {
        InitializeComponents();
    }

    private void InitializeComponents()
    {
        Width                 = 200;
        Height                = 100;
        WindowStartupLocation = WindowStartupLocation.CenterScreen;
        var button = new Button
        {
            Content             = "Button",
            Command             = new ButtonCommand(),
            CommandParameter    = true,
            VerticalAlignment   = VerticalAlignment.Center,
            HorizontalAlignment = HorizontalAlignment.Center
        };
        Content = button;
    }
}

public class ButtonCommand : ICommand
{    
    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public async void Execute(object parameter)
    {
        //.NET 4.5 supported keyword, use ContinueWith if using .NET 4.0
        var families = await RevitTask.RunAsync(
            app => 
            {
                //Run Revit API code here
                
                //Taking advantage of the closure created by the lambda expression,
                //we can make use of the argument passed into the Execute method.
                //Let's assume it's a boolean indicating whether to filter families that is editable
                if(parameter is bool editable)
                {
                    return new FilteredElementCollector(app.ActiveUIDocument.Document)
                        .OfType(typeof(Family))
                        .Cast<Family>()
                        .Where(family => editable ? family.IsEditable : true)
                        .ToList();
                }
                
                return null;
            });
        
        MessageBox.Show($"Family count: {families?.Count ?? 0}");
    }
}

Define your own handler

Fed up with the weak IExternalEventHandler interface? Use the IGenericExternalEventHandler<TParameter,TResult> interface instead. It provides you with the ability to pass argument to a handler and receive result on complete.

It's always recommended to derive from the predefined abstract classes; they are designed to handle the argument passing and result returning part.

Class Description
AsyncGenericExternalEventHandler<TParameter, TResult> Use to execute asynchronous logic
SyncGenericExternalEventHandler<TParameter, TResult> Use to execute synchronize logic
[Transaction(TransactionMode.Manual)]
public class MyRevitCommand : IExternalCommand
{
    public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
    {
        //Always initialize RevitTask ahead of time within Revit API context
        // version 1.x.x
        // RevitTask.Initialze();

        // version 2.x.x
        RevitTask.Initialize(commandData.Application);
        //Register SaveFamilyToDesktopExternalEventHandler ahead of time
        RevitTask.RegisterGlobal(new SaveFamilyToDesktopExternalEventHandler());
        var window = new MyWindow();
        //Show modeless window
        window.Show();
        return Result.Succeeded;
    }
}

public class MyWindow : Window
{
    public MyWindow()
    {
        InitializeComponents();
    }

    private void InitializeComponents()
    {
        Width                 = 200;
        Height                = 100;
        WindowStartupLocation = WindowStartupLocation.CenterScreen;
        var button = new Button
        {
            Content             = "Save Random Family",
            Command             = new ButtonCommand(),
            CommandParameter    = true,
            VerticalAlignment   = VerticalAlignment.Center,
            HorizontalAlignment = HorizontalAlignment.Center
        };
        Content = button;
    }
}

public class ButtonCommand : ICommand
{    
    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public async void Execute(object parameter)
    {
        var savePath = await RevitTask.RunAsync(
            async app =>
            {
                try
                {
                    var document = app.ActiveUIDocument.Document;
                    var randomFamily = await RevitTask.RunAsync(
                        () =>
                        {
                            var families = new FilteredElementCollector(document)
                                .OfClass(typeof(Family))
                                .Cast<Family>()
                                .Where(family => family.IsEditable)
                                .ToArray();
                            var random = new Random(Environment.TickCount);
                            return families[random.Next(0, families.Length)];
                        });

                    //Raise your own handler
                    return await RevitTask.RaiseGlobal<SaveFamilyToDesktopExternalEventHandler, Family, string>(randomFamily);
                }
                catch (Exception)
                {
                    return null;
                }
            });
        var saveResult = !string.IsNullOrWhiteSpace(savePath);
        MessageBox.Show($"Family {(saveResult ? "" : "not ")}saved:\n{savePath}");
        if (saveResult)
        {
            Process.Start(Path.GetDirectoryName(savePath));
        }
    }
}

public class SaveFamilyToDesktopExternalEventHandler : 			
	SyncGenericExternalEventHandler<Family, string>
{
    public override string GetName()
    {
        return "SaveFamilyToDesktopExternalEventHandler";
    }

    protected override string Handle(UIApplication app, Family parameter)
    {
        //write sync logic here
        var document       = parameter.Document;
        var familyDocument = document.EditFamily(parameter);
        var desktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
        var path = Path.Combine(desktop, $"{parameter.Name}.rfa");
        familyDocument.SaveAs(path, new SaveAsOptions {OverwriteExistingFile = true});
        return path;
    }
}

Todo

  • Check current context to decide whether to create an IExternalEventHandler or to run code directly
  • Support progress
  • Support cancellation

Issues

Feel free to contact me by [email protected] if you have any issue using this library.

Star History

Star History Chart

revit.async's People

Contributors

jeremytammik avatar kennanchan avatar lawsonagmt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

revit.async's Issues

NuGet

Great tool!

Have you considered releasing this as a Nuget package?

RevitTask.RunAsync freezes GUI

When I use RevitTask.RunAsync (() => SomeMethod()) the GUI get frozen. If I use a simple task like Task.Delay(10000) it works correctly. What could be the issue?

Said that, thanks for this library. It's awesome!

Can't schedule more than 1 task in a synchronous method

I have found that if I call RevitTask.RunAsync more than once before releasing control of the current thread (through async/await or ending the method), only the first task is ever run. Subsequent calls to RunAsync appear to work through the queue, calling the next method, but always behind. Most of the time this isn't an issue as I await calls to RunAsync. Occasionally, though, I have wanted to set several tasks to run but don't care about when they finish. It's not terribly hard to work around, but because it's not typically an issue with other asynchronous methods, it took me a while to find the issue. Is this intended, unavoidable, or fixable?

In summary, this works:

await RevitTask.RunAsync([something 1]);
await RevitTask.RunAsync([something 2]);

but this doesn't

// These are run from, say, a button press. Only [something 1] is executed.
  RevitTask.RunAsync([something 1]);
  RevitTask.RunAsync([something 2]);

// After the button press method is finished, this is run by another button.
  await RevitTask.RunAsync([something 3]); // This causes [something 2] to run

Getter for UIApplication

You can initialise the UIApplication in RevitTask class. It would be nice to get the uiApp from that static class because I would not have to pass for all windows and commands commandData. I pass for now UIControlledApplication in OnStartUp.

Task dalay

Hello

I'm trying to add a 1sec delay between each instance insertion but I'm having a problem, my transaction is rollback.

public ICommand InsertCommand
        {
            get
            {
                return _insertCommand ?? (_insertCommand = new RelayCommand(
                   async (obj) =>
                   {
                       await RevitTask.RunAsync(
                            async app =>
                            {
                                try
                                {
                                    var document = app.ActiveUIDocument.Document;

                                    using (Transaction transaction = new Transaction(document))
                                    {
                                        transaction.Start("Insert");

                                        int count = 20;

                                        for (int i = 0; i < count; i++)
                                        {
                                            string family_2d = "Family";
                                            string symbol_2d = "Symbol";

                                            var symbol = document.FindSymbol(family_2d, symbol_2d);

                                            FamilyInstance instance = document.Create.NewFamilyInstance(new XYZ(0, 0, 0), symbol, StructuralType.NonStructural);

                                            await Task.Delay(1000);

                                            Informations = $"Insert {i + 1}/{count}";
                                        }

                                        transaction.Commit();
                                    }

                                    Informations = "Finished";
                                }
                                catch (Exception ex)
                                {

                                }
                            });
                   },
                    (obj) =>
                    {
                        return obj as Window != null;
                    }));
            }
        }

RevitTask.Initialize() Two Addin

Hello,

I'm having a problem, I have 2 addins that use RevitTask when I do

RevitTask.Initialize(application);

Addin 1 Application :

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Revit.Async;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RevitTaskAddin1
{
	public class Application : IExternalApplication
	{
		public static AddInId ActiveAddInId { get; private set; }

		public static string PluginName { get; set; }

		public Result OnShutdown(UIControlledApplication application)
		{
			return Result.Succeeded;
		}

		public Result OnStartup(UIControlledApplication application)
		{
			ActiveAddInId = application.ActiveAddInId;

			PluginName = application.ActiveAddInId.GetAddInName();

			var tabName = "Custom Tab";

			RevitUIFactory.AddRibbonTab(application, tabName);

			RibbonPanel detailingPanel = RevitUIFactory.AddRibbonPanel
							(application, tabName, "Custom Panel", true);

			RevitUIFactory.AddRibbonButton
			  (PluginName, tabName, detailingPanel,
			  typeof(MainCommand),
			  Properties.Resources.Icon_32x32,
			  Properties.Resources.Icon_16x16,
			  "Custom command discription.");

			RevitTask.Initialize(application);

			return Result.Succeeded;
		}
	}

	public class CommandAvailability : IExternalCommandAvailability
	{
		public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
		{
			if (applicationData.ActiveUIDocument != null && applicationData.ActiveUIDocument.Document != null &&
			  !applicationData.ActiveUIDocument.Document.IsFamilyDocument)
				return true;
			return false;
		}
	}
}

Command in UI of Addin 1

using Revit.Async;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace RevitTaskAddin1
{
    /// <summary>
    /// Logique d'interaction pour MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            txtBlockGuidAddin.Text = Application.ActiveAddInId.GetGUID().ToString();
            await RevitTask.RunAsync(
             app =>
             {
                 string guid = app.ActiveAddInId.GetGUID().ToString();
                 txtBlockRevitAsync.Text = guid;
             });
        }
    }
}

Addin 2 Application :

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Revit.Async;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RevitTaskAddin2
{
    public class Application : IExternalApplication
    {
		public static AddInId ActiveAddInId { get; private set; }
		
		public static string PluginName { get; set; }

		public Result OnShutdown(UIControlledApplication application)
        {
            return Result.Succeeded;
        }

        public Result OnStartup(UIControlledApplication application)
        {
			ActiveAddInId = application.ActiveAddInId;

			PluginName = application.ActiveAddInId.GetAddInName();

			var tabName = "Custom Tab";

            RevitUIFactory.AddRibbonTab(application, tabName);

            RibbonPanel detailingPanel = RevitUIFactory.AddRibbonPanel
                            (application, tabName, "Custom Panel", true);

            RevitUIFactory.AddRibbonButton
              (PluginName, tabName, detailingPanel,
              typeof(MainCommand),
              Properties.Resources.Icon_32x32,
              Properties.Resources.Icon_16x16,
              "Custom command discription.");

            RevitTask.Initialize(application);

            return Result.Succeeded;
        }
    } 

	public class CommandAvailability : IExternalCommandAvailability
	{
		public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
		{
			if (applicationData.ActiveUIDocument != null && applicationData.ActiveUIDocument.Document != null &&
			  !applicationData.ActiveUIDocument.Document.IsFamilyDocument)
				return true;
			return false;
		}
	}
}

Command in UI of Addin 2

using Revit.Async;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace RevitTaskAddin2
{
    /// <summary>
    /// Logique d'interaction pour MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            txtBlockGuidAddin.Text = Application.ActiveAddInId.GetGUID().ToString();
            await RevitTask.RunAsync(
             app =>
             {
                 string guid = app.ActiveAddInId.GetGUID().ToString();
                 txtBlockRevitAsync.Text = guid;
             });
        }
    }
}

image

The Revit task Guid is identical between the two addins.

A strongly-named assembly is required

Hi and thanks for creating a great library!
I've used the nuget package for a while without any errors. But now I am on a new PC and trying to use it on a new project (with Revit 2020), and I get a FileLoadException stating that the Revit assemblies or dependencies could not be loaded:
StronglyTypedAssemblyRequired

I have tried all versions on nuget and get the same exception with them all. I don't know if this is a problem on my computer or something else but I can't figure out how to resolve it or investigate it further. I even tried placing Revit 2016 DLLs along side the addin and add an AssebmlyResolve but without any luck.
Any ideas are greatly appreciated. Thanks

IExternalApplication Implementation

I'm not getting Revit.Async to work, and I think it might be because I am trying to implement it within an IExternalApplication instead of an IExternalCommand. I tried to start a simple method within RevitTask.RunAsync(), and it never finishes running. So in this sample method, I never reach the second line of code:

private async void SelectInRevit()
{
 await RevitTask.RunAsync(
      UIApp =>
      {
          UIApp.ActiveUIDocument.Selection.SetElementIds(SelectedFam.ElementIds);
      });
SelectionChangeable = !SelectionChangeable; 
}

Hanging when calling via CefSharp

We've been using Revit.Async for a while on our project and generally it works great!

However, recently we've run into an issue where running a task from a CefSharp callback never terminates.

CefSharp is a utility that allows us to trigger C# code from an embedded browser in Revit. The callback must be synchronous, like so:

public string GetSomeData()
{
    var task = RevitTask.RunAsync(app => getSomethingFromRevit(app));
    task.Wait();
    return task.Result;
}

GetSomeData() will be invoked by CefSharp via our Javascript code (I have not been able to find information about how or where exactly it is invoked.)

This has been working fine for a while, but recently it has started hanging on task.Wait(). I suspect this is related to the well-known issue of deadlock when calling async code from a synchronous context.

However, I've tried pretty much every workaround I have found on the internet, including adding .ConfigureAwait(false) to all of the await calls in the Revit.Async code.

If anyone has any ideas of what might be going on, it would be greatly appreciated.

Sometimes InvalidOperationException: 'Starting a transaction from an external application..."

When I use RevitTast.RunAsync(), it seems to work most of the time. However, I occasionally still get the InvalidOperationException sometimes for running a method outside of the API context. It doesn't make any sense to me that it would only fail sometimes. Maybe its because I am trying to run it consecutively, and the thread is already busy. Maybe its because I have multiple transactions within the same RevitTask.RunAsync. Maybe its because it doesn't like running static methods. I'm not sure what the issue could be.

 private async void Replace()
        {
                await RevitTask.RunAsync(app =>
                {
                    Document doc = app.ActiveUIDocument.Document;
                    foreach (ElementId id in SelectedFam.ElementIds)
                    {
                        Element ele = doc.GetElement(id);
                        if (ele == null)
                            throw new NullReferenceException();
                        HostObject host;
                        XYZ placementLocation;
                        Level level;
                        FamilyInstance fam = ele as FamilyInstance;
                        if (fam != null)
                        {
                            host = fam.Host as HostObject;
                            level = doc.GetElement(fam.LevelId) as Level;
                            placementLocation = ((LocationPoint)fam.Location).Point;

                            ElementId typeId = ele.GetTypeId();
                            ElementType type = doc.GetElement(typeId) as ElementType;
                            if (type == null)
                                throw new NullReferenceException("Can not find the element type");
                            using (Transaction tx = new Transaction(doc, "Delete Family"))
                            {
                                tx.Start("Delete Family");
                                doc.Delete(id);
                                tx.Commit();
                                tx.Dispose();
                            }
                            try
                            {
                                Methods.PlaceEntityInKnownLocation(doc, app, SelectedReplacement, host, placementLocation, level);
                            }
                            catch (Exception ex)
                            {
                                SimpleLog.Log(ex);
                            }
                });

 public static async void PlaceEntityInKnownLocation(Document doc, UIApplication uiApp, EntityModels.Material entity, Element host, XYZ placementLocation, Level level)
        {
            
            //Get application and document objects

            Autodesk.Revit.ApplicationServices.Application app = uiApp.Application;

            //IT DOESN'T ALREADY EXIST,
            //CHECK FILE TO LOAD IT FROM
            string dir = System.IO.Path.GetDirectoryName(
      System.Reflection.Assembly.GetExecutingAssembly().Location);
            string localSaveLocation = dir + $"\\{entity.Name}.rfa";

            if (!File.Exists(localSaveLocation))
            {
                //DownloadWindow
                await HttpClientInstance.DownloadFamilyAsync(entity);
            }

            //SEE IF THE ELEMENT ALREADY EXISTS IN THE PROJECT
            FilteredElementCollector a
                = new FilteredElementCollector(doc)
            .OfClass(typeof(Family));

            Family family = a.FirstOrDefault<Element>(
                e => e.Name.Equals(entity.Name))
                as Family;
            FamilySymbol symbol = null;
           
            // LOAD THE FAMILY INTO THE PROJECT
            using (Transaction tx = new Transaction(doc, "Load Family"))
            {
                tx.Start("Load Family");
                if (family == null)
                {
                    bool res = doc.LoadFamily(localSaveLocation, out family);
                }
                if (family != null)
                {
                    //DETERMINE THE FAMILY SYMBOL
                    ISet<ElementId> symbolIds = family.GetFamilySymbolIds();
                    foreach (ElementId symbolId in symbolIds)
                    {
                        symbol = doc.GetElement(symbolId) as FamilySymbol;
                        if(symbol != null)
                        {
                            if (!symbol.IsActive)
                                symbol.Activate();
                            break;
                        }
                    }
                }
                tx.Commit();
            }
            
            if (symbol != null)
            {
                using (Transaction tx = new Transaction(doc, "Place Family"))
                {
                    tx.Start("Place Family");
                    try
                    {
                        doc.Create.NewFamilyInstance(placementLocation, symbol, host, level, Autodesk.Revit.DB.Structure.StructuralType.NonStructural);

                    }
                    catch (Autodesk.Revit.Exceptions.OperationCanceledException ex)
                    {
                        SimpleLog.Log(ex);
                    }
                    tx.Commit();
                }
            } 
            return;
        }

image

RevitTask.RunAsync() returns before Revit

The await RevitTask.RunAsync() does not seem to wait for Revit to finish. In the below sample method, RevitTask.RunAsync() returns "Jimmy" before the Selection is set in the ActiveUiDocument. So my MessageBox shows "jimmy" before the selection is made. I'm not sure why this would be the case.

private async void SelectInRevit()
        {
            var myString = await RevitTask.RunAsync(
                app =>
                {
                    app.ActiveUIDocument.Selection.SetElementIds(SelectedFam.ElementIds);
                    return "jimmy"; 
                });
            MessageBox.Show($"{myString}");
        }

Is there a way to trigger the SaveFamilyCommand() with a MouseEventArgs ?

Hi,
Thanks a lot for this awesome repo!

The title is my question :
Is there a way to trigger the SaveFamilyCommand() with a MouseEventArgs ?

In the current case, the trigger is the TestWindow Button to save a family.
I would like to change the trigger with a MouseEventArgs but because the SaveFamilyCommand is a ICommand class, I have some trouble achieving this...

Execute Console App from Revit

Hi,
I think this repo can be useful to execute a Console App from Revit, because the console app has async code, so I want to await the result of the console app before I trigger any ExternalEvent from Revit.

I'm new to this, so perhaps there is a simple way to explain how to:
Start Revit Command -> Start Console App -> Await Result -> use Result in ExternalEvent -> do Transaction()

any tips on how to achieve this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.