5 Nov 2018

Simple introduction to Design Automation for Inventor

Simple introduction to Design Automation for Inventor

What we'll need

  • Inventor SDK and Visual Studio 2017 to build our Inventor plugin

  • HTTP client for configuring our pipeline using the new APIs

    • We'll be using postman and cURL syntax

    • If you prefer working with GUI, note that all the cURL examples in this article can be imported into postman

Phase 1: Inventor plugin

If you have problems with this part, or simply want to skip the creation of an Inventor plugin, here's a zip file with our plugin's source code and binaries: InventorThumbnailAddin.zip.

Edit: there's now a Visual Studio Project Template that you can use to bootstrap your Inventor plugin solution.

With Inventor SDKs installed, open Visual Studio and create a new Autodesk Inventor AddIn project. We're going to call it InventorThumbnailAddin:

Creating new Visual Studio project

Create a new file in the project, Automation.cs, and use it to define an Automation class with the following implementation:

using System;
using System.Runtime.InteropServices;
using Inventor;

namespace InventorThumbnailAddin
{
    [ComVisible(true)]
    public class Automation
    {
        InventorServer m_server;

        public Automation(InventorServer server)
        {
            m_server = server;
        }

        public void Run(Document document)
        {
            string documentFolder = System.IO.Path.GetDirectoryName(document.FullFileName);
            string imageFilename = System.IO.Path.Combine(documentFolder, "thumbnail.bmp");
            if (document.DocumentType == DocumentTypeEnum.kPartDocumentObject)
            {
                Camera camera = m_server.TransientObjects.CreateCamera();
                camera.SceneObject = (document as PartDocument).ComponentDefinition;
                camera.ViewOrientationType = ViewOrientationTypeEnum.kIsoTopRightViewOrientation;
                camera.ApplyWithoutTransition();
                camera.SaveAsBitmap(imageFilename, 256, 256, Type.Missing, Type.Missing);
            }
        }

        public void RunWithArguments(Document document, NameValueMap args)
        {
            Run(document);
        }
    }
}

In StandardAddInServer.cs file (which should be already created by the Visual Studio project template), change the implementation of the StandardAddInServer class (but keep the GUID that was created for you):

using System;
using System.Runtime.InteropServices;
using Inventor;

namespace InventorThumbnailAddin
{
    [GuidAttribute("YOUR GUID")]
    public class StandardAddInServer : Inventor.ApplicationAddInServer
    {
        private InventorServer m_server;
        private Automation m_automation;

        public StandardAddInServer()
        {
        }

        #region ApplicationAddInServer Members

        public void Activate(Inventor.ApplicationAddInSite addInSiteObject, bool firstTime)
        {
            m_server = addInSiteObject.InventorServer;
            m_automation = new Automation(m_server);
        }

        public void Deactivate()
        {
            Marshal.ReleaseComObject(m_server);
            m_server = null;

            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        public void ExecuteCommand(int commandID)
        {
        }

        public dynamic Automation
        {
            get
            {
                return m_automation;
            }
        }

        #endregion
    }
}

Next, locate the Inventor addin file (Autodesk.InventorThumbnailAddin.Inventor.addin), and replace its contents with the following XML, once again using your own GUID:

<Addin Type="Plugin">
  <ClassId>{YOUR GUID}</ClassId>
  <ClientId>{YOUR GUID}</ClientId>
  <DisplayName>Inventor Thumbnail Addin</DisplayName>
  <Description>Addin for generating thumbnail images from Inventor part files.</Description>
  <Assembly>InventorThumbnailAddin.dll</Assembly>
</Addin>

Finally, open the properties of the project, navigate to Build Events, and replace the Post-build commands with the following:

call "%VS140COMNTOOLS%vsvars32" x86
mt.exe -manifest "$(ProjectDir)InventorThumbnailAddin.X.manifest" -outputresource:"$(TargetPath)"
XCopy "$(ProjectDir)Autodesk.InventorThumbnailAddin.Inventor.addin" "$(TargetDir)" /y

Configuring project build events

These will make sure that our plugin DLL is properly signed and that it's accompanied by the corresponding addin file.

You should now be able to build the project, generating a couple of files in the bin/Debug subfolder. We are going to use those when setting up the Design Automation pipeline.

Phase 2: Design automation pipeline

Before jumping to the individual Design Automation APIs, let's discuss some of the basic concepts used by this service. To summarize the official documentation, there are 4 main types of objects: engines, app bundles, activities, and work items:

  • Engine refers to the actual application that will process your tasks, for example, Revit or Inventor,

  • App bundle represents a collection of files (typically plugin binaries) used to perform a specific task using one of the available engines,

  • Activity is basically a task template, defining types of inputs, outputs, and the app bundle that will process them,

