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 pour acceder aux attributs dynamiquement).

    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 sys
    import clr
    import System
    from System import Environment 
    from System.Runtime.InteropServices import Marshal
    try:
        from System.Reflection import BindingFlags
    except:
        clr.AddReference("System.Reflection")
        from System.Reflection import BindingFlags
    
    xls_filePath = IN[0]
    wsNames = []
    
    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)
    enumerator_sheets = worksheets.GetType().InvokeMember("GetEnumerator", BindingFlags.InvokeMethod , None, worksheets, None) 
    while enumerator_sheets.MoveNext():
        sheet = enumerator_sheets.Current
        sheet_name = sheet.GetType().InvokeMember("Name", BindingFlags.GetProperty, None, sheet, None)
        wsNames.append(sheet_name)
    
    workbooks.GetType().InvokeMember("Close", BindingFlags.InvokeMethod, None, workbooks, None)
    ex.GetType().InvokeMember("Quit", BindingFlags.InvokeMethod, None, ex, None)
    
    if workbooks is not None:
        Marshal.ReleaseComObject(workbooks)
    if ex is not None:
        Marshal.ReleaseComObject(ex)
    #
    workbooks = None
    ex = None
    #
    OUT = wsNames
    

    • 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. 


    Exemple avec 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 egalement scanner la table ROT et de recuperer l'instance de l'application
        
      import sys
      import System
      import clr
      import ctypes
      from System import Array, IntPtr
      from System.Runtime.InteropServices import Marshal
      from System.Runtime.InteropServices.ComTypes import IEnumMoniker, IMoniker, IBindCtx
      from System.Runtime.InteropServices.ComTypes import IRunningObjectTable, IEnumMoniker, IMoniker, IBindCtx
      
      def get_ROT_COM_monikers():
          
          ole32 = ctypes.oledll.ole32
          rot_ptr = ctypes.c_void_p() # creates the empty bucket (generic pointer)
          if ole32.GetRunningObjectTable(0, ctypes.byref(rot_ptr)) != 0:
              return ["Failed to access ROT"]
          
          else:
              # convert the raw pointer into a .NET IRunningObjectTable interface
              rot = Marshal.GetObjectForIUnknown(System.IntPtr(rot_ptr.value))
              # 
              # .NET signature: void EnumRunning(out IEnumMoniker ppenumMoniker)
              enum_moniker = IRunningObjectTable.EnumRunning(rot)
              #
              bind_ctx_ptr = ctypes.c_void_p()
              ole32.CreateBindCtx(0, ctypes.byref(bind_ctx_ptr))
              # Cast the bind_ctx pointer to its .NET interface
              managed_bind_ctx = Marshal.GetObjectForIUnknown(System.IntPtr(bind_ctx_ptr.value))
              # iterate through the monikers
              results = []
              moniker_container = Array.CreateInstance(IMoniker, 1)
              #
              # iterate using IEnumMoniker.Next
              while IEnumMoniker.Next(enum_moniker, 1, moniker_container, System.IntPtr.Zero) == 0:
                  moniker = moniker_container[0]
                  if moniker:
                      # Get the name (e.g., AutoCAD path)
                      name = IMoniker.GetDisplayName(moniker, managed_bind_ctx, None)
                      results.append([name, moniker, rot])
              #
              return results
              
      lst_data_moni = get_ROT_COM_monikers()
      # then you can use : IRunningObjectTable.GetObject(rot, moniker)
      


      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)

        Dans le cas de methodes d'interfaces avec des paramètre out , on peut utiliser la synthaxe de type "UnBound Method" , nous appelons la méthode sur la classe d'interface plutôt que sur l'instance d'objet en passant en 1er paramètre l'objet.

        
                # .NET signature: void EnumRunning(out IEnumMoniker ppenumMoniker)
                ppenumMoniker = None
                enum_moniker = IRunningObjectTable.EnumRunning(rot, ppenumMoniker)
                # OR
                enum_moniker = IRunningObjectTable.EnumRunning(rot)
                

        • "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.

        Dans les exemples ci dessous, vous trouverez la syntaxe PythonNet pour effectuer un cast explicite vers une interface .NET.

        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()
        

        La ligne System.ComponentModel.ISupportInitialize(self._pictureBox1).BeginInit() est donc un appel de méthode sur une interface explicite :

          • Il "caste" (ou plutôt, il accède à) l'objet self._pictureBox1 via son implémentation de l'interface ISupportInitialize.
          • Il appelle ensuite la méthode BeginInit() de cette interface 
        • 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
        
        # Load the Python Standard and DesignScript Libraries
        import sys
        import clr
        
        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 WrappNetObj:
             def __init__(self, obj):
                 self.InnerObject = obj
             def __repr__(self):
                 return "WrappNetObj_" + self.InnerObject.GetType().ToString()
        
        class Form8(Form):
            def __init__(self):
                super().__init__()
                self.out_value = 7
                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