30 mars 2025

[Dynamo += Python] DSPytonNet3 et LINQ

 


Savez-vous que vous pouvez utiliser les méthodes d'extension LINQ avec le moteur Python DynamoPythonNet3 ?


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

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


Depuis l’arrivée du moteur Dynamo PythonNet3, il est désormais possible d’exploiter les méthodes LINQ (Language Integrated Query) avec des versions récentes de Python3 sur DynamoBIM. 

Les méthodes d’extension de System.Linq en .NET permettent d'effectuer des opérations de requêtage et de manipulation de collections. 

Elles s'appliquent principalement aux collections qui implémentent IEnumerable<T> et IQueryable<T>, et incluent des fonctionnalités comme le filtrage, le tri, la transformation et l'agrégation des données.

La différence la plus notable avec IronPython, c'est qu'il faudra être vigilant lorsqu'une méthode d'extension LINQ a plusieurs variantes ( Overloads ).

Voyons quelques exemples :

  • Exemple avec les méthodes Where et Select
.

import sys
import clr
import System
from System.Collections.Generic import List, IList
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

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

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument

clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)
#
lst_int = List[System.Int32](list(range(10)))
resultA = lst_int.Where[System.Int32](System.Func[System.Int32, System.Boolean](lambda x : x % 2 == 0))\
            .Select[System.Int32, System.Int32](System.Func[System.Int32, System.Int32](lambda x : x ** 2))
                
OUT = resultA
.

Pour chaque méthode :
  1.         Les lambdas Python doivent être transformées (cast) en délégués .NET de type System.Func<T> pour être compatibles avec les méthodes d’extension LINQ.

    System.Func[System.Int32, System.Int32](lambda x : x ** 2)

    Func<T> est un type de délégué prédéfini pour une méthode qui renvoie une valeur du type T.


  2.         PythonNET prend en charge les types génériques. Un type générique peut être spécifié en lui attribuant des types avant de pouvoir être instancié.  

    .Select[System.Int32, System.Int32]()

Notes:

    • Si vous créez des nouvelles instances par la suite dans votre code via NewFamilyInstance je vous recommande fortement de fixer votre "collector" en le transformant en List<T> avec la méthode d'extension ToList()
      (pour éviter que de nouveaux éléments viennent s'ajouter à ce dernier)

    • La classe FilteredElementCollector implémente l'interface IEnumerable<T>,  de ce fait la méthode ToElements() n'est pas nécessaire.


    • Dans certains cas, vous pouvez vous passer de spécifier le Type lorsque les méthodes ont le même Type de paramètre (exemple FirstOrDefault )



  • Exemples avec les méthodes FirstOrDefault , GroupBy
.

import sys
import clr
import System
from System.Collections.Generic import List, IList

clr.AddReference("System.Reflection")
from System.Reflection import Assembly

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


clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument

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

resultB = FilteredElementCollector(doc).OfClass(FamilySymbol).WhereElementIsElementType()\
            .Where(System.Func[DB.Element, System.Boolean](lambda e_type : "E1" in e_type.Name))
            
resultC = FilteredElementCollector(doc).OfClass(FamilySymbol).WhereElementIsElementType()\
            .FirstOrDefault(System.Func[DB.Element, System.Boolean](lambda e_type : "E1" in e_type.Name))
            
resultD = FilteredElementCollector(doc).OfClass(FamilySymbol).WhereElementIsElementType()\
            .FirstOrDefault(System.Func[DB.Element, System.Boolean](lambda e_type : "E12" in e_type.Name))
            
resultGroup = FilteredElementCollector(doc).OfClass(FamilySymbol).WhereElementIsElementType()\
            .GroupBy[DB.Element, System.String](System.Func[DB.Element, System.String](lambda e_type :  e_type.Name))\
            .Select(System.Func[System.Object, System.Object](lambda x : x.ToList()))
            
            
assemblies = System.AppDomain.CurrentDomain.GetAssemblies()
assemblies_name = assemblies\
    .OrderBy[Assembly, System.String](System.Func[Assembly, System.String](lambda x : x.GetName().Name))\
    .Select[Assembly, System.String](System.Func[Assembly, System.Int32, System.String](lambda asm, index : f"{index + 1} : {asm.GetName().Name}"))
            
print(assemblies_name.GetType())
OUT = resultB.ToList(), resultC, resultD, resultGroup.ToList(), assemblies_name


  • Exemple pour convertir un IEnumerable[IGrouping[TKey, TElement]]  en Dictionary[TKey, TValue] 
.

import clr
import System
from System.Collections.Generic import List

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

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument

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


fecMultiCat = FilteredElementCollector(doc).OfClass(FamilySymbol)
#
groupList=[]
#                       
dict_group = fecMultiCat.OrderBy[DB.Element, System.String](System.Func[DB.Element, System.String](lambda e_type :  e_type.FamilyName))\
                        .GroupBy[DB.Element, System.String](System.Func[DB.Element, System.String](lambda x : x.Category.Name))\
                        .ToDictionary[System.Object, System.Object, System.Object](
                                                                System.Func[System.Object, System.Object](lambda g : g.Key), 
                                                                System.Func[System.Object, System.Object](lambda g : g.ToList())
                                                                )
# convert to python dict
pydict = dict(dict_group)

OUT = dict_group
...

  • Note concernant les DataTables

Les méthodes d'extension des DataTables ne sont pas prises en charge, une solution consiste à utiliser les méthodes statiques.


...

# Load the Python Standard and DesignScript Libraries
import sys
import clr
import System
from System.Collections.Generic import List, IList

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


clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument

clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)
#
clr.AddReference('System.Data')
from System.Data import *
clr.AddReference('System.Data.Common')
from System.Data import DataTableExtensions 

