From ecdecce2689bd50cd29981b34793e3d33ce30d4d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 16 Dec 2025 17:09:14 +0000 Subject: [PATCH 1/3] Add Python.NET 3 compatibility shim for node-first static methods Co-authored-by: ipetrov --- DSPythonNet3/DSPythonNet3Evaluator.cs | 44 +++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/DSPythonNet3/DSPythonNet3Evaluator.cs b/DSPythonNet3/DSPythonNet3Evaluator.cs index e57f073..bb87b9a 100644 --- a/DSPythonNet3/DSPythonNet3Evaluator.cs +++ b/DSPythonNet3/DSPythonNet3Evaluator.cs @@ -794,6 +794,47 @@ private static bool IsMarkedToSkipConversion(PyObject pyObj) public override event EvaluationFinishedEventHandler EvaluationFinished; private bool registeredUnwrapMarshaler; + private const string ConnectionNodeCompatSentinel = "__dspynet3_connnode_compat__"; + + /// + /// Compatibility shim for "node-first" static methods exposed by some Dynamo/Revit libraries. + /// In older engines these could be invoked like instance methods (e.g. node.SubNodesOfSize(2)). + /// Python.NET 3 binds static methods strictly, so we patch a small set of known APIs to keep + /// existing graphs working. + /// + private static string ConnectionNodeCompatPatchCode() + { + // Keep this snippet extremely defensive: it should be a no-op if the library isn't present + // or if the runtime forbids monkey-patching reflected CLR types. + return $@" +import builtins as __builtins__ +if not getattr(__builtins__, '{ConnectionNodeCompatSentinel}', False): + try: + from AdvanceSteel.ConnectionAutomation.Nodes import ConnectionNode as __ConnectionNode + + def __bind_node_first_static(__name): + try: + __orig = getattr(__ConnectionNode, __name) + except Exception: + return + + def __inst(self, *args, __orig=__orig, **kwargs): + return __orig(self, *args, **kwargs) + + try: + setattr(__ConnectionNode, __name, __inst) + except Exception: + # Some runtimes may forbid setting attributes on CLR types. + pass + + __bind_node_first_static('SubNodesOfSize') + __bind_node_first_static('ExistingConnections') + + setattr(__builtins__, '{ConnectionNodeCompatSentinel}', True) + except Exception: + pass +"; + } /// /// Called immediately before evaluation starts @@ -839,6 +880,9 @@ private void OnEvaluationBegin(PyModule scope, registeredUnwrapMarshaler = true; } + + // Apply compatibility shims (safe no-op if unavailable). + scope.Exec(ConnectionNodeCompatPatchCode()); } /// From c94b8b4f2ec39f95f7e88f3a02a52e1a4d3d5a7c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 16 Dec 2025 17:42:36 +0000 Subject: [PATCH 2/3] Refactor ConnectionNode compatibility patch for Python.NET Co-authored-by: ipetrov --- DSPythonNet3/DSPythonNet3Evaluator.cs | 60 ++++++++++++++++++++------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/DSPythonNet3/DSPythonNet3Evaluator.cs b/DSPythonNet3/DSPythonNet3Evaluator.cs index bb87b9a..421a626 100644 --- a/DSPythonNet3/DSPythonNet3Evaluator.cs +++ b/DSPythonNet3/DSPythonNet3Evaluator.cs @@ -804,33 +804,61 @@ private static bool IsMarkedToSkipConversion(PyObject pyObj) /// private static string ConnectionNodeCompatPatchCode() { - // Keep this snippet extremely defensive: it should be a no-op if the library isn't present - // or if the runtime forbids monkey-patching reflected CLR types. + // Keep this snippet extremely defensive: it should be a no-op if the library isn't present. + // IMPORTANT: Many runtimes forbid setting attributes on CLR *types*. However, Python.NET + // typically allows attaching attributes to CLR *instances* via the wrapper's __dict__. + // So we patch instances coming in through IN (recursively for nested lists). return $@" import builtins as __builtins__ -if not getattr(__builtins__, '{ConnectionNodeCompatSentinel}', False): - try: - from AdvanceSteel.ConnectionAutomation.Nodes import ConnectionNode as __ConnectionNode - def __bind_node_first_static(__name): +try: + from AdvanceSteel.ConnectionAutomation.Nodes import ConnectionNode as __ConnectionNode +except Exception: + __ConnectionNode = None + +if __ConnectionNode is not None: + if not getattr(__builtins__, '{ConnectionNodeCompatSentinel}', False): + def __dspynet3__patch_connnode_instance(__obj): + # Attach instance-callable wrappers for node-first static methods. try: - __orig = getattr(__ConnectionNode, __name) + if not isinstance(__obj, __ConnectionNode): + return + + # Only patch if missing or still the raw reflected method. + def __subnodes(__n, __obj=__obj): + return __ConnectionNode.SubNodesOfSize(__obj, __n) + + def __existing(__obj=__obj): + return __ConnectionNode.ExistingConnections(__obj) + + try: + setattr(__obj, 'SubNodesOfSize', __subnodes) + except Exception: + pass + try: + setattr(__obj, 'ExistingConnections', __existing) + except Exception: + pass except Exception: - return - - def __inst(self, *args, __orig=__orig, **kwargs): - return __orig(self, *args, **kwargs) + pass + def __dspynet3__walk_and_patch(__x): try: - setattr(__ConnectionNode, __name, __inst) + if isinstance(__x, (list, tuple)): + for __i in __x: + __dspynet3__walk_and_patch(__i) + else: + __dspynet3__patch_connnode_instance(__x) except Exception: - # Some runtimes may forbid setting attributes on CLR types. pass - __bind_node_first_static('SubNodesOfSize') - __bind_node_first_static('ExistingConnections') - + setattr(__builtins__, '__dspynet3__walk_and_patch_connnode', __dspynet3__walk_and_patch) setattr(__builtins__, '{ConnectionNodeCompatSentinel}', True) + + # Patch current inputs for this evaluation (IN exists in the module scope). + try: + __inp = globals().get('IN', None) + __builtins__.__dspynet3__walk_and_patch_connnode(__inp) except Exception: pass "; From 496a791de5d6cdd6fd431b18613ed916efd59461 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 17 Dec 2025 13:08:23 +0000 Subject: [PATCH 3/3] Refactor SubNodesOfSize and ExistingConnections to accept arbitrary args Co-authored-by: ipetrov --- DSPythonNet3/DSPythonNet3Evaluator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DSPythonNet3/DSPythonNet3Evaluator.cs b/DSPythonNet3/DSPythonNet3Evaluator.cs index 421a626..2171298 100644 --- a/DSPythonNet3/DSPythonNet3Evaluator.cs +++ b/DSPythonNet3/DSPythonNet3Evaluator.cs @@ -825,11 +825,11 @@ def __dspynet3__patch_connnode_instance(__obj): return # Only patch if missing or still the raw reflected method. - def __subnodes(__n, __obj=__obj): - return __ConnectionNode.SubNodesOfSize(__obj, __n) + def __subnodes(__n, *args, __obj=__obj, **kwargs): + return __ConnectionNode.SubNodesOfSize(__obj, __n, *args, **kwargs) - def __existing(__obj=__obj): - return __ConnectionNode.ExistingConnections(__obj) + def __existing(*args, __obj=__obj, **kwargs): + return __ConnectionNode.ExistingConnections(__obj, *args, **kwargs) try: setattr(__obj, 'SubNodesOfSize', __subnodes)