@@ -150,6 +150,8 @@ internal static DynamoLogger DynamoLogger {
150150 }
151151 }
152152
153+ private static readonly PropertyInfo ? revitDynamoProperty ;
154+
153155 /// <summary>
154156 /// Use Lazy<PythonEngineManager> 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>
0 commit comments