diff --git a/DSPythonNet3/DSPythonNet3Evaluator.cs b/DSPythonNet3/DSPythonNet3Evaluator.cs
index e57f073..2171298 100644
--- a/DSPythonNet3/DSPythonNet3Evaluator.cs
+++ b/DSPythonNet3/DSPythonNet3Evaluator.cs
@@ -794,6 +794,75 @@ 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.
+ // 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__
+
+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:
+ if not isinstance(__obj, __ConnectionNode):
+ return
+
+ # Only patch if missing or still the raw reflected method.
+ def __subnodes(__n, *args, __obj=__obj, **kwargs):
+ return __ConnectionNode.SubNodesOfSize(__obj, __n, *args, **kwargs)
+
+ def __existing(*args, __obj=__obj, **kwargs):
+ return __ConnectionNode.ExistingConnections(__obj, *args, **kwargs)
+
+ try:
+ setattr(__obj, 'SubNodesOfSize', __subnodes)
+ except Exception:
+ pass
+ try:
+ setattr(__obj, 'ExistingConnections', __existing)
+ except Exception:
+ pass
+ except Exception:
+ pass
+
+ def __dspynet3__walk_and_patch(__x):
+ try:
+ if isinstance(__x, (list, tuple)):
+ for __i in __x:
+ __dspynet3__walk_and_patch(__i)
+ else:
+ __dspynet3__patch_connnode_instance(__x)
+ except Exception:
+ pass
+
+ 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
+";
+ }
///
/// Called immediately before evaluation starts
@@ -839,6 +908,9 @@ private void OnEvaluationBegin(PyModule scope,
registeredUnwrapMarshaler = true;
}
+
+ // Apply compatibility shims (safe no-op if unavailable).
+ scope.Exec(ConnectionNodeCompatPatchCode());
}
///