PythonNet and .NET Class Interfaces

Hello,

for those who work with CPython3 / PythonNet here is a workaround for using .Net class interfaces

UPDATE PythonNet3 engine 1.1.0 see this post

Syntax for general cases
the solution is to create a Class Type (with Namespace) only once and reuse it in subsequent calls.

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

clr.AddReference('RevitAPIUI')
import Autodesk.Revit.UI as RUI
from Autodesk.Revit.UI import *
from Autodesk.Revit.UI.Selection import *

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
uidoc = uiapp.ActiveUIDocument


class Custom_SelectionElem:
    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__ = "SelectionNameSpace_tEfYX0DHE"
        try:
            # 1. Try to import the module and class. If it already exists, you've probably already created 
            module_type = __import__(cls.__namespace__)
            return module_type._InnerClassInterface(*cls.args)
        except ImportError as ex:
            print(ex)
            # 2. If we get an ImportError, then the derivative class hasn't been built. Build it. Make sure to set the
            #    __namespace__ so that it is created with the appropriate .Net namespace.
            class _InnerClassInterface(ISelectionFilter):
                __namespace__ = cls.__namespace__
                #
                def __init__(self, bic):
                    super().__init__()
                    self.bic = bic
                    
                def AllowElement(self, e):
                    if e.Category.Id == ElementId(self.bic):
                        return True
                    else:
                        return False
                def AllowReference(self, ref, point):
                    return True 
                    
            return _InnerClassInterface(*cls.args)

# usage       
for i in range(3):
    RUI.TaskDialog.Show("Select", f"Select a door {i+1}/3")
    ref = uidoc.Selection.PickObject(ObjectType.Element, Custom_SelectionElem(BuiltInCategory.OST_Doors))
    print(ref.ElementId)

Compatibility of this method :

  • Net Framework 4.x
    Revit / Civil 2022 - CPython3/PythonNet2.5 : Yes
    Revit / Civil 2023 - CPython3/PythonNet2.5 : Yes
    Revit / Civil 2024 (Dynamo 2.19) - CPython3/PythonNet2.5 : NO

  • Net Core 8+
    Revit / Civil 2025+ - CPython3/PythonNet2.5 : Yes
    Revit / Civil 2025+ - PythonNet3 : Yes

Note on .NET Class Interfaces with Python:

  • due to a bug with Revit 2024, I recommend staying with IronPython up to and including Revit 2024 (unless this is corrected in a future update).
  • the use of .Net Class Interfaces with out parameters work only PythonNet3 (engine currently being integrated by Team Dynamo)

Example use of .Net Class Interfaces with out parameters work only PythonNet3

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

clr.AddReference('RevitAPIUI')
import Autodesk.Revit.UI as RUI
from Autodesk.Revit.UI import *
from Autodesk.Revit.UI.Selection import *

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
uidoc = uiapp.ActiveUIDocument

#         
class Custom_FamilyOption:
    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__ = "FamilyOptionNameSpace_tEfYX0DHE"
        try:
            # 1. Try to import the module and class. If it already exists, you've probably already created it once, so you can use it.
            module_type = __import__(cls.__namespace__)
            return module_type._InnerClassInterface(*cls.args)
        except ImportError as ex:
            print(ex)
            # 2. If we get an ImportError, then the derivative class hasn't been built. Build it. Make sure to set the
            #    __namespace__ so that it is created with the appropriate .Net namespace.
            class _InnerClassInterface(IFamilyLoadOptions) :
                __namespace__ = cls.__namespace__
            
                def __init__(self):
                    super().__init__() 
                    
                def OnFamilyFound(self, familyInUse, _overwriteParameterValues):
                    overwriteParameterValues = True
                    return (True, overwriteParameterValues)
            
                def OnSharedFamilyFound(self, sharedFamily, familyInUse, source, _overwriteParameterValues):
                    overwriteParameterValues = True      
                    return (True, overwriteParameterValues)
                    
            return _InnerClassInterface(*cls.args)
#        
def loadFamily(path):
    TransactionManager.Instance.EnsureInTransaction(doc)
    opts = Custom_FamilyOption()
    dummyFamily = None
    loadFamily = doc.LoadFamily(path, opts, dummyFamily)
    TransactionManager.Instance.TransactionTaskDone()

for path in IN[0] : loadFamily(path)

for explanations and more information, I wrote an article about this (use the translation widget if needed)

10 Likes

Wizardy, as always.

Thanks for sharing this!

1 Like

The latest version of Dynamo PythonNet3 engine (version package 1.1.0) fixes the .NET interface/class inheritance bug, making syntax simpler.

Codes are now going to be

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

clr.AddReference('RevitAPIUI')
import Autodesk.Revit.UI as RUI
from Autodesk.Revit.UI import *
from Autodesk.Revit.UI.Selection import *

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
uidoc = uiapp.ActiveUIDocument


class Custom_SelectionElem(ISelectionFilter):
    __namespace__ = "Custom_SelectionElem"
    #
    def __init__(self, bic):
        super().__init__()
        self.bic = bic
        
    def AllowElement(self, e):
        if e.Category.Id == ElementId(self.bic):
            return True
        else:
            return False
    def AllowReference(self, ref, point):
        return True 

# usage       
for i in range(3):
    RUI.TaskDialog.Show("Select", f"Select a door {i+1}/3")
    ref = uidoc.Selection.PickObject(ObjectType.Element, Custom_SelectionElem(BuiltInCategory.OST_Doors))
    print(ref.ElementId)

and

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

clr.AddReference('RevitAPIUI')
import Autodesk.Revit.UI as RUI
from Autodesk.Revit.UI import *
from Autodesk.Revit.UI.Selection import *

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
uidoc = uiapp.ActiveUIDocument

#         
class Custom_FamilyOption(IFamilyLoadOptions) :
    __namespace__ = "Custom_FamilyOption"

    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)

for path in IN[0] : 
    loadFamily(path)
3 Likes