13 janv. 2021

[Dynamo += Python] Revit et les Événements

 




Lorsque l'on parle de Dynamo et de la gestion des événements Revit (Évents) on pense souvent à une incompatibilité....

if this article is not in your language, use the Google Translate widget (bottom of page for Mobile version)


Par défaut Dynamo ne peut écouter les événements Revit que pendant l'exécution du graphique et Dynamo ne peut s'exécuter qu'à l'intérieur de l'événement inactif de Revit.

L'émetteur d'événements (Revit’s idle event) n'est déclenché que lorsqu'aucune autre interaction ne se produit et lorsqu'il est activé, le contexte de Revit est occupé jusqu'à ce que ce qui a déclenché l'événement s'achève.

Une solution est d'utiliser une boucle d’évènements dans un autre Thread (avec un Winform non bloquant par exemple) ce qui permet au Thread principale de Dynamo de se terminer.


À quoi peut bien servir les événements Revit ?...

Un exemple de cas d'usage courant est la recherche d'éléments dans le projet et de zoomer sur celui-ci. Pour pouvoir effectuer cette opération il faut s'assurer au préalable que la vue ait finit de s'afficher, c'est là qu'intervient l'utilisation d'un événement.

Voici une "reproduction" de la fonction native de Revit ou l'on effectue une recherche d'élément par leur ID (avec entre autres la méthode RequestViewChange(View) et une fois la vue affichée on applique la méthode  ShowElements(ElementId) )






Pour les opérations nécessitant une Transaction  cela devient un peu plus complexe, comme on est un autre Thread (non principal) on doit faire appel à un ExternalEvent 


Voici un exemple ci-dessous qui réalise les actions suivantes :

  • Liste les changements de vue (via les Évents).
  • Liste les éléments modifiés (via les Évents).
  • Colorise la sélection d'éléments en cours si des éléments viennent d'êtres modifiés. (on pourrait également colorisé tous les éléments modifiés mais j'ai simplifié ici) 

import sys
import clr
import System 
from System import EventHandler, Uri

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

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.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

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

clr.AddReference('System.Drawing')
clr.AddReference('System.Windows.Forms')
import System.Drawing
import System.Windows.Forms

from System.Drawing import *
from System.Windows.Forms import *

class MainForm(Form):
    def __init__(self, ext_event, uiapp, app):
        self._uiapp = uiapp
        self._app = app
        self._ext_event = ext_event
        self.InitializeComponent()
    
    def InitializeComponent(self):
        self._buttonCancel = System.Windows.Forms.Button()
        self._richTextBox1 = System.Windows.Forms.RichTextBox()
        self._label1 = System.Windows.Forms.Label()
        self.SuspendLayout()
        # self._uiapp.ViewActivating += EventHandler[ViewActivatingEventArgs](self.ViewEvent)
        # self._app.DocumentChanged += EventHandler[DocumentChangedEventArgs](self.ModifEvent)
        self._delegateViewEvent = EventHandler[ViewActivatingEventArgs](self.ViewEvent)
        self._delegateModifEvent = EventHandler[DocumentChangedEventArgs](self.ModifEvent)
        self._uiapp.ViewActivating += self._delegateViewEvent
        self._app.DocumentChanged += self._delegateModifEvent
        # 
        # buttonCancel
        self._buttonCancel.Location = System.Drawing.Point(150, 550)
        self._buttonCancel.Name = "buttonStop"
        self._buttonCancel.Size = System.Drawing.Size(156, 37)
        self._buttonCancel.TabIndex = 0
        self._buttonCancel.Text = "Stop Loop Event"
        self._buttonCancel.UseVisualStyleBackColor = True
        self._buttonCancel.Click += self.ButtonCancelClick
        # 
        # richTextBox1
        self._richTextBox1.Location = System.Drawing.Point(30, 37)
        self._richTextBox1.Name = "richTextBox1"
        self._richTextBox1.Size = System.Drawing.Size(400, 500)
        self._richTextBox1.TabIndex = 1
        self._richTextBox1.Text = "Processing..."
        # 
        # label1
        self._label1.Location = System.Drawing.Point(30, 9)
        self._label1.Name = "label1"
        self._label1.Size = System.Drawing.Size(152, 25)
        self._label1.TabIndex = 2
        self._label1.Text = "Revit Events"
        # 
        # MainForm
        self.ClientSize = System.Drawing.Size(480, 600)
        self.Controls.Add(self._label1)
        self.Controls.Add(self._richTextBox1)
        self.Controls.Add(self._buttonCancel)
        self.Name = "MainForm"
        self.Text = "UI_Event"
        self.ResumeLayout(False)
        
    def ViewEvent(self, sender, e): 
        #prevent if unregister  event failed
        if not self.Controls[0].IsDisposed:
            newViewName = e.NewActiveView.Name
            currentText = self._richTextBox1.Text
            self._richTextBox1.Text = currentText + "\nSwitch to View ->" + newViewName
        
    def ModifEvent(self, sender, e):
        #prevent if unregister  event failed
        if not self.Controls[0].IsDisposed:
            lstElemNam = []
            lstElemdIds = e.GetModifiedElementIds()
            for xId in lstElemdIds:
                elem = doc.GetElement(xId)
                if elem is not None or int(xId) != 1:
                    lstElemNam.append(elem.Name + "Id :"+ str(xId.IntegerValue))
            if len(lstElemNam) > 0: 
                currentText = self._richTextBox1.Text
                self._richTextBox1.Text = currentText + "\nModification Elements:\n" + "\n".join(lstElemNam)
                self._ext_event.Raise()
        
    def ButtonCancelClick(self, sender, e):
        self._ext_event.Dispose()
        self.Close()
        try:
            self._uiapp.ViewActivating -= self._delegateViewEvent
        except:
            self._uiapp.ViewActivating -= self.ViewEvent
        try:
            self._app.DocumentChanged -= self._delegateModifEvent
        except:
            self._app.DocumentChanged -= self.ModifEvent
        self.Dispose()
        
class ModExternalEvent(IExternalEventHandler):
    def Execute(self, _uiap):
        _uidoc = _uiap.ActiveUIDocument
        _doc = _uidoc.Document
        _view = _doc.ActiveView
        #processing
        tx = Transaction(_doc)
        tx.Start("MyEvent")
        rvtcolor = Autodesk.Revit.DB.Color(255, 0, 0)
        gSettings = OverrideGraphicSettings()
        gSettings.SetProjectionFillColor(rvtcolor)
        gSettings.SetProjectionLineColor(rvtcolor)
        gSettings.SetCutLineColor(rvtcolor)
        gSettings.SetCutFillColor(rvtcolor) 
        for elid in _uidoc.Selection.GetElementIds():
            _view.SetElementOverrides(elid, gSettings)
        tx.Commit()     
    
    def GetName(self):
        return "Test External Event"

obj_handler = ModExternalEvent()    
ext_event = ExternalEvent.Create(obj_handler)
objEvent =  MainForm(ext_event, uiapp, app)
objEvent.Show()
OUT = 0
  

un autre Exemple en WPF et PythonNet avec :

  • tracking de modification d'éléments 
  • se désabonner d'un Événement API Revit "DocumentChanged" dans le cas d'une fenêtre non Modale (unregister Revit API event handler in Modeless Window WPF or Form) 

import clr
import sys
import System 
from System import EventHandler, Uri
my_path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments)

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

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

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

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

