Skip to content

Commit 0c6c2bc

Browse files
twastvedtGitHub Enterprise
authored andcommitted
DYN-8150: Use custom unwrap marshaler in Revit 2025 (#13)
* Add custom marshaler in Revit 2025 * Get model differently * Clean up * Clean up * Don't register marshaler on every run.
1 parent 4da1436 commit 0c6c2bc

File tree

2 files changed

+59
-17
lines changed

2 files changed

+59
-17
lines changed

DSPythonNet3/DSPythonNet3Evaluator.cs

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ internal static DynamoLogger DynamoLogger {
150150
}
151151
}
152152

153+
private static readonly PropertyInfo? revitDynamoProperty;
154+
153155
/// <summary>
154156
/// Use Lazy&lt;PythonEngineManager&gt; to make sure the Singleton class is only initialized once
155157
/// </summary>
@@ -166,6 +168,13 @@ private static readonly Lazy<DSPythonNet3Evaluator>
166168
static DSPythonNet3Evaluator()
167169
{
168170
InitializeEncoders();
171+
172+
var dynamoRevitAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.GetName().Name == "DynamoRevitDS");
173+
174+
if (dynamoRevitAssembly is not null)
175+
{
176+
revitDynamoProperty = dynamoRevitAssembly.GetType("Dynamo.Applications.DynamoRevit")?.GetProperty("RevitDynamoModel", BindingFlags.Public | BindingFlags.Static);
177+
}
169178
}
170179

