15 sept. 2025

[Dynamo += Python] PythonNet série : Aide Migration vers PythonNet3

 


Vous voulez anticiper la migration de votre code Python vers PythonNet3, bonne décision, voici quelques conseils.


#DynamoBIM #Python #RevitAPI  #PythonNet3 #AutodeskExpertElite #AutodeskCommunity 

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


Vous découvrirez dans cet article différents conseils et astuces (non exhaustifs) pour passer à PythonNet3.

Cet article s'adresse à tous, mais par défaut on va prendre le cas le plus extrême, une migration depuis IronPython2.7 vers PythonNet3.

Si vous utilisez déjà CPython3 il est probable que certains conseils vous soient déjà familiers.


Résumé audio (IA)


📌Pourquoi PythonNet3 ? 

Dynamo_PythonNet3 est le nouveau moteur Python pour Dynamo, basé sur CPython 3.11 et la librairie Python.NET v3. Il vise à rapprocher l’expérience “Python dans Dynamo” de ce qu’on avait avec IronPython, tout en profitant de tout l’écosystème CPython (numpy, pandas, etc.). 

Python.NET crée un pont bidirectionnel entre CPython et le CLR/.NET : il permet à du code Python d’utiliser des assemblies .NET et, inversement, d’embarquer Python dans une appli .NET.

Dynamo_PythonNet3 existe à ce jour en tant que package et sera le moteur par défaut dans les futures versions de Dynamo. Les codes réalisés avec CPython3 devraient s'exécuter sans problème avec ce nouveau moteur.

Voici un résumé des différentes versions de Python disponibles dans Dynamo à ce jour.
Caractéristiques IronPython 2.7 IronPython 3 Python.NET 2.5.x Python.NET 3.x
Nom Dynamo IronPython2 IronPython3 CPython3 PythonNet3
Concept Principal Implémentation de Python sur .NET
(avec DLR)
Implémentation de Python sur .NET
(avec DLR)
Passerelle vers CPython Passerelle vers CPython
Version Python Python 2.7 Python 3.4  (actuellement) Python 2.7,
Python 3.5-3.9
Python 3.7+ (moderne)
Support .NET .NET Framework & .Net Core/ 
.NET 6/7/8
Incompatibilité avec les futures versions .NET
.NET Framework & .NET Core / .NET 5+ .NET Framework (principalement) .NET Framework & .NET Core / .NET 5+
Compatibilité Librairies ⚠️ Faible. Incompatible avec les librairies Python qui utilisent des extensions en C (ex: NumPy, Pandas, Scikit-learn). ⚠️ Faible. Même limitation fondamentale que la v2.7, bien que l'écosystème pur Python 3 soit plus riche. Excellente. Accès à tout l'écosystème de CPython. Excellente. Accès à tout l'écosystème PyPI moderne (NumPy, Pandas, etc.).
Performance Bonne pour l'interopérabilité .NET pure. Bonne pour l'interopérabilité .NET pure. Dépend de CPython. Léger surcoût pour les appels. Dépend de CPython. Léger surcoût pour les appels, mais très optimisé.
Correction de bugs.
État du Projet Obsolète. Plus maintenu activement. Actif. La version moderne et recommandée si vous choisissez IronPython. Obsolète. Remplacé par la version 3.x. Actif. Le standard de l'industrie pour l'interopérabilité Python/.NET.



📌Migration Python2.7 vers Python3 



D'abord, si vous migrez depuis IronPython2.7, il faudra prendre en considération les nouvelles syntaxes de Python3.
Il existe plusieurs guides sur le Web.
https://docs.python.org/3/howto/pyporting.html


  • Exemple 

import clr
clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import DockStyle

dockstyle = DockStyle.None
logger.warn(dockstyle)

devient

import clr
clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import DockStyle

dockstyle = getattr(DockStyle, 'None')
logger.warning(dockstyle)

📌Nouveautés et changements majeurs de PythonNet3 

