diff --git a/DSPythonNet3/DSPythonNet3Evaluator.cs b/DSPythonNet3/DSPythonNet3Evaluator.cs index e57f073..9f9a6f5 100644 --- a/DSPythonNet3/DSPythonNet3Evaluator.cs +++ b/DSPythonNet3/DSPythonNet3Evaluator.cs @@ -514,12 +514,17 @@ def write(self, text): /// private static void InitializeEncoders() { - var shared = new object[] { new ListEncoderDecoder() }; - var encoders = shared.Cast().ToArray(); - var decoders = shared.Cast().Concat(new IPyObjectDecoder[] + var listEncoderDecoder = new ListEncoderDecoder(); + var encoders = new IPyObjectEncoder[] { + listEncoderDecoder, + new ConnectionNodeObjectEncoder() + }; + var decoders = new IPyObjectDecoder[] + { + listEncoderDecoder, new DictionaryDecoder() - }).ToArray(); + }; Array.ForEach(encoders, e => PyObjectConversions.RegisterEncoder(e)); Array.ForEach(decoders, d => PyObjectConversions.RegisterDecoder(d)); } diff --git a/DSPythonNet3/Encoders/ConnectionNodeObjectEncoder.cs b/DSPythonNet3/Encoders/ConnectionNodeObjectEncoder.cs new file mode 100644 index 0000000..fb52429 --- /dev/null +++ b/DSPythonNet3/Encoders/ConnectionNodeObjectEncoder.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections; +using System.Reflection; +using Dynamo.Utilities; +using Python.Runtime; + +namespace DSPythonNet3.Encoders +{ + /// + /// Prevents Python.NET from auto-encoding certain IEnumerable-based host objects + /// (notably Dynamo for Revit "ConnectionNode") into Python iterables, which + /// strips away callable .NET members like SubNodesOfSize / ExistingConnections. + /// + internal sealed class ConnectionNodeObjectEncoder : IPyObjectEncoder + { + private static readonly string[] methodNames = new[] + { + "SubNodesOfSize", + "ExistingConnections" + }; + + public bool CanEncode(Type type) + { + // Don't intercept common primitives / containers. + if (type == typeof(string)) + { + return false; + } + + if (!typeof(IEnumerable).IsAssignableFrom(type)) + { + return false; + } + + // Let our existing List encoder/decoder handle IList. + if (typeof(IList).IsAssignableFrom(type)) + { + return false; + } + + // Don't interfere with dictionary encoding. + if (typeof(IDictionary).IsAssignableFrom(type)) + { + return false; + } + + // Target by type name (no hard reference to Revit/DynamoRevit assemblies). + if (type.Name.Contains("ConnectionNode", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // Target by known member names used in graphs. + const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public; + foreach (var name in methodNames) + { + if (type.GetMethod(name, flags) != null) + { + return true; + } + } + + return false; + } + + public PyObject TryEncode(object value) + { + // Wrap as CLR object so methods remain callable in Python. + return PyObject.FromManagedObject(value); + } + } +} +