  • and finally, work item is an instance of a task with specific inputs and outputs (typically URLs for files to be downloaded from or uploaded to)

There's one very important thing to understand: each app bundle, activity, or work item object can have multiple versions, and in order to properly reference a specific object, you must create something called an alias which is a custom string that refers to a specific version of the object. Kind of like git tags. For example, when you create a 2nd version of an activity called GenerateThumbnail, there's an endpoint (we will use it later) you can call to have an alias - let's say prod - point to this version. Then, in order to reference this specific activity, you would use the activity name followed by plus sign and the alias, for example, GenerateThumbnail+prod. Moreover, in some cases you will see the ID prefixed with a hash and a dot, for example, YhryNMLor4R1maFhY4zER8unpISoP5E4.GenerateThumbnail+prod - that string fully identifies the object with you as its owner.

Now that we understand the concepts, we're ready to setup our Design Automation pipeline!

Getting an access token

We will use a 2-legged authentication token for all our Forge requests. Follow this step-by-step tutorial for more details on how to obtain such information. Most of the Design Automation APIs only require one OAuth scope: code:all, but in this article we may also need to create buckets and objects using the Data Management APIs, so make sure to also include scopes bucket:create, bucket:read, data:create, data:read, and data:write.

curl -X POST \
  https://developer.api.autodesk.com/authentication/v1/authenticate \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'client_id=<YOUR CLIENT ID>&client_secret=<YOUR CLIENT SECRET>&grant_type=client_credentials&scope=bucket%3Acreate%20bucket%3Aread%20data%3Acreate%20data%3Aread%20data%3Awrite%20code%3Aall'

Use the token from the response whenever you see Authorization: Bearer <YOUR ACCESS TOKEN> header in the rest of this article.

Creating an app bundle

First of all, let's package our Inventor plugin binaries into a bundle. A bundle is basically a zipped folder with your binaries and a PackageContents.xml file describing the content of the folder. In our example we're going to take the output files from our Inventor plugin build, and zip them in the following folder structure:

Bundle folder structure

Note that the PackageContents.xml file is under the InventorThumbnailAddin.bundle folder, and not under the Contents folder.

In the PackageContents.xml file we describe our plugin, including the GUID, and a relative path to the addin manifest. Optionally you can also include additional information about the author/company behind the plugin.

<?xml version="1.0" encoding="utf-8" ?>
<ApplicationPackage SchemaVersion="1.0" Version="1.0" ProductCode="{YOUR GUID}" Name="..." Description="..." Author="...">
    <CompanyDetails Name="..." Phone="..." Url="..." Email="..." />
    <Components>
        <!-- For Inventor Engine, "Platform" attribute must be "Inventor" -->
        <RuntimeRequirements  OS="Win64" Platform="Inventor" />
        <!-- For Inventor Plug-in, the "Module" attribute must point to the .addin manifest file. -->
        <ComponentEntry LoadOnAutoCADStartup="False" LoadOnCommandInvocation="False"
            AppDescription="Thumbnail App Package."
            ModuleName="./Contents/Autodesk.InventorThumbnailAddin.Inventor.addin" AppName="InventorThumbnailAddin"/>
    </Components>
    <EnvironmentVariables>
    </EnvironmentVariables>
</ApplicationPackage>

With our InventorThumbnailAddin.bundle.zip file ready, let's create a new app bundle - let's say ThumbnailBundle - and a new alias to its first version.

curl -X POST \
  https://developer.api.autodesk.com/da/us-east/v3/appbundles \
  -H 'Authorization: Bearer <YOUR ACCESS TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
	"id": "ThumbnailBundle",
	"description": "Inventor plugin for generating thumbnails from IPT files.",
	"engine": "Autodesk.Inventor+23"
  }'

The response will be a JSON with a structure similar to this one:

{
    "uploadParameters": {
        "endpointURL": "https://dasprod-store.s3.amazonaws.com",
        "formData": {
            "key": "...",
            "content-type": "...",
            "policy": "...",
            "success_action_status": "200",
            "success_action_redirect": "",
            "x-amz-signature": "...",
            "x-amz-credential": "...",
            "x-amz-algorithm": "...",
            "x-amz-date": "...",
            "x-amz-server-side-encryption": "...",
            "x-amz-security-token": "..."
        }
    },
    "engine": "Autodesk.Inventor+23",
    "description": "Inventor plugin for generating thumbnails from IPT files.",
    "version": 1,
    "id": "<OWNER ID>.ThumbnailBundle"
}

uploadParameters are the arguments we will need to upload our app bundle to a location where the Design Automation service can find it. Make a POST request to the provided endpoint URL, including all the arguments as form data, with an extra file filed where you provide your bundle zip file.

curl -X POST \
  https://dasprod-store.s3.amazonaws.com \
  -H 'content-type: multipart/form-data' \
  -F key=... \
  -F policy=... \
  -F content-type=... \
  -F success_action_status=200 \
  -F success_action_redirect= \
  -F x-amz-signature=... \
  -F x-amz-credential=... \
  -F x-amz-algorithm=... \
  -F x-amz-date=... \
  -F x-amz-server-side-encryption=... \
  -F x-amz-security-token=... \
  -F file=@/path/to/your/zipfile

If you don't want to upload the binaries from command line, you can import the curl command into postman, fill in all the fields based on your uploadParameters, switch the type of the file parameter to File, and open the zip file for uploading.

