Skip to content

Commit c387e30

Browse files
committed
update
1 parent a92411d commit c387e30

3 files changed

Lines changed: 182 additions & 36 deletions

File tree

src/DADynamoApp/DADynamoApp.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
</ItemGroup>
2727
<ItemGroup>
2828
<PackageReference Include="Autodesk.Forge.DesignAutomation.Revit" Version="2026.0.0" />
29+
<PackageReference Include="Autodesk.DataManagement" Version="2.0.0-beta4" />
2930
<PackageReference Include="DynamoVisualProgramming.Core" Version="$(DYNAMOCORE_VERSION)" ExcludeAssets="runtime" GeneratePathProperty="true" />
3031
<PackageReference Include="DynamoPlayer" Version="6.0.2" ExcludeAssets="runtime" GeneratePathProperty="true" />
3132
<PackageReference Include="GregRevitAuth" Version="$(GregRevitAuth_VERSION)" GeneratePathProperty="true">

src/DADynamoApp/DAEntrypoint.cs

Lines changed: 173 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
1-
using Autodesk.Revit.ApplicationServices;
1+
using Autodesk.DataManagement;
2+
using Autodesk.DataManagement.Model;
3+
using Autodesk.Revit.ApplicationServices;
24
using Autodesk.Revit.DB;
35
using DesignAutomationFramework;
6+
using DSCPython;
7+
using Dynamo.Applications;
8+
using Dynamo.Migration;
49
using Dynamo.Models;
10+
using Dynamo.PythonServices;
11+
using Dynamo.Scheduler;
512
using DynamoPlayer;
6-
using RevitServices.Persistence;
13+
using Greg.AuthProviders;
14+
using Newtonsoft.Json;
15+
using Newtonsoft.Json.Linq;
16+
using ProtoCore.DesignScriptParser;
717
using RevitServices.Elements;
18+
using RevitServices.Persistence;
19+
using System.Diagnostics;
820
using System.Reflection;
921
using System.Runtime.Loader;
10-
using System.Diagnostics;
11-
using Dynamo.Applications;
12-
using Dynamo.Scheduler;
13-
using static Dynamo.Models.DynamoModel;
1422
using 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

2026
namespace 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)

src/DADynamoApp/SetupDARequest.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace DADynamoApp
55
/// <summary>
66
/// Configuration details for a cloud model.
77
/// </summary>
8-
internal class CloudModelConfig
8+
internal class OpenCloudModelLocation
99
{
1010
/// <summary>
1111
/// Represents the region where the cloud model is hosted.
@@ -57,19 +57,16 @@ internal class SetupDARequest
5757
//
5858
// Summary:
5959
// Save the revit document to the default result.rvt file.
60-
[JsonProperty(nameof(SaveRevitFile), Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)]
61-
public bool SaveRevitFile { get; set; } = false;
60+
[JsonProperty(nameof(GenerateOutputModel), Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)]
61+
public bool GenerateOutputModel { get; set; } = false;
6262

6363
/// <summary>
64-
/// The location to save the current DA work model.
64+
/// Gets or sets the name of the Revit model file.
6565
/// </summary>
66-
[JsonProperty(nameof(SaveCloudModelLocation), Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)]
67-
public SaveCloudModelLocation? SaveCloudModelLocation { get; set; } = null;
66+
public string LocalFileName { get; set; } = string.Empty;
6867

69-
/// <summary>
70-
/// Gets or sets the configuration for the cloud model.
71-
/// </summary>
72-
[JsonProperty(nameof(CloudModel), Required = Required.Default, NullValueHandling = NullValueHandling.Ignore)]
73-
public CloudModelConfig? CloudModel { get; set; } = null;
68+
public OpenCloudModelLocation? OpenCloudModelLocation { get; set; } = null;
69+
70+
public SaveCloudModelLocation? SaveCloudModelLocation { get; set; } = null;
7471
}
7572
}

0 commit comments

Comments
 (0)