171180
public override string Name => "PythonNet3";
@@ -316,7 +325,7 @@ public static object EvaluatePythonScript(
316325

317326
private static bool isPythonInstalled = false;
318327
/// <summary>
319-
/// Makes sure Python is installed on the system and it's location added to the path.
328+
/// Makes sure Python is installed on the system and its location added to the path.
320329
/// NOTE: Calling SetupPython multiple times will add the install location to the path many times,
321330
/// potentially causing the environment variable to overflow.
322331
/// </summary>
@@ -326,14 +335,11 @@ internal static async Task InstallPythonAsync()
326335
{
327336
try
328337
{
329-
var disableEmbedded = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DYN_DISABLE_PYNET_EMBEDDED"));
330-
if (disableEmbedded)
338+
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DYN_DISABLE_PYNET_EMBEDDED")))
331339
{
332-
Python.Included.PythonEnv.DeployEmbeddedPython = false;
340+
await Python.Included.Installer.SetupPython();
333341
}
334342

335-
await Python.Included.Installer.SetupPython();
336-
337343
Assembly assembly = Assembly.GetAssembly(typeof(DSPythonNet3Evaluator)) ?? throw new Exception("Can't get assembly.");
338344
AssemblyLoadContext context = AssemblyLoadContext.GetLoadContext(assembly) ?? throw new Exception("Can't get assembly context.");
339345

@@ -494,8 +500,15 @@ private static void InitializeEncoders()
494500
[SupressImportIntoVM]
495501
internal override void RegisterHostDataMarshalers()
496502
{
497-
DataMarshaler? dataMarshalerToUse = HostDataMarshaler as DataMarshaler;
498-
dataMarshalerToUse?.RegisterMarshaler((PyObject pyObj) =>
503+
if (HostDataMarshaler is DataMarshaler dataMarshalerToUse)
504+
{
505+
RegisterHostDataMarshalers(dataMarshalerToUse);
506+
}
507+
}
508+
509+
private static void RegisterHostDataMarshalers(DataMarshaler dataMarshaler)
510+
{
511+
dataMarshaler.RegisterMarshaler((PyObject pyObj) =>
499512
{
500513
try
501514
{
@@ -508,8 +521,8 @@ internal override void RegisterHostDataMarshalers()
508521
foreach (PyObject item in pyDict.Items())
509522
{
510523
dict.SetItem(
511-
ConverterExtension.ToPython(dataMarshalerToUse.Marshal(item.GetItem(0))),
512-
ConverterExtension.ToPython(dataMarshalerToUse.Marshal(item.GetItem(1)))
524+
ConverterExtension.ToPython(dataMarshaler.Marshal(item.GetItem(0))),
525+
ConverterExtension.ToPython(dataMarshaler.Marshal(item.GetItem(1)))
513526
);
514527
}
515528
return dict;
@@ -521,7 +534,7 @@ internal override void RegisterHostDataMarshalers()
521534
var outputList = new PyList();
522535
foreach (PyObject item in inputList)
523536
{
524-
outputList.Append(ConverterExtension.ToPython(dataMarshalerToUse.Marshal(item)));
537+
outputList.Append(ConverterExtension.ToPython(dataMarshaler.Marshal(item)));
525538
}
526539
return outputList;
527540
}
@@ -534,7 +547,7 @@ internal override void RegisterHostDataMarshalers()
534547
return unmarshalled;
535548
}
536549

537-
return dataMarshalerToUse.Marshal(unmarshalled);
550+
return dataMarshaler.Marshal(unmarshalled);
538551
}
539552
}
540553
catch (Exception e)
@@ -726,6 +739,8 @@ private static bool IsMarkedToSkipConversion(PyObject pyObj)
726739
[SupressImportIntoVM]
727740
public override event EvaluationFinishedEventHandler EvaluationFinished;
728741

742+
private bool registeredUnwrapMarshaler;
743+
729744
/// <summary>
730745
/// Called immediately before evaluation starts
731746
/// </summary>
@@ -744,6 +759,32 @@ private void OnEvaluationBegin(PyModule scope,
744759
Dynamo.Logging.Categories.PythonOperations,
745760
"CPythonEvaluation");
746761
}
762+
763+
// Set custom unwrap marshaler in Revit 2025: If `UnwrapElementMarshaler` exists on `RevitDynamoModel`, we're in pre-2026 Revit
764+
// where D4R doesn't call RegisterHostDataMarshalers, so we do it manually.
765+
// We only need to add the marshalers once for each RevitDynamoModel.
766+
// `registeredUnwrapMarshaler` is reset when Dynamo is restarted (Python evaluator persists across restarts within same Revit session.
767+
if (revitDynamoProperty is not null && !registeredUnwrapMarshaler)
768+
{
769+
var revitDynamoModel = revitDynamoProperty.GetValue(null);
770+
771+
DataMarshaler? unwrapMarshaler = revitDynamoModel?.GetType()
772+
.GetField(
773+
"UnwrapElementMarshaler",
774+
BindingFlags.NonPublic | BindingFlags.Instance)?
775+
.GetValue(revitDynamoModel) as DataMarshaler;
776+
777+
if (unwrapMarshaler is not null)
778+
{
779+
RegisterHostDataMarshalers(unwrapMarshaler);
780+
}
781+
else
782+
{
783+
DynamoLogger?.LogWarning("UnwrapElementMarshaler not found on RevitDynamoModel.", WarningLevel.Error);
784+
}
785+
786+
registeredUnwrapMarshaler = true;
787+
}
747788
}
748789

749790
/// <summary>

DynamoPythonNet3Extension/Extension.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
using System;
2-
using System.IO;
3-
using System.Linq;
4-
using System.Reflection;
1+
using System.Reflection;
52
using System.Runtime.Loader;
63
using Dynamo.Extensions;
74
using Dynamo.Graph.Workspaces;
@@ -68,7 +65,7 @@ public void Ready(ReadyParams rp)
6865
}
6966
}
7067

71-
private static void LoadPythonEngine(Assembly assembly)
68+
private void LoadPythonEngine(Assembly assembly)
7269
{
7370
if (assembly == null)
7471
{
@@ -97,6 +94,10 @@ private static void LoadPythonEngine(Assembly assembly)
9794

9895
PythonEngine engine = (PythonEngine)(instanceProp.GetValue(null)
9996
?? throw new Exception($"Could not get a valid PythonEngine instance by calling the {eType?.Name}.Instance method"));
97+
98+
// Reset flag to register unwrap marshaler again in Revit 2025
99+
engine.GetType().GetField("registeredUnwrapMarshaler", BindingFlags.NonPublic | BindingFlags.Instance)?.SetValue(engine, false);
100+
100101
if (PythonEngineManager.Instance.AvailableEngines.All(x => x.Name != engine.Name))
101102
{
102103
PythonEngineManager.Instance.AvailableEngines.Add(engine);

0 commit comments

Comments
 (0)