Closing Open Floor Views/deleting them

Hi, in a very big script to export stuff I included a python script to delete not needed views.
Errors occour when one of the to be deleted views is open before starting the script.
Currently the only solution is for the user to not have those views open at all, but it would be more elegant if Revit opens an alternative view (because no views open isnt possible), closes the others and then deletes them.

I wasnt able to find useful solutions to my specific issue in the forum.

Note: IN[0] is the currently open view, IN[1] is the alternative view, rest are views that should be decoupled/made indepentend from other refrenced views and then deleted.

import clr
clr.AddReference('RevitAPI')
clr.AddReference('RevitServices')
clr.AddReference('System')

from Autodesk.Revit.DB import Transaction, View
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
from System.Collections.Generic import List

doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
app = uiapp.Application

view_to_check_and_close = UnwrapElement(IN[0])
view_to_open = UnwrapElement(IN[1])

# All views except 0 + 1 are added to a list for further processing
remaining_views = [UnwrapElement(view) for view in IN[2:]]

def is_view_open(view):
    open_views = uiapp.ActiveUIDocument.GetOpenUIViews()
    for ui_view in open_views:
        if ui_view.ViewId == view.Id:
            return ui_view
    return None

TransactionManager.Instance.TransactionTaskDone()

if not is_view_open(view_to_open):
    uiapp.ActiveUIDocument.RequestViewChange(view_to_open)

TransactionManager.Instance.EnsureInTransaction(doc)

open_view = is_view_open(view_to_check_and_close)
open_views = uiapp.ActiveUIDocument.GetOpenUIViews()

# This should close the currently open and to be deleted view. Deoesnt work tho
if open_view and len(open_views) > 1:
    open_view.Close()

if not is_view_open(view_to_check_and_close):
    try:
        doc.Delete(view_to_check_and_close.Id)
    except Exception as e:
        OUT = "Failed to delete View(-s): " + str(e)
        TransactionManager.Instance.TransactionTaskDone()

indepentend_views = []

# Gets the Views, checks if they are dependent, makes them independent, deletes the selected views
for view in remaining_views:
    dependent_view_ids = view.GetDependentViewIds()
    if len(dependent_view_ids) > 0:
        for dependent_view_id in dependent_view_ids:
            dependent_view = doc.GetElement(dependent_view_id)
            if dependent_view.CanBeDependent:
                independent_view_id = dependent_view.Duplicate(ViewDuplicationOption.WithDetailing)
                independent_view = doc.GetElement(independent_view_id)
                independent_views.append(independent_view)
                doc.Delete(dependent_view_id)
    try:
        doc.Delete(view.Id)
    except Exception as e:
        OUT = "Failed to delete View(-s): " + str(e)
        TransactionManager.Instance.TransactionTaskDone()

TransactionManager.Instance.TransactionTaskDone()

OUT = "Refrenced Views were made indepentend, unneeded Views deleted"

I would very much oppose to deleting views in a graph that isn’t specifically used for deleting views.

When you delete a view, you lose the ability to revert changes made manually/by graph. So you will never be able to undo what you did with this graph, because you deleted a view. I think you should make a graph specifically for deleting the views instead of merging the two functionalities.

1 Like

I do not believe that you can ‘change the view’ and then run the rest of the graph in a single Dynamo action. Instead setup the Python to check if the view is scheduled to be deleted before performing the rest of the action, and if so trigger a UI instructing the user to close all views except for the desired “alternative view” and proceed from there.

I’m hoping that this is intended to be done in a detached copy to prevent loss of data as @Garbage_Collector indicated. :slight_smile:

1 Like

Why does that matter? The script runs for an export of an .rvt file, the architect only links to our objects within that as well, it’s only to remove WIP views.
Doing it in one go would be better, the entire graph is already confusing and large enough

problem is, the program already runs through several scripts before that right now.

How do I give an abort command in python? Because my current approach is just to give information from one script to the next via the OUT = process and attaching new results to the string, scanning the string for any “Errors” and then skipping certain things.
I would need to stop the script pretty much instantly on start

Then you need to do one of the following:

  • Trigger the view check BEFORE any other action is taken
  • Break the graph into parts to allow the other things to complete first
  • Switch from Dynamo graph to a Revit add-in where the UI can safely be controlled

Since you’re not providing the full context of your automation we can’t really help with any of the ‘how’, but there are many threads showing examples of this behavior. Personally I like a method along the lines of this in Python, but you may want something which exposes something other than a warning on the node:

if currentView.Id == desiredView.Id: 
    sys.Exit("\r\rYour active view is not the correct view for export. \r\rPlease set the view the the correct export view and run the Dynamo graph again.\r\r")
1 Like

I will check it before in the first script, but the entire script was a miracle to get running, honestly, I won’t touch the graph much at all.
I guess sys.Exit stops the dynamo script completly, will try that later.

Thx

sys exit will cause the Python node to stop executing and trigger the warning consisting of the string provided as the input. The result for Dynamo is that the node ‘outputs’ a null (as the Python didn’t finish executing there is no data in the OUT), and as a result every node thereafter will produce a ‘null’ as well.