Si vous migrez depuis CPython3 qui est basé sur PythonNet2.5, voici une liste des principaux changements et nouveautés.

  • Changements majeurs (BREAKING CHANGES)

    • Héritage et constructeurs : si vous surchargez la méthode __init__ d'un type .NET en Python, vous devez dorénavant appeler explicitement le constructeur de la classe de base en utilisant super().__init__(...)
    • Conversions d'énumérations : la conversion implicite entre les énumérations C# et les entiers Python est désactivée. Vous devez à présent utiliser les membres de l'énumération (par exemple, MonEnum.Option ). 
De plus, la méthode .NET Enum.Value.ToString() retourne désormais le nom de la valeur au lieu de son entier.
      • Collections et tableaux : les collections et tableaux .NET ne sont plus convertis automatiquement en collections Python.
        Néanmois, ils implémentent les interfaces de collections Python standard de collections.abc. 

        Ce changement permet d'utiliser des méthodes .NET comme LINQ directement sur ces objets.

    • Améliorations et corrections de bugs

      • PyObject implémente maintenant IEnumerable en plus de IEnumerable<T>. Un bug où toutes les instances de classes .NET étaient considérées comme Iterable a été corrigé.
        Ce qui résout des problèmes avec hasattr(pyObject, "__iter__").
      • Vous pouvez maintenant surcharger des méthodes .NET en Python qui utilisent les paramètres out ou ref. Pour ce faire, vous devez retourner les valeurs de ces paramètres modifiés sous forme de tuple.

      • Les opérateurs arithmétiques binaires et unaires de Python appellent désormais les méthodes d'opérateurs C# correspondantes.
      • La prise en charge de la surcharge des méthodes a été améliorée, y compris les méthodes ayant des paramètres de type générique (<T>).
      • La possibilité d'utiliser simplement des Interfaces de Classe .NET (feature Dynamo).

      • Extension de méthodes LINQ sur les IEnumerable<T>  (feature Dynamo).

    📌Net Framework 4.x VS .Net 8+ (Net Core) 


    Dynamo_PythonNet3 est disponible à partir de Revit 2025 en tant que package, ce qui signifie qu'il est exécuté sous .Net8+ au lieu du .Net Framework 4.x.

    Cela entraîne certaines conséquences, en voici quelques une :

    • Accès aux objets COM
      • Impossible d'utiliser directement les "assemblies" présentes dans le G.A.C. Windows (comme Excel Interop ou Word Interop).

      • Necessité d'utiliser la Reflexion pour acceder aux objets COM (contrairement a IronPython qui utilise le DLR).

    Ainsi cette ligne échouera.
      
    clr.AddReference(“Microsoft.Office.Interop.Excel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c”)
    
    Si vous vous trouvez dans ce cas, envisagez de migrer vers OpenXML SDK ou openpyxl.

    Si pour une raison quelconque, vous avez besoin d'utiliser Excel Interop ou une interface COM, il faudra :
    • Potentiellement créer une instance du Type d'Application
    • Utiliser la Reflexion .Net (pas d'acces direct aux méthodes et propriétés comme sur IronPython)
        
    import clr
    import sys
    import System
    from System import Array
    from System.Collections.Generic import List, Dictionary, IDictionary
    
    clr.AddReference("System.Reflection")
    from System.Reflection import BindingFlags
    
    from System.Runtime.InteropServices import  Marshal
    
    clr.AddReference("System.Core")
    clr.ImportExtensions(System.Linq)
    
    xls_filePath = IN[0]
    xls_SheetName = IN[1]
    dict_values = {}
    
    systemType = System.Type.GetTypeFromProgID("Excel.Application", True)
    try:
        ex = System.Activator.CreateInstance(systemType)
    except:
        methodCreate = next((m for m in clr.GetClrType(System.Activator)\
                        .GetMethods() if "CreateInstance(System.Type)" in m.ToString()), None)
        ex = methodCreate.Invoke(None, (systemType, ))
        
    ex.Visible = False
    workbooks = ex.GetType().InvokeMember("Workbooks", BindingFlags.GetProperty , None, ex, None)
    workbook = workbooks.GetType().InvokeMember("Open", BindingFlags.InvokeMethod , None, workbooks, (xls_filePath, )) 
    worksheets = workbook.GetType().InvokeMember("Worksheets", BindingFlags.GetProperty , None, workbook, None)
    #
    ws = worksheets.GetType().InvokeMember("Item", BindingFlags.GetProperty , None, worksheets, (xls_SheetName,))

    • Windows.Forms 
      • Windows.Forms contient une nouvelle Classe TaskDialog qui peut être en conflit avec la classe de l'API RevitAPIUI .

      • La taille du Font par défaut a changé.
    Solution pour rétablir la taille du Font (WinForms)
    
    # in the __init__ function
    self.Font = System.Drawing.SystemFonts.DefaultFont


    • Changement d'Espaces de Noms

    Certaines technologies spécifiques à Windows ou obsolètes ont été supprimées ou remplacées par de nouvelles implémentations avec de nouveaux espaces de noms. 


    • Changement de la méthode Diagnostics.Process.Start()
    ProcessStartInfo.UseShellExecute a une valeur par défaut de False sur .NET Core
    (sur .NET Framework, sa valeur par défaut est True).
    
    psi = ProcessStartInfo()
    psi.FileName = fullpath
    psi.UseShellExecute = True
    System.Diagnostics.Process.Start(psi)

    • Marshal.GetActiveObject()

    La méthode pour obtenir  l'instance COM en cours d'exécution d'un objet spécifié   Marshal.GetActiveObject() n'est plus disponible.

    Vous pouvez utiliser BindToMoniker si vous connaissez le chemin du fichier en cours d'utilisation.
      
    import clr
    import os
    import time
    import System
    
    clr.AddReference("System.Reflection")
    from System.Reflection import BindingFlags
    
    clr.AddReference("AcMgd")
    clr.AddReference("AcCoreMgd")
    clr.AddReference("Autodesk.AutoCAD.Interop")
    
    from System import *
    
    from Autodesk.AutoCAD.Runtime import *
    from Autodesk.AutoCAD.ApplicationServices import *
    from Autodesk.AutoCAD.Interop import *
    from Autodesk.AutoCAD.ApplicationServices import Application as acapp
    
    changeViewCommand = "_VIEW "
    
    adoc = Application.DocumentManager.MdiActiveDocument
    currentFileName = adoc.Name
    print(currentFileName)
    com_doc = System.Runtime.InteropServices.Marshal.BindToMoniker(currentFileName)
    args = System.Array[System.Object]([changeViewCommand])
    com_doc.GetType().InvokeMember("SendCommand", BindingFlags.InvokeMethod, None, com_doc, args)
    
    OUT = True
    

    Vous pouvez essayer avec Microsoft.Visualbasic.Interaction.GetObject() , personnellement je n'ai jamais eu de bons résultats.


    Vous pouvez retrouver une liste complète ici.

    📌Conseils et Astuces 


    • Respecter les signatures.

    La signature d'une méthode doit être bien respectée, ce qui implique :

      • le nom de la méthode doit être correct.

      • la méthode doit être appelée sur le bon type d'objet (méthode d'instance VS méthode statique).

      • les types des objets passés en arguments doivent être corrects.

    Dans cet exemple avec IronPython, nous pouvons faire ceci.
      
    from System.Collections.Generic import List
    # some imports
    selelemz=[ElementId(124291), ElementId(124292), ElementId(124293)]
    elements=FilteredElementCollector(doc,selelemz).WhereElementIsNotElementType().ToElements()
    


    Avec PythonNet, il faut caster/convertir la liste Python. 
      
    from System.Collections.Generic import List
    # some imports
    selelemz=List[ElementId][ElementId(124291), ElementId(124292), ElementId(124293)])
    elements=FilteredElementCollector(doc,selelemz).WhereElementIsNotElementType().ToElements()
    

    • Les Collections .Net 

    Comme mentionné plus haut, PythonNet3 ne convertit plus les .Net Collection liste Python native.

      • Plus besoin de caster/convertir vers une .Net Collection lorsqu'on appelle une méthode .Net

      • Possibilité d'utiliser les extensions Linq (feature by Dynamo).
      Résumé du comportement des listes dans les différents moteurs Python

      Caractéristique IronPython(2 ou 3) Python.NET 2 (alias CPython3) Python.NET 3
      Mécanisme de conversion Les collections.NET sont intégrées de manière native, sans conversion ni copie de données. Conversion automatique et implicite en listes Python natives. La conversion automatique a été supprimée. Les collections.NET implémentent maintenant les interfaces de collections standard de Python (collections.abc).
      Conséquences pour le développeur Les collections.NET se comportent comme des objets Python, mais les manipulations ne génèrent pas de copies. C'est performant, mais le framework ne prend pas en charge les bibliothèques CPython comme NumPy. Pratique mais coûteux en performance à cause d'une copie profonde des données. Les modifications apportées à la liste Python ne sont pas répercutées sur l'objet.NET d'origine. L'objet est une "vue" de la collection.NET, sans copie de données, ce qui améliore la performance. Les opérations de base (comme l'itération) fonctionnent, mais les méthodes spécifiques aux listes Python (comme append()) nécessitent une conversion explicite via list().

      Possibilité d'utiliser les extensions LINQ


      REMARQUES:

          • Utilisation des méthodes d'extension LINQ

      Voir mon article à ce sujet.
      https://voltadynabim.blogspot.com/2025/03/dynamo-python-dspytonnet3-et-linq.html

      À titre d'exemple, si vous utilisiez précédemment les Predicate sur des IList<T>, vous pouvez migrer ainsi :

      Exemple 1

      
      fillPatterns = FilteredElementCollector(doc).OfClass(FillPatternElement).ToElements().FindAll(lambda x : x.GetFillPattern().GridCount  == 2)
      devient
      
      clr.AddReference("System.Core")
      clr.ImportExtensions(System.Linq)
      
      fillPatterns = FilteredElementCollector(doc).OfClass(FillPatternElement)\
                      .Where(System.Func[DB.Element, System.Boolean](lambda x : x.GetFillPattern().GridCount  == 2))\
                      .ToList()
      
      Exemple 2
      
      fillPattern = FilteredElementCollector(doc).OfClass(FillPatternElement).ToElements().Find(lambda x : x.GetFillPattern().GridCount  == 2)
      

      devient      
      
      clr.AddReference("System.Core")
      clr.ImportExtensions(System.Linq)
      
      fillPattern = FilteredElementCollector(doc).OfClass(FillPatternElement).FirstOrDefault(System.Func[DB.Element, System.Boolean](lambda x : x.GetFillPattern().GridCount  == 2))
      

          • Attention, IEnumerable<T> ≠ List<T>
      L'indexation avec les crochets [index] n'est pas possible sur IEnumerables.

      Voici un exemple avec PythonNet3 où nous ne pouvons pas utiliser l'indexation car la méthode face.ToProtoType() retourne un IEnumerable et non une IList<T>

      
      import clr
      import sys
      import System
      #
      clr.AddReference('ProtoGeometry')
      from Autodesk.DesignScript.Geometry import *
      import Autodesk.DesignScript.Geometry as DS
      
      #import Revit API
      clr.AddReference('RevitAPI')
      import Autodesk
      from Autodesk.Revit.DB import *
      import Autodesk.Revit.DB as DB
      
      clr.AddReference('RevitNodes')
      import Revit
      clr.ImportExtensions(Revit.Elements)
      clr.ImportExtensions(Revit.GeometryConversion)
      clr.ImportExtensions(Revit.GeometryReferences)
      
      clr.AddReference("System.Core")
      clr.ImportExtensions(System.Linq)
      
      element = UnwrapElement(IN[0])
      
      ref = HostObjectUtils.GetSideFaces(element, ShellLayerType.Exterior)[0] # GetSideFaces return an Ilist so we can use indexer
      face = element.GetGeometryObjectFromReference(ref)
      ds_surface = face.ToProtoType()[0] # ToProtoType() on Revit face return an IEnumerable so we can't use indexer
      OUT = ds_surface
      

      Une solution consiste à convertir l'IEnumerable en liste Python ou  à utiliser directement une méthode d'extension LINQ ElementAt() ou First()
      
      import clr
      import sys
      import System
      #
      clr.AddReference('ProtoGeometry')
      from Autodesk.DesignScript.Geometry import *
      import Autodesk.DesignScript.Geometry as DS
      
      #import Revit API
      clr.AddReference('RevitAPI')
      import Autodesk
      from Autodesk.Revit.DB import *
      import Autodesk.Revit.DB as DB
      
      clr.AddReference('RevitNodes')
      import Revit
      clr.ImportExtensions(Revit.GeometryConversion)
      
      clr.AddReference("System.Core")
      clr.ImportExtensions(System.Linq)
      
      element = UnwrapElement(IN[0])
      
      ref = HostObjectUtils.GetSideFaces(element, ShellLayerType.Exterior)[0] # GetSideFaces return an Ilist so we can use indexer
      face = element.GetGeometryObjectFromReference(ref)
      ds_surface = face.ToProtoType().First() # ToProtoType() on Revit face return an IEnumerable so we can use LINQ
      # OR convert to python list
      ds_surface = list(face.ToProtoType())[0] # ToProtoType() on Revit face return an IEnumerable so we need convert to python list to user indexer
      OUT = ds_surface


      • Profiter des librairies Python

      Si d'origine vous utilisiez IronPython(2 ou 3) vous pourrez désormais profiter des librairies Python telles que :
        • numpy
        • pandas
        • openpyxl
        • scipy
      Voici quelques exemples d'utilisation avec scipy


      • Classe qui hérite d'un type .Net¶

      Désormais dans les classes qui héritent d'un type .Net (Winform, WPF, DataTable, Interface), si vous surchargez la méthode __init__ , vous devez dorénavant appeler explicitement le constructeur (initialisateur) de la classe de base en utilisant super().__init__(...)


      Exemple:
      
      class TestForm(Form):
          def __init__(self):
              super().__init__() # add this line
              self.Font  = System.Drawing.SystemFonts.DefaultFont
              self.InitializeComponent()
          
          def InitializeComponent(self):
              self._buttonCancel = System.Windows.Forms.Button()
              self._buttonOK = System.Windows.Forms.Button()
              self.SuspendLayout()
      

      • Implémentation des Interfaces de Classe .NET

      Dans le CPython3 (PythonNet2.5) il est impossible d'utiliser facilement des interfaces de classe .Net, c'est désormais corrigé avec Dynamo_PythonNet3.

      Rappel:
      Une classe Python qui dérive d'une classe .NET  doit possèder l'attribut __namespace__

        
      Voir cet article pour plus de détails
      https://voltadynabim.blogspot.com/2024/10/dynamo-python-pythonnet-et-les.html

      Exemple

      
      class Custom_SelectionElem(ISelectionFilter):
          __namespace__ = "SelectionNameSpace_tEfYX0DHE"
          #
          def __init__(self, bic):
              super().__init__() # necessary  if you override the __init__ method
              self.bic = cls.builtInCategory
              
          def AllowElement(self, e):
              if e.Category.Id == ElementId(self.bic):
                  return True
              else:
                  return False
          def AllowReference(self, ref, point):
              return True 
      
      #         
      
      class Custom_FamilyOption(IFamilyLoadOptions) :
          __namespace__ = "FamilyOptionNameSpace_tEfYX0DHE"
      
          def __init__(self):
              super().__init__() # necessary  if you override the __init__ method
              
          def OnFamilyFound(self, familyInUse, _overwriteParameterValues):
              overwriteParameterValues = True
              return (True, overwriteParameterValues)
      
          def OnSharedFamilyFound(self, sharedFamily, familyInUse, source, _overwriteParameterValues):
              overwriteParameterValues = True      
              return (True, overwriteParameterValues)
      

      • MAJ Ref Out Parameters
       
      La syntaxe est différente sous IronPython et PythonNet3.

      Avec PythonNet3, lorsqu'une méthode comporte des paramètres out ou ref, les arguments apparaissent comme des arguments normaux en Python, mais la valeur de retour de la méthode est modifiée. 

      Exemple avec IronPython.
      
      curvA = cableTray1.Location.Curve
      curvB = cableTray2.Location.Curve
      outrefClosest = clr.Reference[IList[ClosestPointsPairBetweenTwoCurves]](List[ClosestPointsPairBetweenTwoCurves]())
      curvA.ComputeClosestPoints(curvB, True, True, True, outrefClosest)
      listOfPoint = [[x.XYZPointOnFirstCurve.ToPoint(), x.XYZPointOnSecondCurve.ToPoint()] for x in outrefClosest.Value]
      
      devient 
      
      curvA = cableTray1.Location.Curve
      curvB = cableTray2.Location.Curve
      outrefClosest = IList[ClosestPointsPairBetweenTwoCurves](List[ClosestPointsPairBetweenTwoCurves]())
      outrefClosestA = curvA.ComputeClosestPoints(curvB, True, True, True, outrefClosest)
      listOfPoint = [[x.XYZPointOnFirstCurve.ToPoint(), x.XYZPointOnSecondCurve.ToPoint()] for x in outrefClosestA]
      
      Lien documentation

      Attention, il y a une différence avec l'ancienne version "PythonNet2.5" lorsque la méthode ne retourne rien (Void)

      • "System.Object" VS "object"
      Avec IronPython "object" retourne l'objet .Net System.Object

      Avec PythonNet "object" est l'objet intégré à Python (built-in), et non l'objet .NET System.Object.

      Ainsi cette ligne échouera.
      
      a = Array.CreateInstance(object, nbr_row, nbr_colum)
      
      à remplacer par
      
      a = Array.CreateInstance(System.Object, nbr_row, nbr_colum)
      


      📌Problèmes connus et solutions 


      • Propriétés de type "Indexer"
      Remplacer les propriétés de type "Indexer" par la méthode get_

      space = elem.Space[phase]
      
      à remplacer par
      
      space = elem.get_Space(phase)
      


      • Pas de librairie WPF comme IronPython

      Néanmoins, avec quelques concessions sur du Binding, il est toujours possible d'utiliser WPF (même avec le pattern MVVM) via XamlReader.Load(StringReader(xaml)) 

      Vous trouverez des exemples ici
      https://voltadynabim.blogspot.com/2025/02/dynamo-python-pythonnet-serie-wpf-et-le.html


      • Erreur sur les collections python set()

      La collection Python set() n'accepte pas les objets de type ElementId.InvalidElementId.
      Il est nécessaire de retirer ce type d'objet dans la construction d'un d'un  set() ou d'utiliser la classe .Net  HashSet<T>  .

      
      setViewsIds = set([w.OwnerViewId for w in allWires])
      

      à remplacer par
      
      setViewsIds = set([w.OwnerViewId for w in allWires if w.OwnerViewId != ElementId.InvalidElementId])
      # OR
      setViewsIds = HashSet[ElementId](List[ElementId]([w.OwnerViewId for w in allWires]))
      

      • Objets .Net qui héritent d'une Interface

      Dans le cas d'objets qui héritent d'une Interface .Net, il faut caster (convertir) l'objet sur cette dernière pour utiliser les méthodes héritées.

      Exemple sur conversion sur les méthodes d'Interfaces Enums (Enum.IConvertible)

      
      BuiltInParameter.VIS_GRAPHICS_MODEL.ToInt32(None)
      
      à remplacer par
      
      value = System.IConvertible(BuiltInParameter.VIS_GRAPHICS_MODEL).ToInt32(System.Globalization.CultureInfo.InvariantCulture)
      # or just for this example
      int(BuiltInParameter.VIS_GRAPHICS_MODEL)
      

      Exemple avec une PictureBox pour appeler la méthode BeginInit()
      
      class Form8(Form):
      	def __init__(self):
      		self.InitializeComponent()
      	
      	def InitializeComponent(self):
      		self._pictureBox1 = System.Windows.Forms.PictureBox()
      		self._pictureBox1.BeginInit()
      		self.SuspendLayout()
      

      à remplacer par
      
      class Form8(Form):
      	def __init__(self):
              super().__init__() # necessary  if you override the __init__ method
      		self.InitializeComponent()
      	
      	def InitializeComponent(self):
      		self._pictureBox1 = System.Windows.Forms.PictureBox()
      		System.ComponentModel.ISupportInitialize(self._pictureBox1).BeginInit()
      		self.SuspendLayout()
      


      • Instruction "with" sur des objets .Net 

      Sous IronPython le mot clé with permet de gérer une ressource en garantissant l'invocation d'une méthode d'acquisition et d'une autre de libération de cette ressource.

      Actuellement avec PythonNet3 cela génère une erreur.



      
      import clr
      import sys
      import re
      import System
      
      #import Revit API
      clr.AddReference('RevitAPI')
      import Autodesk
      from Autodesk.Revit.DB import *
      import Autodesk.Revit.DB as DB
      
      #import transactionManager and DocumentManager (RevitServices is specific to Dynamo)
      clr.AddReference('RevitServices')
      import RevitServices
      from RevitServices.Persistence import DocumentManager
      from RevitServices.Transactions import TransactionManager
      
      doc = DocumentManager.Instance.CurrentDBDocument
      
      with Transaction(doc, 'CommandName') as t:
          print(f"{t=}")
          print(t.GetType().ToString())
          t.Start()
          # do stuff
          t.Commit()
      

      une solution consiste à construire son propre "context manager"
      
      import sys
      import clr
      
      # Add Assemblies for AutoCAD and Civil3D
      clr.AddReference('AcMgd')
      clr.AddReference('AcCoreMgd')
      clr.AddReference('AcDbMgd')
      clr.AddReference('AecBaseMgd')
      clr.AddReference('AecPropDataMgd')
      clr.AddReference('AeccDbMgd')
      
      # Import references from AutoCAD
      from Autodesk.AutoCAD.Runtime import *
      from Autodesk.AutoCAD.ApplicationServices import *
      from Autodesk.AutoCAD.EditorInput import *
      from Autodesk.AutoCAD.DatabaseServices import *
      from Autodesk.AutoCAD.Geometry import *
      
      # Import references from Civil3D
      from Autodesk.Civil.ApplicationServices import *
      from Autodesk.Civil.DatabaseServices import *
      
      # The inputs to this node will be stored as a list in the IN variables.
      dataEnteringNode = IN
      
      adoc = Application.DocumentManager.MdiActiveDocument
      editor = adoc.Editor
      
      class CManager:
          """
          a custom context manager for Disposable Object
          """
          def __init__(self, obj):
              self.obj = obj
              
          def __enter__(self):
              return self.obj
              
          def __exit__(self, exc_type, exc_value, exc_tb):
              self.obj.Dispose()
              if exc_type:
                  error = f"{exc_value} at line {exc_tb.tb_lineno}"
                  raise ValueError( error)
              return self
              
              
              
      dynCADObjects = IN[0]
      with adoc.LockDocument():
          with CManager(adoc.Database) as db:
              print(db)
              with CManager(db.TransactionManager.StartTransaction()) as t:
                  print(t)
                  for dynObj in dynCADObjects:
                      ent = t.GetObject(dynObj.AcadObjectId, OpenMode.ForWrite)
                      ent.Layer = "Layer1"
                      #a = 10 /0 
                  t.Commit()
      
      print(f"{db.IsDisposed=}")
      print(f"{t.IsDisposed=}")
      

      • Ligne d'erreur manquante

      Dans de rares cas, le numéro de ligne d'erreur dans le message d'erreur du nœud Python est manquant.



      Voici quelques solutions en attendant une correction :
        • Implémenter un bloc try-except avec la librairie traceback.
        • Implémenter un bloc try-except avec un logger.
        • Implémenter un debugger.

      • OUT Python DynamoWrapper break .Net Class

      Des instance de classe Python qui dérive d'une classe .NET  ont leurs attributs supprimés par le Wrapper Dynamo




      Une solution temporaire consiste à emballer l'objet .Net dans une classe Python avant de l'attribuer à la variable OUT
      
      class WrappNetObj:
           def __init__(self, obj):
               self.InnerObject = obj
           def __repr__(self):
               return "WrappNetObj_" + self.InnerObject.GetType().ToString()
      
      
      import System.Drawing
      import System.Windows.Forms
      
      from System.Drawing import *
      from System.Windows.Forms import *
      
      class Form8(Form):
          def __init__(self):
              super().__init__()
              self.InitializeComponent()
          
          def InitializeComponent(self):
              self.SuspendLayout()
              # 
              # Form8
              # 
              self.ClientSize = System.Drawing.Size(284, 261)
              self.Name = "Form8"
              self.Text = "Form8"
              self.ResumeLayout(False)
      
      win_obj = Form8()
      OUT = WrappNetObj(win_obj)
      
      ...


      ...

      0 commentaires:

      Enregistrer un commentaire