1- using Autodesk . Revit . ApplicationServices ;
1+ using Autodesk . DataManagement ;
2+ using Autodesk . DataManagement . Model ;
3+ using Autodesk . Revit . ApplicationServices ;
24using Autodesk . Revit . DB ;
35using DesignAutomationFramework ;
6+ using DSCPython ;
7+ using Dynamo . Applications ;
8+ using Dynamo . Migration ;
49using Dynamo . Models ;
10+ using Dynamo . PythonServices ;
11+ using Dynamo . Scheduler ;
512using DynamoPlayer ;
6- using RevitServices . Persistence ;
13+ using Greg . AuthProviders ;
14+ using Newtonsoft . Json ;
15+ using Newtonsoft . Json . Linq ;
16+ using ProtoCore . DesignScriptParser ;
717using RevitServices . Elements ;
18+ using RevitServices . Persistence ;
19+ using System . Diagnostics ;
820using System . Reflection ;
921using System . Runtime . Loader ;
10- using System . Diagnostics ;
11- using Dynamo . Applications ;
12- using Dynamo . Scheduler ;
13- using static Dynamo . Models . DynamoModel ;
1422using System . Text . RegularExpressions ;
15- using Greg . AuthProviders ;
16- using Dynamo . PythonServices ;
17- using DSCPython ;
18- using Newtonsoft . Json ;
23+ using System . Windows . Controls ;
24+ using static Dynamo . Models . DynamoModel ;
1925
2026namespace DADynamoApp
2127{
@@ -25,7 +31,6 @@ public class DAEntrypoint : IExternalDBApplication
2531 {
2632 private DynamoModel model ;
2733 internal static DynamoPlayerLoggerConfiguration logConfig = new DynamoPlayerLoggerConfiguration ( ) { DynamoLogLevel = Dynamo . Logging . LogLevel . Console , LogLevel = DynamoPlayer . LogLevel . Information } ;
28- private AssemblyLoadContext loadContext = AssemblyLoadContext . GetLoadContext ( Assembly . GetExecutingAssembly ( ) ) ?? AssemblyLoadContext . Default ;
2934 private string DynamoPath ;
3035 private string DynamoRevitPath ;
3136 private ControlledApplication controlledApplication ;
@@ -120,10 +125,91 @@ private static string GetRevitContext(Autodesk.Revit.ApplicationServices.Applica
120125 return r . Replace ( app . VersionName , "" ) ;
121126 }
122127
128+ /// <summary>
129+ /// Extracts the 'token' value from the work item JSON content.
130+ /// This is robust to casing differences and supports BoundArguments as an object.
131+ /// Returns null if token cannot be found.
132+ /// </summary>
133+ private string ? ExtractTokenFromWorkItem ( string workItemContent )
134+ {
135+ if ( string . IsNullOrWhiteSpace ( workItemContent ) ) return null ;
136+
137+ try
138+ {
139+ var job = JObject . Parse ( workItemContent ) ;
140+
141+ // Find BoundArguments (case-insensitive)
142+ JToken ? boundToken = null ;
143+ if ( ! job . TryGetValue ( "BoundArguments" , out boundToken ) )
144+ {
145+ foreach ( var prop in job . Properties ( ) )
146+ {
147+ if ( string . Equals ( prop . Name , "BoundArguments" , StringComparison . OrdinalIgnoreCase ) )
148+ {
149+ boundToken = prop . Value ;
150+ break ;
151+ }
152+ }
153+ }
154+
155+ if ( boundToken == null ) return null ;
156+
157+ // If it's an object, try direct lookup
158+ if ( boundToken . Type == JTokenType . Object )
159+ {
160+ var boundObj = ( JObject ) boundToken ;
161+
162+ // try exact key 'token' then case-insensitive
163+ if ( boundObj . TryGetValue ( "adsk3LeggedToken" , out var tokVal ) )
164+ {
165+ return tokVal . Type == JTokenType . String ? tokVal . Value < string > ( ) : tokVal . ToString ( ) ;
166+ }
167+
168+ foreach ( var prop in boundObj . Properties ( ) )
169+ {
170+ if ( string . Equals ( prop . Name , "adsk3LeggedToken" , StringComparison . OrdinalIgnoreCase ) )
171+ {
172+ return prop . Value . Type == JTokenType . String ? prop . Value . Value < string > ( ) : prop . Value . ToString ( ) ;
173+ }
174+ }
175+ }
176+
177+ // Fallback: convert to dictionary and search case-insensitively
178+ var boundDict = boundToken . ToObject < Dictionary < string , JToken > > ( ) ;
179+ if ( boundDict != null )
180+ {
181+ foreach ( var kv in boundDict )
182+ {
183+ if ( string . Equals ( kv . Key , "adsk3LeggedToken" , StringComparison . OrdinalIgnoreCase ) )
184+ {
185+ return kv . Value . Type == JTokenType . String ? kv . Value . Value < string > ( ) : kv . Value . ToString ( ) ;
186+ }
187+ }
188+ }
189+ }
190+ catch ( Exception ex )
191+ {
192+ Console . WriteLine ( $ "ExtractTokenFromWorkItem failed: { ex . Message } ") ;
193+ }
194+
195+ return null ;
196+ }
197+
123198 public void HandleDesignAutomationReadyEvent ( object sender , DesignAutomationReadyEventArgs e )
124199 {
125200 Console . WriteLine ( "<<!>> DA event raised." ) ;
126201
202+ var workItemId = Environment . ExpandEnvironmentVariables ( "%DAS_WORKITEM_ID%" ) ;
203+
204+ Console . WriteLine ( $ "<<!>> WorkItemId is '{ workItemId } '") ;
205+
206+ var workItemFileName = $ "{ workItemId } _job.das";
207+ Console . WriteLine ( $ "Looking for WorkItem file at '{ workItemFileName } ', exists: { File . Exists ( workItemFileName ) } ") ;
208+ var workItemContent = File . ReadAllText ( workItemFileName ) ;
209+
210+ var token = ExtractTokenFromWorkItem ( workItemContent ) ;
211+ Console . WriteLine ( $ "<<!>> Token is '{ token } '") ;
212+
127213 // Local Change
128214 //WorkItemFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
129215
@@ -149,19 +235,35 @@ public void HandleDesignAutomationReadyEvent(object sender, DesignAutomationRead
149235 }
150236
151237 Document ? doc = null ;
152- var cModel = setupReq ? . CloudModel ;
238+ var cModel = setupReq ? . OpenCloudModelLocation ;
153239 if ( cModel != null )
154240 {
155241 try
156242 {
243+ Console . WriteLine ( $ "Opening cloud model with Region: { cModel . Region } , Project: { cModel . ProjectGuid } , Model: { cModel . ModelGuid } ") ;
244+
157245 var cloudModelPath = ModelPathUtils . ConvertCloudGUIDsToCloudPath ( cModel . Region , cModel . ProjectGuid , cModel . ModelGuid ) ;
246+ Console . WriteLine ( doc == null ? $ "Cloud model path is { JsonConvert . SerializeObject ( cloudModelPath ) } " : "doc is not null before opening cloud model" ) ;
158247 doc = app ? . OpenDocumentFile ( cloudModelPath , new OpenOptions ( ) ) ;
159248 }
160249 catch ( Exception ex )
161250 {
162251 Console . WriteLine ( ex . Message ) ;
163252 }
164253 }
254+ else if ( setupReq ? . LocalFileName != null )
255+ {
256+ try
257+ {
258+ var localModelPath = Path . Combine ( WorkItemFolder , setupReq . LocalFileName ) ;
259+ Console . WriteLine ( $ "Opening local model at { localModelPath } ") ;
260+ doc = app ? . OpenDocumentFile ( localModelPath ) ;
261+ }
262+ catch ( Exception ex )
263+ {
264+ Console . WriteLine ( ex . Message ) ;
265+ }
266+ }
165267 else
166268 {
167269 doc = e . DesignAutomationData ? . RevitDoc ;
@@ -240,39 +342,85 @@ public void HandleDesignAutomationReadyEvent(object sender, DesignAutomationRead
240342 File . WriteAllText ( Path . Combine ( WorkItemFolder , "result.json" ) , output ) ;
241343
242344 // Default, save the rvt
243- bool saveRvt = setupReq ? . SaveRevitFile ?? true ;
345+ bool saveRvt = setupReq ? . GenerateOutputModel ?? true ;
244346 Console . WriteLine ( $ "{ nameof ( saveRvt ) } is set to { saveRvt } ") ;
245347 if ( saveRvt )
246348 {
247349 try
248350 {
249351 if ( doc . IsModelInCloud )
250352 {
353+ Console . WriteLine ( "Document is in cloud." ) ;
251354 if ( doc . IsWorkshared ) // work-shared/C4R model
252355 {
356+ Console . WriteLine ( "Document is C4R." ) ;
253357 // Syncronize with central
254358 SynchronizeWithCentralOptions swc = new SynchronizeWithCentralOptions ( ) ;
255359 swc . SetRelinquishOptions ( new RelinquishOptions ( /*relinquishAll*/ true ) ) ; // Should this be configurable?
256360 doc . SynchronizeWithCentral ( new TransactWithCentralOptions ( ) , swc ) ;
257361 }
258362 else
259363 { // Single user cloud model
260- var newLoc = setupReq ? . SaveCloudModelLocation ;
261- if ( newLoc != null )
262- {
263- doc . SaveAsCloudModel ( newLoc . AccountId , newLoc . ProjectId , newLoc . FolderId , newLoc . ModelName ?? Path . GetFileName ( doc . PathName ) ) ;
264- }
265- else
364+ Console . WriteLine ( "Document is single-user cloud model." ) ;
365+
366+ // Save the project locally (this will detach the model from the cloud, but we will re-upload at a new location)
367+ doc . SaveAs ( setupReq . LocalFileName ) ;
368+
369+ /* TODO: figure out if we need to make this work and how.
370+
371+ doc.SaveCloudModel();
372+
373+ Console.WriteLine($"uploading with token {token}");
374+ // TODO: For single-user cloud models, we need to publish the model after saving so that the changes are reflected in the cloud.
375+ var cmPath = doc.GetCloudModelPath();
376+ var dmClient = new DataManagementClient(null, new StaticAuthenticationProvider(token));
377+ var projId = "b." + setupReq.SaveCloudModelLocation.AccountId;
378+
379+ var publishTask = dmClient.ExecutePublishModelAsync(projId, new PublishModelPayload()
266380 {
267- doc . SaveCloudModel ( ) ;
268- }
381+ Type = TypeCommands.Commands,
382+ Attributes = new PublishModelPayloadAttributes()
383+ {
384+ Extension = new PublishModelPayloadAttributesExtension()
385+ {
386+ Type = TypeCommandtypePublishmodel.CommandsautodeskBim360C4RModelPublish,
387+ VarVersion = "1.0.0"
388+ }
389+ },
390+ Relationships = new PublishModelPayloadRelationships()
391+ {
392+ Resources = new PublishModelPayloadRelationshipsResources()
393+ {
394+ Data = new List<PublishModelPayloadRelationshipsResourcesData>
395+ {
396+ new PublishModelPayloadRelationshipsResourcesData()
397+ {
398+ Id = cmPath.GetModelGUID().ToString(),
399+ Type = TypeItem.Items
400+ }
401+ }
402+ }
403+ }
404+ });
405+
406+ publishTask.Wait();
407+ PublishModel respo = publishTask.Result;
408+ Console.WriteLine($"Publish result: {JsonConvert.SerializeObject(respo)}");
409+ */
269410 }
270411 }
271412 else
272- { // Save locally
273- RevitServices . Transactions . TransactionManager . Instance . ForceCloseTransaction ( ) ;
274- ModelPath path = ModelPathUtils . ConvertUserVisiblePathToModelPath ( "result.rvt" ) ;
275- doc . SaveAs ( path , new SaveAsOptions ( ) ) ;
413+ {
414+ /* TODO: Figure ou tif this is useful.
415+ var newLoc = setupReq?.SaveCloudModelLocation;
416+ if (newLoc != null)
417+ {
418+ Console.WriteLine($"Saving to new cloud model location with AccountId: {newLoc.AccountId}, ProjectId: {newLoc.ProjectId}, FolderId: {newLoc.FolderId}, ModelName: {newLoc.ModelName}");
419+ doc.SaveAsCloudModel(newLoc.AccountId, newLoc.ProjectId, newLoc.FolderId, newLoc.ModelName ?? Path.GetFileName(doc.PathName));
420+ }*/
421+
422+ // Save locally
423+ doc . Save ( ) ;
276424 }
277425 }
278426 catch ( Exception ex )
0 commit comments