clr.AddReference("System.Xml")
clr.AddReference("PresentationFramework")
clr.AddReference("System.Xml")
clr.AddReference("PresentationCore")
clr.AddReference("System.Windows")
import System.Windows.Controls 
from System.Windows.Controls import *
import System.Windows.Controls.Primitives 
from System.Collections.Generic import List
from System.IO import StringReader
from System.Xml import XmlReader
from System.Windows import LogicalTreeHelper 
from System.Windows.Markup import XamlReader, XamlWriter
from System.Windows import Window, Application

import time
import traceback

class MainForm(Window):
    string_xaml = '''
        <Window 
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                Title="UI_Event" Height="600" Width="480">
            <Grid>
                <!-- Label -->
                <Label x:Name="label1" Content="Revit Events" HorizontalAlignment="Left" VerticalAlignment="Top"
                       Margin="30,9,0,0" FontSize="16" />
        
                <!-- RichTextBox -->
                <RichTextBox x:Name="richTextBox1" HorizontalAlignment="Center" VerticalAlignment="Top"
                             Margin="5,45,5,5" Height="472">
                    <FlowDocument>
                        <Paragraph>Processing...</Paragraph>
                    </FlowDocument>
                </RichTextBox>
        
                <!-- Button -->
                <Button x:Name="buttonCancel" Content="Stop Loop Event" HorizontalAlignment="Center"
                        VerticalAlignment="Bottom" Margin="5,5,5,5" Width="156" Height="37" />
            </Grid>
        </Window>'''
  
    def __init__(self, uiapp, app):
        super().__init__()
        self._app = app
        self.start_time = time.time()
        xr = XmlReader.Create(StringReader(MainForm.string_xaml))
        self.winLoad = XamlReader.Load(xr) 
        self.event_unregister = None

        self.InitializeComponent()
        
    def InitializeComponent(self):
        #
        self._richTextBox = LogicalTreeHelper.FindLogicalNode(self.winLoad, "richTextBox1")
        #
        self.buttonCancel = LogicalTreeHelper.FindLogicalNode(self.winLoad, "buttonCancel")
        self.buttonCancel.Click += self.ButtonCancelClick
        
        self.handler = EventHandler[DocumentChangedEventArgs](self.ModifEvent)
        self._app.DocumentChanged += self.handler
        #
        self.winLoad.Loaded += self.OnLoad
        self.winLoad.ContentRendered  += self.OnContentRendered
        #
        
    def ConvertB64_to_BitmapImage(self, b64_value):
        picInst = System.Convert.FromBase64String(b64_value)
        bi = System.Windows.Media.Imaging.BitmapImage()
        bi.BeginInit()
        bi.StreamSource = System.IO.MemoryStream(picInst)
        bi.EndInit()
        return bi
        
    def ModifEvent(self, sender, e):
        #prevent if unregister  event failed
        try:
            print("ModifEvent6")
            source = System.Windows.PresentationSource.FromVisual(self.winLoad)
         
            if source is not None and not source.IsDisposed:
                lstElemNam = []
                modified_element_ids = e.GetModifiedElementIds()
                transaction_names = e.GetTransactionNames()
                lst_elem_names = []
                for elem_id in modified_element_ids:
                    element = doc.GetElement(elem_id)
                    if element:
                        lst_elem_names.append(f"{element.Name} (Id: {elem_id.IntegerValue})")
        
                # Update the RichTextBox
                if lst_elem_names:
                    paragraph_text = f"\n\nLIST ELEMENTS:\n" + "\n".join(lst_elem_names)
                    paragraph_text += f"\n\nTRANSACTION NAME:\n" + "\n".join(transaction_names)
                    paragraph_text += f"\n\nUSER NAME: {self._app.Username}"
                    paragraph_text += "\n###########################################"
        
                    # Create a new paragraph for the new content
                    new_paragraph = System.Windows.Documents.Paragraph()
                    new_paragraph.Inlines.Add(paragraph_text)
            
                    # Append the paragraph to the RichTextBox's FlowDocument
                    flow_document = self._richTextBox.Document
                    flow_document.Blocks.Add(new_paragraph)
                    
        except Exception as ex:
            print(traceback.format_exc())

    def OnContentRendered(self, sender, e):
        print(f"UI show in {time.time() - self.start_time} seconds")
        
    def OnLoad(self, sender, e):
        try:
           print("UI loaded")     
        except Exception as ex:
            print(traceback.format_exc())

    def ButtonCancelClick(self, sender, e):
        try:
            self.event_unregister.Raise() 
            self.winLoad.Close()
            #self.event_unregister.Dispose() 
        except Exception as ex:
            print(ex)

