architecture-building-systems / revitpythonshell

An IronPython scripting environment for Autodesk Revit and Vasari
MIT License
492 stars 112 forks source link

Revit crashes on UIView.Close() #64

Open damirsib opened 6 years ago

damirsib commented 6 years ago

The following code crashes Revit:

try:
    user_active_view = uidoc.ActiveView

    views = [view for view in FilteredElementCollector(doc).WherePasses(ElementClassFilter(ViewPlan)) if view.IsValidObject and not view.IsTemplate and view.ViewType != ViewType.CeilingPlan]

    for view in views:
        uidoc.ActiveView = view

    uidoc.ActiveView = user_active_view

    for ui_view in uidoc.GetOpenUIViews():
        if ui_view.ViewId != uidoc.ActiveView.Id:
            ui_view.Close()

except Exception as exception:
    print exception.message

But C# code works fine:

[TransactionAttribute(TransactionMode.Manual)]
[RegenerationAttribute(RegenerationOption.Manual)]
public class CloseViews : IExternalCommand
{
    public Result Execute(
      ExternalCommandData commandData,
      ref string message,
      ElementSet elements)
    {
        UIApplication uiApp = commandData.Application;
        Document doc = uiApp.ActiveUIDocument.Document;
        UIDocument uidoc = uiApp.ActiveUIDocument;

        View user_active_view = uidoc.ActiveView;

        FilteredElementCollector view_plans = new FilteredElementCollector(doc).WherePasses(new ElementClassFilter(typeof(ViewPlan)));
        IEnumerable<View> views = view_plans.Cast<View>().ToList<View>().Where(view => view.IsValidObject && !view.IsTemplate && view.ViewType != ViewType.CeilingPlan);

        foreach (View view in views)
        {
            uidoc.ActiveView = view;
        }

        uidoc.ActiveView = user_active_view;
        foreach (UIView ui_view in uidoc.GetOpenUIViews())
        {
            if (ui_view.ViewId != uidoc.ActiveView.Id)
            {
                ui_view.Close();
            }
        }

        return Result.Succeeded;
    }
}
eirannejad commented 6 years ago

I did a whole series of tests troubleshooting this. Under RPS the script either crashes Revit or returns an illegal memory access inside Revit's UIFrameworks module. This part of the script is the cause of error:

for view in views:
        uidoc.ActiveView = view

It's activating a series of views in rapid succession. Revit regenerates all UIView objects wrapping the active View object every time there is a change in the state of open views. I've verified this by comparing object ids of UIView objects vs their associated ViewId.

I concluded that the crash is due to the UI complications of activating views, while an external command has a Modal or Non-Modal window open. The script runs fine in pyRevit and C# addon and this verifies that the error is definitely not an IronPython or CLR error.

The easy way to resolve this and similar GUI errors is to Hide and Unhide the RPS window. This IMHO should be the general practice when making UI changes to Revit in RPS.

Here is the revised script that works:

__window__.Hide()
user_active_view = uidoc.ActiveView

views = [view for view in FilteredElementCollector(doc).WherePasses(ElementClassFilter(ViewPlan))
         if view.IsValidObject and not view.IsTemplate and view.ViewType != ViewType.CeilingPlan]

for view in views:
    uidoc.ActiveView = view

uidoc.ActiveView = user_active_view

for ui_view in uidoc.GetOpenUIViews():
    if ui_view.ViewId != uidoc.ActiveView.Id:
        ui_view.Close()

__window__.Show()

Notes: Tested under Revit 2018.2

daren-thomas commented 6 years ago

I have just tested both versions and can confirm that

  1. the original version (without __window__.Hide()) crashes on my system too
  2. the proposed workaround by @eirannejad works on my system (doesn't crash)

I think this is ugly, but don't really see another way to do this...