class Test():
    def __init__(self):
        self.i= 89

dt = DataTable("test")
dt.Columns.Add('Name', System.String)
dt.Columns.Add('Number', System.String)
dt.Columns.Add('Level', System.String)
dt.Columns.Add('Area', System.Double)
dt.Columns.Add('object', System.Object)

dt.Rows.Add("A", "1", "L1", 23.9, Test())
dt.Rows.Add("B", "2", "L1", 43.9, Test())
dt.Rows.Add("C", "3", "L2", 33.9, Test())

# syntax IronPython
# newdt = List[DataRow](dt.AsEnumerable().Where(lambda r : r['Level'] == "L1")).CopyToDataTable()
newRows = List[DataRow]([r for r in dt.Rows])\
                        .Where(System.Func[DataRow, System.Boolean](lambda r : r['Level'] == "L1"))
newdt = DataTableExtensions.CopyToDataTable[DataRow](newRows)
OUT = newdt.GetType().ToString(), [[r['Area'], r["object"]] for r in newdt.Rows]


  • Test de performances entre PythonNet et IronPython 

Test sur 30000 points

Environnement Méthode Nombre de points Moyenne temps (µs) Nombre de tests Version Runtime
IronPython2 Where 30 000 600 20 4.8
IronPython3 Where 30 000 450 20 4.8
PythonNet3 Where 30 000 350 20 8.0.10






« Il vaut mieux être un optimiste qui se trompe parfois qu'un pessimiste qui a toujours raison. »
Mark Twain

Related Posts:

7 commentaires:

  1. Can we use Plinq somehow with PythonNET3? Thank you!

    RépondreSupprimer
    Réponses
    1. I mean AsParallel().

      Supprimer
    2. Hi, parallel multi-threading with AsParallal() do not work with PythonNet, try multiprocessing with concurrent.futures instead.

      Supprimer
    3. After checking, it works, but I'll do a few more tests to see if parallelization is taken into account.

      Supprimer
    4. Good news! I could test performance on big rvt model, if you share the code. Thanks!

      Supprimer
    5. I confirm that the AsParallel() method works, but the parallelization of the methods is not taken into account (limitation due to Python/PythonNet) that said the Linq have very good performance under Net Core without multiprocessing

      Supprimer
  2. Ce commentaire a été supprimé par l'auteur.

    RépondreSupprimer