1 Like

You should be able to change the active view with the RequestViewChange method. You’ll likely need a new transaction before you start deleting views though.

EDIT: I may have spoken too soon. There are methods to change the active view, close views, and then delete views, but the application doesn’t seem to refresh the UI with a transaction. Even after opening a new view, the UI still thinks it’s deleting the last open view in the document. I’ll keep looking, but that seems to be the hangup at the moment. It does seem like breaking this graph into multiple steps would help.

2 Likes

Further to Nick’s comments, I have come across this problem when working with the UI
Here are the remarks from RequestViewChange, bold highlight is mine

This method requests to change the active view by posting a message asynchronously. Unlike setting the ActiveView property, this will not make the change in active view immediately. Instead the request will be posted to occur when control returns to Revit from the API context. This method is permitted to change the active view from the Idling event or an ExternalEvent callback.

The active view cannot be changed when:

  • There is an open transaction in the currently active document.
  • IsModifiable is true.
  • IsReadOnly is true.
  • ViewActivating, ViewActivated, or any pre-event (such as DocumentSaving) is being handled.

Here is an example with uidoc.RequestViewChange(UnwrapElement(IN[0]))


The UI will open the view but not until the Dynamo run has completed

2 Likes

Wherein lies the problem - the UI cannot update until AFTER Dynamo finishes executing. So you can change the view and tell the user to run again, or tell the user to set the view and run again, but I do not believe you can use Dynamo to run this end to end in one run.

4 Likes

I did exactly that, it works pretty well, tho I had to make edits to a few other scripts that fired even tho they should not, leading to purging and removing of views even when it detected it’s open.

Knowing what I know now, I would likely do things diffrent with the way I handle outputs of scripts, to not cause these kinds of issues

...
active_view = uiapp.ActiveUIDocument.ActiveView

views_to_check = [UnwrapElement(view) for view in IN]

for view in views_to_check:
    if view is not None and view.IsValidObject: 
        if active_view.Id == view.Id:
            sys.exit(ctypes.windll.user32.MessageBoxW(0, "Currently open View should be deleted. Please open another view and close the WIP view", "To be deleted view is open", 0))
            break
...
1 Like

A workaround using ExternalEvent and ViewActivated event

delete_all_views

code IronPython (to date)

import clr
import sys
import System
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

clr.AddReference('RevitAPI')
import Autodesk
import Autodesk.Revit.DB as DB
from Autodesk.Revit.DB import *

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *
from Autodesk.Revit.UI.Events import ViewActivatedEventArgs, ViewActivatingEventArgs
from Autodesk.Revit.UI import IExternalEventHandler, ExternalEvent

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Transactions import TransactionManager
from RevitServices.Persistence import DocumentManager

doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
uidoc = uiapp.ActiveUIDocument
app = uiapp.Application

from System.Collections.Generic import List, Dictionary
from System import  EventHandler, Uri

clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

def ViewEventZ(sender, e):
	global do_this
	global ext_eventDelete
	print("call ViewEventZ")
	if do_this :
		ext_eventDelete.Raise()
	do_this = False

def dispose_ExternalEvent():
	print("Dispose ModExternalEventDelete")
	ext_eventDelete.Dispose()

class ModExternalEventDelete(IExternalEventHandler):
	def __init__(self, lst_viewIds):
		self.lst_viewIds = lst_viewIds
		
	def Execute(self, _uiap):		
		_uidoc = _uiap.ActiveUIDocument
		_doc = _uidoc.Document
		_view = _doc.ActiveView
		#processing
		tx = Transaction(_doc)
		tx.Start("MyEventDelete")
		_doc.Delete(self.lst_viewIds)
		tx.Commit()     
		dispose_ExternalEvent()
		print("remove handler")
		_uiap.ViewActivated -= EventHandler[ViewActivatedEventArgs](ViewEventZ)
	def GetName(self):
		return "Reset External Event"		
					
					
lst_views = UnwrapElement(IN[0])
lst_viewIds = List[ElementId]([x.Id for x in lst_views])
TransactionManager.Instance.ForceCloseTransaction()
treeD_view = FilteredElementCollector(doc).OfClass(DB.View3D).WhereElementIsNotElementType().FirstOrDefault(lambda x : "{" in x.Name)

do_this = True
obj_handlerDelete = ModExternalEventDelete(lst_viewIds)    
ext_eventDelete = ExternalEvent.Create(obj_handlerDelete)	
# 
uiapp.ViewActivated += EventHandler[ViewActivatedEventArgs](ViewEventZ)
uidoc.RequestViewChange(treeD_view)
OUT = treeD_view
3 Likes

I tried it, got this error (likely from using CPython3).
Which version of IronPython do you have installed?
Is it a packet to download? Or is it unrelated to that?

Thanks alot!

You can install Ironpython 2 or 3 with the package manager

1 Like

I thought it was just that a single 3D view was in the list. Seems not:

Even just 2D Views cause this error

Hi,
Need a list at input IN[0]

1 Like