Uploading binaries using postman

Finally, to make our new app bundle addressable in subsequent steps, we need to create an alias - let's call it prod - to its first version:

curl -X POST \
  https://developer.api.autodesk.com/da/us-east/v3/appbundles/ThumbnailBundle/aliases \
  -H 'Authorization: Bearer <YOUR ACCESS TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
	"id": "prod",
	"version": 1
  }'

Defining an activity

Next, we define an activity GenerateThumbnail that will use our ThumbnailBundle, expecting one IPT file on the input, and generating a single BMP file on the output:

curl -X POST \
  https://developer.api.autodesk.com/da/us-east/v3/activities \
  -H 'Content-Type: application/json' \
  -d '{
    "commandLine": [
        "$(engine.path)\\InventorCoreConsole.exe /i $(args[PartFile].path) /al $(appbundles[ThumbnailBundle].path)"
    ],
    "parameters": {
        "PartFile": {
            "verb": "get",
            "description": "IPT file or ZIP with assembly to process"
        },
        "OutputBmp": {
            "zip": false,
            "ondemand": false,
            "optional": true,
            "verb": "put",
            "description": "Generated thumbnail",
            "localName": "thumbnail.bmp"
        }
    },
    "engine": "Autodesk.Inventor+23",
    "appbundles": ["<OWNER ID>.ThumbnailBundle+prod"],
    "description": "Generate thumbnails for IPT files (Inventor 2019).",
    "id": "GenerateThumbnail"
  }'

Let's look at the different pieces of the JSON:

  • we give our activity an id and a description

  • we specify a list of appbundles it uses (don't forget to replace <OWNER ID> with the hash you received when creating your app bundle), and the engine that will run them

  • we define an input parameter called PartFile

    • the activity knows it is an input parameter because we're asking it to "GET" the file from a specific URL when it's instantiated

  • we define an output parameter called OutputBmp as a file called thumbnail.bmp that could (optionally) be generated by the activity

  • finally, we specify a commandLine that will run the actual engine executable with our parameters

    • the $(<something>.path) syntax will be resolved with actual paths to engine executables, input/output files, or app bundles when the activity is instantiated

As with the app bundle, we must create an alias that will point to the first version of our activity:

curl -X POST \
  https://developer.api.autodesk.com/da/us-east/v3/activities/GenerateThumbnail/aliases \
  -H 'Authorization: Bearer <YOUR ACCESS TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
	"id": "prod",
	"version": 1
  }'

Executing a work item

Before running our GenerateThumbnail activity, we need to prepare the input and output URLs. You can use any storage solution that supports signed URLs for pre-authorized download and upload. In our example we will leverage the POST /oss/v2/buckets/:bucketKey/objects/:objectKey/signed endpoint from Data Management APIs, creating one signed URL with read access to an existing IPT file, and one signed URL with readwrite access to a location where our thumbnail can be uploaded to.

curl -X POST \
  'https://developer.api.autodesk.com/oss/v2/buckets/<YOUR BUCKET KEY>/objects/<YOUR INPUT IPT FILENAME>/signed?access=read' \
  -H 'Authorization: Bearer <YOUR ACCESS TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{}'
curl -X POST \
  'https://developer.api.autodesk.com/oss/v2/buckets/<YOUR BUCKET KEY>/objects/<YOUR OUTPUT BMP FILENAME>/signed?access=readwrite' \
  -H 'Authorization: Bearer <YOUR ACCESS TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{}'

With the URLs ready, let's create our work item:

curl -X POST \
  https://developer.api.autodesk.com/da/us-east/v3/workitems \
  -H 'Authorization: Bearer <YOUR ACCESS TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "activityId": "<OWNER ID>.GenerateThumbnail+prod",
    "arguments": {
        "PartFile": {
            "url": "<YOUR INPUT FILE URL>",
            "zip": false
        },
        "OutputBmp": {
            "url": "<YOUR OUTPUT FILE URL>",
            "verb": "put"
        }
    }
}'

The respond should look similar to this:

{
    "status": "pending",
    "stats": {
        "timeQueued": "..."
    },
    "id": "<WORK ITEM ID>"
}

With your own work item ID, you can then query its status:

curl -X GET \
https://developer.api.autodesk.com/da/us-east/v3/workitems/<WORK ITEM ID> \
-H 'Authorization: Bearer <YOUR ACCESS TOKEN>'

If the task completes successfully, you should see a response similar to this:

{
    "status": "success",
    "reportUrl": "...",
    "stats": {
        "timeQueued": "...",
        "timeDownloadStarted": "...",
        "timeInstructionsStarted": "...",
        "timeInstructionsEnded": "...",
        "timeUploadEnded": "..."
    },
    "id": "<WORK ITEM ID>"
}

If there's been any issue, the reportUrl field links to a text file with additional information.

And that's it. The second pre-signed URL ("<YOUR UPLOAD FILE URL>") you created should now point to a bitmap file with a thumbnail of your input Inventor part file.

Related Article