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.
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.
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
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")
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.
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.
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.
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.
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.
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
...
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?