class EventUnRegisterHandler :
    def __new__(cls, *args, **kwargs):
        cls.args = args
        # name of namespace : "CustomNameSpace" + "_" + shortUUID (8 characters)
        # IMPORTANT NOTE Each time you modify this class, you must change the namesapce name.
        cls.__namespace__ = "EventRegisterHandler_QTRGDE"
        try:
            # 1. Try to import the module and class. If it already exists, you've probably already created 
            module = __import__(cls.__namespace__)
            # import class
            return module._InnerClassInterface(*cls.args)
        except Exception as ex:
            print(ex)
            class _InnerClassInterface(IExternalEventHandler):
                __namespace__ = EventUnRegisterHandler.__namespace__
                def __init__(self, wpf_form):
                    super().__init__()
                    self.wpf_form = wpf_form
                    
                def Execute(self, _uiap):        
                    print("remove Event")
                    try:
                        _uidoc = _uiap.ActiveUIDocument
                        _doc = _uidoc.Document
                        _view = _doc.ActiveView
                        #processing
                        self.wpf_form._app.DocumentChanged -= self.wpf_form.handler
                        self.wpf_form.event_unregister.Dispose()
                    except Exception as ex:
                        print(ex)

                def GetName(self):
                    return "EventRegisterHandler"        
                    
            return _InnerClassInterface(*cls.args)
            
objForm = MainForm(uiapp, app)
obj_handlerUnRegister = EventUnRegisterHandler(objForm)    
ext_eventUnRegister = ExternalEvent.Create(obj_handlerUnRegister)    
objForm.event_unregister = ext_eventUnRegister

objForm.winLoad.Show()
OUT = 0
   
Note
L'exemple a été fait sous Revit 2019 et le code ne prends pas compte les divers changements d'API comme la classe DB.OverrideGraphicSettings 



Aperçu en Vidéo 



Bonne Année, prenez soin de vous et de vos proches

0 commentaires:

Enregistrer un commentaire