Introduction
In last post in had introduced about Use Revit Design Automation Update Revit Model In ACC. Today we will continue with rest of part to finish.
Part 1 : We have done with build the bundle Revit add-in for update assembly code.
Part 2 : Some step we will go throught:
- Obtain an Access Token
- Upload an AppBundle to Design Automation
- Publish an Activity
- Submit a WorkItem
- Download the results
Obtain an Access Token
This task produces a two-legged OAuth token with a scope sufficient to authenticate the remaining tasks in this walkthrough.
By the end of this task, you will know how to obtain a two-legged access token when the Client ID and Client Secret is known.
You use the following operations in this task:
using Autodesk.SDKManager;
using Autodesk.Authentication.Model;
using Autodesk.Authentication;
string? client_id = System.Environment.GetEnvironmentVariable("APS_CLIENT_ID");
string? client_secret = System.Environment.GetEnvironmentVariable("APS_CLIENT_SECRET");
SDKManager sdkManager = SdkManagerBuilder
.Create() // Creates SDK Manager Builder itself.
.Build();
var _authClient = new AuthenticationClient(sdkManager);
TwoLeggedToken twoLeggedToken = await _authClient.GetTwoLeggedTokenAsync(client_id, client_secret,
new List<Scopes>() { Scopes.DataRead, Scopes.BucketRead });
I had post a full topic about Authentication, you can visit at see at
Use Authentication SDK .NET Autodesk Platform Services (APS)
Create a nick name
APS uses the Client ID that was generated in the previous task to uniquely identify the app you created. The Client ID can be long and cryptic, and hence a source of irritation when you reference data you add to your app.
A Nickname is a way to map an app’s Client ID to an easy-to-use name that you can use in place of the Client ID.
You can assign a nickname to an app only if there is no data associated with that app. That is why we are creating the nickname before we do anything else with the app.
By the end of this task, you will know how to create a nickname to reference an app.
You will use the following API endpoint in this task:
1.Include the package in project configuration
<PackageReference Include="Autodesk.Forge" Version="1.9.9" />
<PackageReference Include="Autodesk.Forge.DesignAutomation" Version="6.0.1" />
2.Create a class to define configuration for design automation
DesignAutomateConfiguration configuration = new DesignAutomateConfiguration();
Autodesk.Forge.Core.ForgeService service =
new Autodesk.Forge.Core.ForgeService(
new HttpClient(
new ForgeHandler(Microsoft.Extensions.Options.Options.Create(new ForgeConfiguration()
{
ClientId = configuration.ClientId,
ClientSecret = configuration.ClientSecret
}))
{
InnerHandler = new HttpClientHandler()
})
);
var _designAutomation = new DesignAutomationClient(service);
3.Create a nick name
private async Task CreateOrUpdateNickName()
{
// create nickname
Console.WriteLine("Start Create NickName");
string? result = _designAutomation.GetNicknameAsync(_configuration.NickName)?.Result;
if (string.IsNullOrEmpty(result))
{
await _designAutomation.CreateNicknameAsync(_configuration.NickName, new NicknameRecord()
{
Nickname = _configuration.NickName,
});
Console.WriteLine("Created NickName: " + _configuration.NickName);
}
else
{
Console.WriteLine("NickName: " + result + " is exist");
_configuration.NickName = result;
}
Console.WriteLine("Created NickName: " + _configuration.NickName);
}
Upload an AppBundle
An AppBundle is a package of binaries and supporting files that make a Revit add-in.
By the end of this task you will be able to:
- Put together an AppBundle.
- Upload an AppBundle to Design Automation.
- Create an alias for the AppBundle.
- Create a new version of the AppBundle.
- Point the alias to the new version of the AppBundle.
private async Task EnsureAppBundle()
{
// get the list and check for the name
Console.WriteLine("Retrieving app bundles");
Page<string> appBundles = await _designAutomation.GetAppBundlesAsync().ConfigureAwait(false);
bool existAppBundle = false;
foreach (string appName in appBundles.Data)
{
if (appName.Contains(_configuration.AppName))
{
existAppBundle = true;
Console.WriteLine("Found existing app bundle, start delete: " + appName);
break;
}
}
if (!existAppBundle)
{
// check if ZIP with bundle is here
Console.WriteLine("Start Create new app bundle");
if (!File.Exists(_configuration.PackageZipPath))
throw new Exception("zip bundle not found at " + _configuration.PackageZipPath);
AppBundle appBundleSpec = new AppBundle()
{
Package = _configuration.AppName,
Engine = _configuration.EngineName,
Id = _configuration.AppName,
Description = _configuration.BundleDescription
};
AppBundle newAppVersion = await _designAutomation.CreateAppBundleAsync(appBundleSpec).ConfigureAwait(false);
if (newAppVersion == null) throw new Exception("Cannot create new app");
Console.WriteLine("Created new bundle: " + newAppVersion.Id);
// create alias pointing to v1
string id = _configuration.Alias.ToString().ToLower();
Autodesk.Forge.DesignAutomation.Model.Alias aliasSpec = new Autodesk.Forge.DesignAutomation.Model.Alias()
{ Id = id, Version = 1 };
Autodesk.Forge.DesignAutomation.Model.Alias newAlias = await _designAutomation
.CreateAppBundleAliasAsync(_configuration.AppName, aliasSpec).ConfigureAwait(false);
Console.WriteLine("Created new alias version: " + newAlias.Id + " pointing to " + newAlias.Version);
// upload the zip with .bundle
UploadAppBundleBits(newAppVersion.UploadParameters, _configuration.PackageZipPath).Wait();
}
}
Publish an Activity
An Activity is an action that can be executed in Design Automation. You create and post Activities to run specific AppBundles.
Bellow is sample code task to publish an Activity
private async Task EnsureActivity()
{
Console.WriteLine("Start Retrieving activity");
Page<string> activities = await _designAutomation.GetActivitiesAsync().ConfigureAwait(false);
bool existActivity = false;
foreach (string activity in activities.Data)
{
if (activity.Contains(_configuration.ActivityFullName))
{
existActivity = true;
Console.WriteLine("Found existing activity, id : " + activity);
break;
}
}
if (existActivity) return;
string commandLine =
$@"""$(engine.path)\\revitcoreconsole.exe"" /al ""$(appbundles[{_configuration.AppName}].path)"" ""$(args[inputFile].path)"" ""$(args[inputJson].path)""";
Activity activitySpec = new Activity()
{
Id = _configuration.ActivityName,
Appbundles = new List<string>() { _configuration.AppBundleFullName },
CommandLine = new List<string>() { commandLine },
Engine = _configuration.EngineName,
Parameters = new Dictionary<string, Autodesk.Forge.DesignAutomation.Model.Parameter>()
{
{
"inputFile", new Autodesk.Forge.DesignAutomation.Model.Parameter()
{
Description = "input revit model infor to run in acc", LocalName = "input.json",
Ondemand = false, Required = true,
Verb = Verb.Get, Zip = false
}
},
{
"inputJson",
new Parameter()
{
Description = "input data", LocalName = "params.json", Ondemand = false, Required = false,
Verb = Verb.Get, Zip = false
}
},
// {
// "result",
// new Autodesk.Forge.DesignAutomation.Model.Parameter()
// {
// Description = "Resulting JSON File",
// LocalName = _configuration.ResultFileName,
// Ondemand = false,
// Required = true,
// Verb = Verb.Put,
// Zip = false,
// }
// },
},
Settings = new Dictionary<string, ISetting>()
{
{ "script", new StringSetting() { Value = _configuration.Script } }
},
Description = _configuration.ActivityDescription,
};
Activity newActivity = await _designAutomation.CreateActivityAsync(activitySpec).ConfigureAwait(false);
Console.WriteLine("Created new activity: " + newActivity.Id);
// specify the alias for this Activity
string id = _configuration.Alias.ToString().ToLower();
Autodesk.Forge.DesignAutomation.Model.Alias aliasSpec = new Autodesk.Forge.DesignAutomation.Model.Alias()
{ Id = id, Version = 1 };
Autodesk.Forge.DesignAutomation.Model.Alias newAlias =
await _designAutomation.CreateActivityAliasAsync(_configuration.ActivityName, aliasSpec)
.ConfigureAwait(false);
Console.WriteLine($"Created new alias for activity Version: {newAlias.Version} ID: {newAlias.Id}: ");
}
Submit a WorkItem
When you post a WorkItem to Design Automation, you are instructing Design Automation to execute an Activity.
The relationship between an Activity and a WorkItem can be thought of as a “function definition” and “function call”, respectively. Named parameters of the Activity have corresponding named arguments of the WorkItem. Like in function calls, optional parameters of the Activity can be skipped and left unspecified while posting a WorkItem.
private async Task<WorkItemStatus> ExecuteWorkItem(string data)
{
Console.WriteLine("ActivityId: " + _configuration.ActivityFullName);
// XrefTreeArgument treeArgument = await BuildUploadURL(forgeToken);
// Console.WriteLine("TreeArgument: " + treeArgument.Url);
XrefTreeArgument inputJsonArgument = BuildInputJsonArgument(data);
WorkItem workItem = new WorkItem()
{
ActivityId = _configuration.ActivityFullName,
Arguments = new Dictionary<string, IArgument>()
{
{ "inputFile", BuildInputRevitModelJsonArgument() },
// { "result", treeArgument },
{ "inputJson", inputJsonArgument },
{
"onComplete", new XrefTreeArgument
{
Verb = Verb.Post,
Url = _configuration.CallbackUrl
}
},
{"adsk3LeggedToken", new StringArgument(_configuration.AccessToken3Legged)}
},
LimitProcessingTimeSec = 3600,
};
Console.WriteLine("Start Create WorkItem");
WorkItemStatus workItemStatus = await _designAutomation.CreateWorkItemAsync(workItem).ConfigureAwait(false);
//StartWebsocket(workItem);
Console.WriteLine("WorkItemStatus: " + workItemStatus.Status);
Console.WriteLine("Working Item Id: " + workItemStatus.Id);
//wait until the work item is finished
while (!workItemStatus.Status.IsDone())
{
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
workItemStatus = await _designAutomation.GetWorkitemStatusAsync(workItemStatus.Id).ConfigureAwait(false);
// if (workItemStatus.Status != Status.Success && workItemStatus.Status != Status.Inprogress)
// {
// Console.WriteLine("Status: " + workItemStatus.Status);
// Console.WriteLine(workItemStatus.ReportUrl);
// }
}
Console.WriteLine("WorkItemStatus: " + workItemStatus.Status);
return workItemStatus;
}
private XrefTreeArgument BuildInputJsonArgument(string jsonString)
{
// Json pass to Bundle Add-in Revit for execute
// parse json string to json object
var inputJson = JsonConvert.DeserializeObject(jsonString)!;
XrefTreeArgument inputJsonArgument = new XrefTreeArgument()
{
Url = "data:application/json, " + ((JArray)inputJson).ToString(Formatting.None).Replace("\"", "'")
};
return inputJsonArgument;
}
private XrefTreeArgument BuildInputRevitModelJsonArgument()
{
// Json pass to Bundle Add-in Revit for execute
dynamic inputJson = new JObject();
inputJson.Region = _configuration.Region;
inputJson.ProjectGuid = _configuration.ProjectGuid;
inputJson.Leg2Token = _configuration.Leg2Token;
inputJson.ObjectKey = _configuration.BucketKey;
inputJson.ObjectId = _configuration.ObjectKey;
inputJson.ModelGuid = _configuration.ModelGuid;
XrefTreeArgument inputJsonArgument = new XrefTreeArgument()
{
Url = "data:application/json, " + ((JObject)inputJson).ToString(Formatting.None).Replace("\"", "'")
};
return inputJsonArgument;
}
Write report work item job executed
After you submit a WorkItem, you must wait for it to complete and download the results. The code bellow will download txt format report into local for review by print the console output workitem executed.
void WriteReport(WorkItemStatus workItemStatus)
{
Console.WriteLine("Status:");
string json = JsonConvert.SerializeObject(workItemStatus, Formatting.Indented);
Console.WriteLine(json);
}
void WriteLogDetail(WorkItemStatus workItemStatus)
{
Console.WriteLine("Log Detail:");
string reportUrl = workItemStatus.ReportUrl;
// download txt file from url
byte[] bytes = new HttpClient().GetByteArrayAsync(reportUrl).Result;
PrintText(bytes);
}
void PrintText(byte[] bytes)
{
string resultString = System.Text.Encoding.UTF8.GetString(bytes);
Console.WriteLine(resultString);
}
Result
I'm happy when try to build some application to see from user interface, this is how it look like :
Top comments (0)